/*
Theme Name:   Marzan
Theme URI:    https://github.com/marzansecurity/marzan-web
Description:  Child theme of GeneratePress implementing the Marzan Security design system v2, shared by every Marzan content site. Two-layer tokens: CORE is shared across every brand, BRAND is switched per data-brand attribute, which the site plugin sets. All behaviour lives in the marzan-core plugin so this theme can be replaced without losing a site.
Author:       Marzan Security
Author URI:   https://marzansecurity.com
Template:     generatepress
Version:      2.1.0
Requires PHP: 8.2
License:      GNU General Public License v2 or later
Text Domain:  batterysafety
*/

/* ===========================================================================
   FONTS
   Declared once, in theme.json, not here. WordPress emits the @font-face rules
   from theme.json for both the front end and the block editor, so the editor
   previews in the real typefaces. Duplicating them in this file registered every
   face twice.

   One self-hosted variable file in assets/fonts/: Open Sans 300-800, latin
   subset, 42 KB, covering headings and body both. Nothing is fetched from a
   third party on the critical path. It is preloaded in functions.php because it
   renders the H1 and the first paragraph.
   ======================================================================== */

/* ===========================================================================
   LAYER 1 - CORE TOKENS
   Identical for every Marzan brand. Do not put brand colours in here.
   ======================================================================== */

:root {
	/* ── Typography ─────────────────────────────────────────────────── */
	/*
	 * One family, both roles.
	 *
	 * The 2021 brand guidelines specify Gotham, with Open Sans named as the
	 * sanctioned alternative - "a free Google font that is also available on
	 * Adobe Fonts". Gotham is commercial and the desktop OTFs in the brand folder
	 * do not carry web-embedding rights, so Open Sans is the on-brand choice that
	 * can actually be served. It replaces both Archivo and Montserrat, which the
	 * v2 design brief had picked as Gotham substitutes before this document was
	 * available.
	 *
	 * One variable file, weights 300-800, latin subset, 42 KB - against 71 KB for
	 * the two families it replaces. There is no width axis in the file Google
	 * ships (font-stretch: 100%), so hierarchy is carried entirely by weight:
	 * 800 on headings against 400 body is the whole system.
	 */
	--font-heading: 'Open Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
	--font-body:    'Open Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;

	/* Fluid throughout, so there is no jump at a breakpoint and the
	   intermediate sizes need no overrides. */
	--text-display:    clamp(1.875rem, 1.2rem + 2.8vw, 3rem);      /* 30 → 48px */
	--text-display-xl: clamp(2.25rem, 1.4rem + 3.6vw, 3.75rem);    /* 36 → 60px, full-width hero only */
	--text-h2:         clamp(1.625rem, 1.35rem + 1.2vw, 2rem);     /* 26 → 32px */
	--text-h3:         clamp(1.25rem, 1.15rem + 0.5vw, 1.5rem);    /* 20 → 24px */
	--text-h4:         1.125rem;                                   /* 18px */
	--text-body-lg:    clamp(1.0625rem, 1rem + 0.2vw, 1.125rem);   /* 17 → 18px */
	--text-body:       1rem;                                       /* 16px */
	--text-article:    1.125rem;                                   /* 18px - long-form */
	--text-small:      0.875rem;                                   /* 14px */
	--text-button:     0.9375rem;                                  /* 15px */

	--leading-display: 1.05;
	--leading-h2:      1.15;
	--leading-h3:      1.25;
	--leading-h4:      1.3;
	--leading-body:    1.5;
	--leading-body-lg: 1.6;
	--leading-article: 1.7;

	/* ── Reading measure ────────────────────────────────────────────── */
	/*
	 * A page may be 1280px wide. A paragraph may not.
	 *
	 * The design brief specifies 70ch and states the goal as 60-75 characters a
	 * line. Those two do not agree in Montserrat: 1ch is the width of the "0"
	 * glyph, which here is 12px against an average character width of 10.6px, so
	 * 70ch measured out at 840px and 79 characters - above the range it was meant
	 * to produce.
	 *
	 * Re-measured when the body typeface changed to Open Sans, as the note that
	 * stood here said to. Two findings. First, the old value was not what this
	 * comment claimed: counting rendered characters per line across the site's own
	 * prose, 696px in Montserrat 500 lands on 73.8 characters, not ~68 - at the
	 * very top of the 60-75 band rather than the middle of it. Second, Open Sans
	 * averages 8.17px a character against Montserrat's 8.89px at 18px, so it needs
	 * roughly 8% less width for the same line.
	 *
	 * 640px holds the line length exactly where it has been (73.9 characters
	 * measured), so this change moves the typeface and nothing else. Dropping to
	 * 600px would land on 68.9 and finally hit the middle of the band - a better
	 * column for dense regulatory prose, and a separate decision from swapping a
	 * font. It is a one-line change if that is wanted.
	 *
	 * Expressed in rem, not ch, for a second reason found while measuring: 1ch is
	 * relative to the element's own font-size, so a 14px caption and an 18px
	 * paragraph carrying the same token ended up in columns of 542px and 696px.
	 * That is not one column, it is several. rem keeps the column identical
	 * whatever size the text inside it is.
	 */
	--measure:         40rem;     /* 640px - 73.9 characters at 18px Open Sans */
	--measure-intro:   34.5rem;   /* 552px - same ratio to the measure as before */
	/*
	 * This one grew rather than shrank. Open Sans 800 sets 3% wider than Archivo
	 * 800 on the site's actual headings, so holding the old 480px would have cost
	 * a line on the longer ones - the opposite of what the cap is for.
	 */
	--measure-heading: 31rem;     /* 496px - a heading over three lines reads
	                                 worse than one over two */

	/* ── Neutral and semantic ───────────────────────────────────────── */
	/*
	 * The neutrals are not neutral.
	 *
	 * Marzan_BrandGuidelines_2021.pdf, page 7: "there is also a range of grey hues
	 * that can be used. The grey is an opacity percentage of our darkest blue
	 * colour" - 8%, 15%, 30% and 50% of #10395B. The site had generic greys
	 * (#F5F5F5, #C7C7C7, #8A8A8A) inherited from the old Elementor build, which
	 * read as slightly warm against the navy. These are the four tints composited
	 * on white, so the whole surface stack is the brand colour at low opacity.
	 */
	--surface:         #FFFFFF;
	--surface-muted:   #ECEFF2;   /* navy at  8% */
	--surface-inverse: var(--brand-primary);

	/*
	 * Text that sits on the accent fill - its own role, not --brand-primary.
	 *
	 * The label on an orange button was --brand-primary at 5.14:1: above AA and
	 * visibly soft, because navy and orange are complementary and vibrate against
	 * each other rather than separating cleanly. Reported as "a bit dull", which
	 * was the right observation.
	 *
	 * White is the instinctive answer and the wrong one: #FFFFFF on #FF8C30 is
	 * 2.32:1, and #F5F5F5 is 2.13:1. That is the exact defect correction 3 of the
	 * v2 brief fixed on the old Marzan site. White on a saturated mid-tone looks
	 * cleaner than it measures.
	 *
	 * This is the brand navy taken darker until the label separates properly:
	 * 6.57:1, a third more contrast, same hue family. Paired with weight 700 on
	 * the primary button. Getting white to pass would mean dropping the fill to
	 * about #B25A05, which is brown, not the brand's orange.
	 */
	--on-accent:       #0A2740;   /* 6.57:1 on --brand-accent */

	--text-heading:    var(--brand-primary);
	--text-body-color: #45565E;   /* 7.7:1 on white - full hex, never rgba */
	/*
	 * The design brief specifies #6B7A80 here. Measured, that is 4.45:1 on white
	 * and 4.08:1 on the muted surface - both below the 4.5:1 the brief itself sets
	 * as the standard, and this colour carries 14px text: source attributions,
	 * breadcrumbs, byline dates, CTA blurbs. Darkened to the nearest value that
	 * clears AA on both surfaces with margin. Same hue.
	 *
	 * Darkened again when the muted surface moved to the brand's 8% navy tint:
	 * against #ECEFF2 the old #626F75 measured 4.49:1, a hundredth under AA, and
	 * this colour carries 14px text. Re-measured values are in design-system.md.
	 */
	--text-muted:      #5C686E;
	--text-on-inverse: #F5F5F5;

	/*
	 * Two border roles, because they answer to different rules.
	 * --border-subtle is decoration (table rules, hairlines, dividers) and has no
	 * contrast requirement. --border-control is the visible boundary of an
	 * interactive control, which WCAG 1.4.11 requires to reach 3:1 - the measured
	 * #C7C7C7 manages 1.69:1, so inputs would have had no perceivable edge.
	 *
	 * --border-subtle is the brand's 30% navy tint. --border-control is not one of
	 * the four published tints, and cannot be: the 50% tint composites to #889CAD,
	 * which measures 2.84:1 on white and 2.6:1 on the muted surface - below the 3:1
	 * WCAG 1.4.11 requires. It is the same navy taken further down (62%) until it
	 * clears 3:1 on both surfaces. The brand's tint ladder governs decoration; a
	 * control edge answers to the standard first.
	 */
	--border-subtle:   #B7C4CE;   /* navy at 30% - decorative only, never a control edge */
	--border-control:  #6B8499;   /* navy at 62% - inputs, selects, radio and checkbox boxes */
	--border-strong:   #40607C;   /* hover state on a control */

	--color-success:   #1E7B4D;
	--color-warning:   #B45309;
	--color-error:     #C0392B;
	--color-info:      #005C94;

	/*
	 * The blue range, straight from page 7 of the guidelines. Every one of these is
	 * in the Marzan logo - the mark is built from these five and nothing else, no
	 * orange anywhere in it - and in the guidelines' own layouts #1BA2DD and
	 * #005C94 are the two most-used colours on the page. On this site they were
	 * defined and then barely used. --blue-800 was missing entirely.
	 *
	 * One rule, measured: --blue-400 is a graphic colour, not a text background.
	 * White on it is 2.89:1 and navy on it is 4.13:1 - it carries large text and
	 * icons and nothing else. When text has to sit on blue, the background is
	 * --blue-700 (white at 7.1:1) or --blue-800 (8.84:1 with --text-on-inverse).
	 */
	--blue-100: #9DCAEC;
	--blue-400: #1BA2DD;
	--blue-700: #005C94;
	--blue-800: #004874;

	/* ── Shape ──────────────────────────────────────────────────────── */
	/* The contrast between soft 25px on media and tight 10px on buttons is the
	   strongest piece of handwriting in this design. Keep it consistent. */
	--radius-card:   25px;
	--radius-button: 10px;
	--radius-input:  10px;
	--radius-badge:  999px;

	/* Four-step elevation, navy-tinted rather than black. One level per role;
	   an element rises exactly one step on interaction, never two. */
	--shadow-1: 0 1px   2px        rgba(16, 57, 91, 0.08);
	--shadow-2: 0 6px  24px  -4px  rgba(16, 57, 91, 0.18);
	--shadow-3: 0 12px 32px  -6px  rgba(16, 57, 91, 0.24);
	--shadow-4: 0 24px 64px -12px  rgba(16, 57, 91, 0.32);
	--shadow-card:       var(--shadow-2);
	--shadow-card-hover: var(--shadow-3);

	/* ── Space - 4px base ───────────────────────────────────────────── */
	--space-1:  0.25rem;
	--space-2:  0.5rem;
	--space-3:  0.75rem;
	--space-4:  1rem;
	--space-5:  1.25rem;
	--space-6:  1.5rem;
	--space-8:  2rem;
	--space-12: 3rem;
	--space-16: 4rem;
	--space-20: 5rem;

	--container-max: 1280px;
	/*
	 * The header is wider than the page it sits on: 1440px against 1280px, which
	 * is the relationship accukluis.nl has. The logo and the nav therefore sit
	 * ~73px outside the article column on a 1440px screen rather than lining up
	 * with it. That is the point - a header that agrees with the content edge to
	 * the pixel reads as one tall column, and the offset breaks that up.
	 *
	 * Deliberately not aligned to anything below it. Asked for on 2026-09-07.
	 */
	--container-header: 90rem;  /* 1440px */
	--wide:          77rem;     /* 1232px - the section-block width: the 1280px
	                               container less its two 24px gutters, so a
	                               section block reaches the page edge exactly */
	/*
	 * The page edge. One value, used by all four containers that touch it: the
	 * header bar, the article container, a full-bleed band's inner container and
	 * the footer.
	 *
	 * They used to disagree. The header was 24px, GeneratePress's .inside-article
	 * was left at its own 30px, a band's inner container was 20px and the footer
	 * was 30px. On a desktop the text column is centred and none of that shows; on
	 * a 375px phone it is the only thing setting the left edge, so the logo, the
	 * H1, a band heading and the footer each started at a different x and the page
	 * read as though nothing lined up. 24px is the figure in the design brief
	 * (20-24px) and the one the header already used.
	 */
	--gutter:        1.5rem;    /* 24px - the page edge, everywhere */
	--section-y:     clamp(3rem, 2rem + 4vw, 5rem);
	--card-gap:      2rem;      /* 32px - was 20px, cards stuck together */

	/* ── Motion ─────────────────────────────────────────────────────── */
	/* Under 150ms reads as a jolt, over 220ms reads as sluggish. */
	--ease:       cubic-bezier(0.4, 0, 0.2, 1);
	--duration:   180ms;
	--duration-slow: 220ms;
}

/* ===========================================================================
   LAYER 2 - BRAND TOKENS
   Set data-brand on <html>. One component library, several brands.
   ======================================================================== */

/*
 * Values taken from Marzan_BrandGuidelines_2021.pdf, page 7, and cross-checked
 * against the official logo SVG. The v2 design brief derived these by measuring
 * the old Elementor site instead, which had drifted: the accent was #FA8C30
 * where the guidelines specify R255 G140 B48, and --brand-primary-deep was
 * #0B2C5A, a colour that appears nowhere in the guidelines at all. The real
 * second navy is #004874. Both are corrected here.
 */
:root,
[data-brand='batterysafety'],
[data-brand='marzan'],
[data-brand='accukluis'] {
	--brand-primary:      #10395B;   /* C100 M76 Y38 K30 */
	--brand-primary-deep: #004874;   /* C99  M69 Y30 K16 */
	--brand-accent:       #FF8C30;   /* C0   M55 Y82 K0  */
	/*
	 * Orange is not a text colour: #FF8C30 measures 2.3:1 on white. This is the
	 * same hue taken dark enough to carry text, and is the only value in this
	 * block that is not literally in the guidelines.
	 */
	--brand-accent-text:  #A84E00;   /* 5.6:1 on white */
	/*
	 * The hover goes LIGHTER, which is the opposite of the usual instinct.
	 *
	 * The CTA is navy text on orange, so darkening the fill closes the gap on the
	 * label rather than opening it. Measured: the previous #E07A1F hover carried
	 * navy at 3.97:1 and the #D06E15 active at 3.37:1 - both under AA, on the one
	 * control the site exists to get clicked. That was never caught because a
	 * static contrast audit only measures the resting state.
	 *
	 * The resting fill has 5.14:1 to spend. Lightening on hover spends none of it
	 * (5.91:1) and still reads as a state change; the pressed state is the only
	 * one that darkens, and stops at the last value that clears 4.5:1. The lift
	 * and the shadow do most of the work in both cases - see .bs-button--primary.
	 */
	--brand-accent-hover: #FFA050;   /* navy 5.91:1 */
	--brand-accent-active:#F0842D;   /* navy 4.57:1 */
}

/*
 * BatterySafety currently shares the family accent so it reads as part of the
 * Marzan group. To differentiate it instead, uncomment this block: it swaps the
 * accent to the blue already present in the Marzan palette. Nothing else in the
 * system needs to change - that is the point of the two-layer split.
 *
 * [data-brand='batterysafety'] {
 *   --brand-accent:        #1BA2DD;
 *   --brand-accent-text:   #005C94;
 *   --brand-accent-hover:  #1789BC;
 *   --brand-accent-active: #14759F;
 * }
 */

/* ===========================================================================
   BASE
   ======================================================================== */

body {
	font-family: var(--font-body);
	font-size: var(--text-body);
	/*
	 * 400, where Montserrat needed 500. Montserrat is a geometric display face
	 * and sets thin at its regular weight, so the body copy was nudged up a step
	 * to hold the page. Open Sans is a text face and 400 is what it was drawn
	 * for; keeping 500 here would have made every paragraph on the site read
	 * slightly bold. The 500s and 600s elsewhere are relative steps above this
	 * and still work.
	 */
	font-weight: 400;
	line-height: var(--leading-body);
	color: var(--text-body-color);
	background: var(--surface);
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	/*
	 * Full-bleed bands are 100vw, and 100vw includes the scrollbar, so they overhang
	 * the content box by the scrollbar width and the page gains a horizontal scroll.
	 * `clip` rather than `hidden`: hidden makes this a scroll container and would break
	 * the sticky header, clip only clips.
	 */
	overflow-x: clip;
}

h1, h2, h3, h4, h5, h6,
.entry-title {
	font-family: var(--font-heading);
	color: var(--text-heading);
	text-wrap: balance;
}

/* The H1 gets the text measure, not the tighter heading measure: at 48px that
   works out to roughly 24 characters a line, which is the figure the design brief
   targets. The heading measure would cut it to about 19 and make a large heading
   read as cramped rather than large. */
h1, .entry-title {
	font-size: var(--text-display);
	font-weight: 800;
	line-height: var(--leading-display);
	letter-spacing: -0.01em;
	max-width: var(--measure);
	margin-bottom: var(--space-6);
}

h2 {
	font-size: var(--text-h2);
	font-weight: 800;
	line-height: var(--leading-h2);
	max-width: var(--measure-heading);
	margin-top: var(--space-16);
	margin-bottom: var(--space-4);
}

h3 {
	font-size: var(--text-h3);
	font-weight: 700;
	line-height: var(--leading-h3);
	max-width: var(--measure-heading);
	margin-top: var(--space-12);
	margin-bottom: var(--space-3);
}

h4 {
	font-size: var(--text-h4);
	font-weight: 700;
	line-height: var(--leading-h4);
	margin-top: var(--space-8);
	margin-bottom: var(--space-2);
}

/* Display-xl is reserved for a full-width hero with no image beside it.
   Never on the standard split hero - see the design brief. */
.bs-display-xl {
	font-size: var(--text-display-xl);
	line-height: var(--leading-display);
	max-width: none;
}

strong { font-weight: 700; }

/* ── Links: navy and underlined, never orange ───────────────────────────
   Orange is reserved for the primary CTA. When inline links are orange too,
   every link in a paragraph competes with the conversion button and the eye
   cannot find the destination. */
a {
	color: var(--brand-primary);
	text-decoration: underline;
	text-underline-offset: 3px;
	text-decoration-thickness: 1px;
	transition: color 150ms var(--ease), text-decoration-thickness 150ms var(--ease);
}

a:hover {
	color: var(--brand-accent-text);
	text-decoration-thickness: 2px;
}

:focus-visible {
	outline: 2px solid var(--brand-primary);
	outline-offset: 2px;
}

/* On navy, the focus ring and links have to invert or they disappear. */
.bs-inverse :focus-visible,
.bs-trust :focus-visible,
.site-header :focus-visible,
.bs-mobile-cta :focus-visible {
	outline-color: var(--text-on-inverse);
}

.bs-inverse a,
.bs-trust a {
	color: var(--text-on-inverse);
}

.bs-inverse a:hover,
.bs-trust a:hover {
	color: var(--brand-accent);
}

/* ===========================================================================
   SKIP LINK
   ======================================================================== */

.bs-skip-link {
	position: absolute;
	left: -9999px;
	z-index: 999;
	padding: var(--space-3) var(--space-4);
	background: var(--brand-primary);
	color: var(--text-on-inverse);
	border-radius: 0 0 var(--radius-button) 0;
	text-decoration: none;
	font-weight: 600;
}

.bs-skip-link:focus {
	left: 0;
	top: 0;
}

/* ===========================================================================
   PAGE WIDTH

   GeneratePress ships .grid-container at 1200px inside main.min.css, and
   inc/css-output.php only emits a rule of its own when the customiser value
   differs from 1200 - so the width cannot be set from theme code through the
   option, and setting it in the customiser would be a database value with no
   record in the repo. This stylesheet is enqueued after GeneratePress's, so a
   rule at the same specificity here is the code-first way to state it.

   1280px is the figure accukluis.nl uses for its content sections, so the two
   sites in the group now read at one width. The prose measure is deliberately
   not widened with it: 640px is 74 characters, and accukluis sets its own text
   column at roughly the same. What the extra 80px buys is the section blocks -
   bands, card grids, the hero split - which is where the page felt narrow.
   ======================================================================== */

body .grid-container,
body:not(.full-width-content) #page {
	max-width: var(--container-max);
}

/* The header takes the wider one. Its navy ground is full-bleed either way; it
   is only the container the logo and the nav sit in that changes. */
.site-header .inside-header {
	max-width: var(--container-header);
}

/* ===========================================================================
   HEADER - navy, negative logo
   ======================================================================== */

.site-header,
body .site-header {
	position: sticky;
	top: 0;
	z-index: 100;
	background: var(--brand-primary);
	border-bottom: none;
}

/*
 * The admin bar.
 *
 * WordPress fixes it to the top of the viewport from 601px up and shifts the
 * document down with `html { margin-top }`. A sticky header at `top: 0` knows
 * nothing about either: it parks underneath the bar and loses the top of its own
 * height - 32px of an 84px bar, which takes 20px of the logo with it. Measured in
 * the browser with the bar simulated, not guessed at.
 *
 * Only logged-in editors ever see this, which is why it survived unnoticed
 * through three logo sizes: at 44px the loss was the same 32px but the logo was
 * small enough inside the bar that nothing visibly cut.
 *
 * Two figures because WordPress has two: the bar is 46px tall below 783px and
 * 32px from there up. Below 601px it is `position: absolute` and scrolls away
 * with the page, so `top: 0` is correct there and neither query applies.
 */
@media screen and (min-width: 601px) {
	body.admin-bar .site-header { top: 46px; }
}

@media screen and (min-width: 783px) {
	body.admin-bar .site-header { top: 32px; }
}

/* Sticky headers sit above content, so they need one elevation step. */
.site-header.bs-header--scrolled {
	box-shadow: var(--shadow-3);
	backdrop-filter: saturate(140%) blur(6px);
}

.site-header .inside-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	flex-wrap: nowrap;
	/* The page gutter, per the design brief. GeneratePress defaults to 40px, which
	   cost 32px the header did not have to spare. */
	padding: var(--space-3) var(--gutter);
	gap: var(--space-6);
}

/*
 * The other three containers that touch the page edge.
 *
 * GeneratePress pads .inside-article by 30px and .inside-site-info by 20px/30px
 * and neither was ever overridden, so the body copy and the footer sat 6px
 * inside the header and 10px outside a band. .separate-containers is on the
 * selector because that is the specificity GeneratePress uses; a bare
 * .inside-article loses to it. Only the inline padding is taken - the vertical
 * rhythm is GeneratePress's and is fine.
 */
.separate-containers .inside-article,
.site-footer .inside-site-info,
.site-footer .inside-footer-widgets {
	padding-inline: var(--gutter);
}

/* GeneratePress puts 20px either side of every nav link. Across six items that is
   240px of padding, which is what pushed the header past the viewport. 14px keeps a
   comfortable target while giving the row room to fit. */
.site-header .main-nav > ul > li > a {
	padding-left: var(--space-4);
	padding-right: var(--space-4);
}

.main-navigation .main-nav > ul > li:not(.bs-menu-item-cta) > a::after {
	left: var(--space-4);
	right: var(--space-4);
}

/* The mark never shrinks to make room for the navigation. When the two no longer
   fit, the navigation collapses to a toggle - that is what the breakpoint filter in
   functions.php is for. Without this, flexbox squashed a 240px logo to 101px. */
.site-header .site-branding {
	flex: 0 0 auto;
	min-width: 0;
}

/*
 * The nav does not shrink either.
 *
 * min-width:0 here was a bug: it let flexbox compress the nav below its min-content
 * width, so every label containing a space broke onto two lines - "Battery fires" and
 * "Storage & charging" wrapped while "Cabinets" and "News" did not, which is what made
 * the row heights alternate between 60px and 120px. Below 1024px the nav becomes a
 * toggle, so on desktop it never needs to give way.
 */
.site-header .main-navigation {
	flex: 0 0 auto;
	min-width: auto;
}

.site-header .main-nav > ul {
	flex-wrap: nowrap;
}

/* Belt and braces: a nav label is never worth two lines. */
.site-header .main-nav > ul > li > a {
	white-space: nowrap;
}

/*
 * ── The collapsed menu ──────────────────────────────────────────────────
 *
 * Everything above this point is about keeping the desktop header on one row,
 * and all of it was applying at every width. Below the collapse breakpoint
 * GeneratePress moves the navigation onto its own full-width row underneath the
 * bar, which it can only do if the header is allowed to wrap and the nav is
 * allowed to take the whole line. `flex-wrap: nowrap` plus `flex: 0 0 auto`
 * denied it both, so the open menu was laid out as a third column beside the
 * logo and the toggle: it started at x=271 on a 375px screen and ran 223px off
 * the right-hand edge, where `overflow-x: clip` quietly cut it off. Every label
 * was truncated and the toggle sat in the middle of the bar instead of at the
 * end. Below 1025px those two rules are now reversed.
 *
 * :not(.mobile-menu-control-wrapper) matters - GeneratePress gives the toggle's
 * own wrapper the .main-navigation class too, and a full-width toggle wrapper
 * would push the logo onto its own line.
 *
 * The links carry no inline padding here: .inside-header's padding is already
 * the page gutter, so adding the gutter again on each link would indent every
 * label 24px past the logo.
 *
 * 1119px, not 1024px: this has to be the same figure as the media query the
 * mobile toggle uses, which functions.php sets from the header's own arithmetic.
 * Two different figures would leave a band of widths where the toggle is showing
 * and the panel is still styled as a desktop row.
 */
@media (max-width: 1119px) {
	.site-header .inside-header {
		flex-wrap: wrap;
		row-gap: 0;
	}

	.site-header .main-navigation:not(.mobile-menu-control-wrapper) {
		flex: 1 0 100%;
		width: 100%;
		min-width: 0;
		order: 3;
	}

	/* The toggle is the only control left on the top row, so it goes hard right. */
	.site-header .mobile-menu-control-wrapper { margin-left: auto; }

	.site-header .main-navigation .inside-navigation { padding: 0; }

	.site-header .main-nav > ul > li > a {
		padding-left: 0;
		padding-right: 0;
	}

	.main-navigation .main-nav > ul > li:not(.bs-menu-item-cta) > a::after {
		left: 0;
		right: 0;
	}
}

/* Anchored links must clear the sticky header rather than landing underneath it.
   overflow-x: clip is the fallback for full-bleed bands if --scrollbar-width is
   never set; `clip` does not create a scroll container, so sticky still works. */
html {
	scroll-padding-top: 6rem;
	overflow-x: clip;
}

/* Wordmark fallback, used until the logo assets arrive. */
.bs-wordmark {
	display: inline-flex;
	align-items: baseline;
	gap: 0.1em;
	font-family: var(--font-heading);
	font-weight: 800;
	font-size: 1.375rem;
	line-height: 1;
	letter-spacing: -0.02em;
	color: var(--text-on-inverse);
	text-decoration: none;
}

.bs-wordmark:hover { color: var(--text-on-inverse); }

/*
 * Deliberately not orange. The wordmark sits inches from the orange header CTA,
 * and two orange things in the same bar is exactly the dilution the "orange is
 * the CTA only" rule exists to prevent. Weight carries the emphasis instead, and
 * the pale blue is from the Marzan palette.
 */
.bs-wordmark__accent { color: var(--text-on-inverse); }

.bs-wordmark__tld {
	font-size: 0.6em;
	font-weight: 600;
	color: var(--blue-100);
	letter-spacing: 0;
}

/*
 * Explicit height, not max-height.
 *
 * The supplied SVG carries a viewBox but no width/height attributes, so it has an
 * intrinsic ratio and no intrinsic size. With height:auto and only a max-height to
 * constrain it, the browser resolved the used size to 0×0 and the logo vanished.
 * A stated height plus width:auto gives the ratio something to work from.
 */
.bs-logo {
	display: flex;
	align-items: center;
	line-height: 0;   /* no descender gap under an inline-level image */
	/* The mark carries the tap target on mobile, and 44px is the floor for one.
	   It is now larger than that on every breakpoint, so this is a guard rather
	   than a correction. */
	min-height: 60px;
}

.bs-logo img,
.site-header .site-logo img {
	height: 60px;
	width: auto;
	max-width: 100%;
	display: block;
}

/* Below the point where the nav collapses there is more room, but the header bar
   is shorter, so the mark comes down with it. 60px is desktop only: at 60px the
   mark is 240px wide, which on a 390px viewport is 62% of the bar and crowds the
   toggle. 43px holds it at 172px, and it is still over the 44px target minimum. */
@media (max-width: 639px) {
	.bs-logo img { height: 43px; }
}

/* ── Navigation ─────────────────────────────────────────────────────────
   The source site had no explicit font-family here and fell back to the
   system font. */
/* 15px rather than the brief's 16px: at 16px the six labels plus the CTA and a 240px
   logo left almost no headroom in the bar, and a slightly smaller nav reads as more
   composed against the 48px H1. Uses the existing button size token, so it stays
   inside the type scale. */
.main-navigation,
.main-navigation .main-nav > ul > li > a,
.main-navigation .menu-toggle {
	font-family: var(--font-body);
	font-size: var(--text-button);
	font-weight: 500;
	background: transparent;
}

.main-navigation .main-nav > ul > li > a,
.main-navigation .menu-toggle {
	color: var(--text-on-inverse);
	text-decoration: none;
	position: relative;
}

/* The underline is a pseudo-element rather than a border so it can animate
   without shifting the text.
   
   :not(.bs-menu-item-cta) on every one of these four selectors, so the CTA never
   gets the pseudo-element in the first place. Excluding it further down instead
   did not work: `.main-navigation .bs-menu-item-cta > a::after` is specificity
   0,2,2 against this rule's 0,2,3, so `content: none` lost and the underline was
   generated on the button all along - a 2px #FF8C30 bar 12px up from its bottom
   edge, invisible against the resting orange and visible against the darker hover
   orange. Found 2026-09-08. */
.main-navigation .main-nav > ul > li:not(.bs-menu-item-cta) > a::after {
	content: '';
	position: absolute;
	left: var(--space-5);
	right: var(--space-5);
	bottom: 12px;
	height: 2px;
	background: var(--brand-accent);
	transform: scaleX(0);
	transform-origin: left;
	transition: transform var(--duration) var(--ease);
}

/* Hover used to switch to the same orange as "you are here"
   (var(--brand-accent)) - except GeneratePress's own generated rule
   (".main-navigation .main-nav ul li:not([class*='current-menu-']):hover > a",
   specificity 0,4,3, beats this rule's 0,3,3) got there first and turned
   hover text var(--accent) instead - GP's own default, a plain blue
   (#1e73be) - so it went from off-white to a washed-out blue instead of
   getting brighter ("van wit naar blauwig, bijna niet meer leesbaar",
   2026-09-10).
   Fixing the specificity alone would have made hover the same orange as the
   current-page state below, which is its own problem: a visitor could no
   longer tell "I'm hovering this" from "I'm already on this page." So hover
   no longer changes colour at all - only the underline (::after, below)
   answers - and current-page keeps orange as the one colour with that
   meaning. Qualified with .site-header, a real ancestor, to reliably
   outrank GP's rule regardless of source order. */
.site-header .main-navigation .main-nav > ul > li:not(.bs-menu-item-cta) > a:hover {
	color: var(--text-on-inverse);
}

.main-navigation .main-nav > ul > li[class*='current-menu'] > a {
	color: var(--brand-accent);
	background: transparent;
}

.main-navigation .main-nav > ul > li:not(.bs-menu-item-cta) > a:hover::after,
.main-navigation .main-nav > ul > li:not(.bs-menu-item-cta)[class*='current-menu'] > a::after {
	transform: scaleX(1);
}

/* Dropdowns must open on focus as well as hover, or they are unreachable by
   keyboard. GeneratePress handles hover; this adds the focus path. */
.main-navigation ul ul {
	background: var(--brand-primary-deep);
	box-shadow: var(--shadow-3);
	border-radius: 0 0 var(--radius-button) var(--radius-button);
	overflow: hidden;
}

.main-navigation li:focus-within > ul,
.main-navigation li.bs-submenu-open > ul {
	display: block;
	opacity: 1;
	visibility: visible;
	pointer-events: auto;
	left: auto;
	transform: none;
}

/* Qualified with .main-nav too, not just .main-navigation: GeneratePress's own
   generated ".main-navigation .main-nav ul li a" rule sets a dark default
   colour (var(--contrast), meant for a light menu bar) at specificity 0,2,3,
   which beat this rule's 0,1,4 - the submenu items were near-black text on
   the navy dropdown ("niet leesbaar", 2026-09-10). */
.main-navigation .main-nav ul ul li a { color: var(--text-on-inverse); }

/* Same fix and the same reasoning as the top-level hover above: GP's rule
   also matches nested "ul li", so a dropdown item had the identical
   washed-out-blue problem, and switching it to the current-page orange
   instead would have made a hovered row indistinguishable from the current
   page inside the very same dropdown. A row highlight instead - text stays
   put, the row itself lifts - qualified with .site-header for the same
   specificity reason. */
.site-header .main-navigation .main-nav ul ul li a:hover {
	color: var(--text-on-inverse);
	background: rgba(255, 255, 255, 0.08);
}

/* Header CTA - the one orange element up here.
   The <li> loses GeneratePress's link padding so the button controls its own
   box; without this the nav padding and the button padding stack. */
.main-navigation .bs-menu-item-cta {
	display: flex;
	align-items: center;
	margin-left: var(--space-3);
}

.main-navigation .bs-menu-item-cta > a,
.main-navigation .main-nav > ul > li.bs-menu-item-cta > a {
	color: var(--on-accent);
	background: var(--brand-accent);
	font-weight: 700;
	padding: 10px 18px;
	line-height: 1;
}

.main-navigation .bs-menu-item-cta > a:hover {
	color: var(--on-accent);
	background: var(--brand-accent-hover);
}

/* No underline on the CTA - it is a button, not a nav link. Handled by the
   :not(.bs-menu-item-cta) on the rules that generate and animate it, further up.
   A `content: none` override here is what was tried first, and it silently lost
   on specificity; excluding the element is the only version that cannot. */

/*
 * In the collapsed menu the CTA becomes a full-width block. At its desktop padding
 * it measured 38px tall, under the 44px minimum touch target, and a button squeezed
 * against the left edge of a dropdown does not read as the primary action.
 *
 * It was not actually full-width: .bs-menu-item-cta is a flex container, so the
 * anchor inside it sized to its own text (155px of a 327px row) and the 20px side
 * margins then pushed the item 24px past the right edge of the menu. The list is
 * already inset by the header's gutter, so the margins go and the anchor is told
 * to fill the row.
 *
 * 1119px for the same reason as the block above: it must match the toggle's own
 * media query.
 */
@media (max-width: 1119px) {
	.main-navigation .bs-menu-item-cta {
		display: block;
		margin: var(--space-3) 0 var(--space-4);
	}

	.main-navigation .bs-menu-item-cta > a,
	.main-navigation .main-nav > ul > li.bs-menu-item-cta > a {
		display: flex;
		width: 100%;
		align-items: center;
		justify-content: center;
		min-height: 48px;
		text-align: center;
	}
}

/* ===========================================================================
   BREADCRUMBS
   ======================================================================== */

/* Aligned with the text column, not with the container edge. GeneratePress emits the
   breadcrumb outside .entry-content, so it does not inherit the measure. */
.bs-breadcrumb {
	max-width: var(--measure);
	margin-inline: auto;
	margin-bottom: var(--space-6);
	font-size: var(--text-small);
	color: var(--text-muted);
}

.bs-breadcrumb ol {
	list-style: none;
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2);
	margin: 0;
	padding: 0;
}

.bs-breadcrumb a {
	color: var(--text-muted);
	text-decoration: none;
}

.bs-breadcrumb a:hover {
	color: var(--brand-accent-text);
	text-decoration: underline;
}

.bs-breadcrumb li + li::before {
	content: '›';
	margin-right: var(--space-2);
	color: var(--blue-400);
}

/* See "One left edge" under ARTICLE MEASURE for the left-aligned variant. */

/* ===========================================================================
   ARTICLE MEASURE
   GeneratePress nests content as .bs-article > .inside-article > .entry-content
   and does not apply theme.json's contentSize to that container, so the measure
   is set here on the children of .entry-content. Do not move it up to
   .bs-article or .inside-article: those also wrap the tables, which are wider.

   Long-form keeps 18px/1.7. The size and leading were originally compensation
   for Montserrat, a display face pressed into body duty. Open Sans does not need
   the compensation, but the pages are long regulatory reference text read on
   screen, and 18px/1.7 is a good column for that on its own merits - so the
   values stay and the reason for them changes.
   ======================================================================== */

.bs-article .entry-content {
	font-size: var(--text-article);
	line-height: var(--leading-article);
}

.bs-article .entry-content > *,
.bs-article .entry-title,
.bs-article .bs-byline {
	max-width: var(--measure);
	margin-inline: auto;
}

/* A post's title runs the full width of the container instead of stopping at
   the reading measure: it sits above two columns, so the measure was capping
   it against nothing ("de titel van de blogberichten over de gehele
   breedte", 2026-09-10). The measure still governs the article body. */
.single-post .bs-article .entry-title { max-width: none; }

/*
 * Headings are capped shorter than the body, but their left edge must stay on the
 * body column. `margin-inline: auto 0` did the opposite - it put the free space on
 * the left and flushed every heading to the right-hand edge of the column.
 *
 * The body column is centred inside .entry-content, so its left offset is
 * (100% - measure) / 2. Reproducing that as an explicit left margin keeps the
 * heading aligned with the text while the shorter cap takes effect on the right.
 * max() guards the narrow case, where the column is already the full width.
 */
.bs-article .entry-content > h2,
.bs-article .entry-content > h3 {
	max-width: var(--measure-heading);
	margin-left: max(0px, calc((100% - var(--measure)) / 2));
	margin-right: auto;
}

/* ── One left edge ───────────────────────────────────────────────────────
   A centred 640px column reads well on a page that is prose from top to
   bottom. On a page built from section blocks it does not: the title, the
   breadcrumb and the intro sit 300px in while the splits, card grids and bands
   below them start at the container edge, and the top of the page reads as
   indented. Reported by Martijn on 2026-09-10, looking at accukluis.

   `content_align: 'left'` in the site config puts one left edge down the whole
   page - breadcrumb, title, byline, prose, wide blocks - and keeps the 640px
   reading measure. The default stays centred, so an existing site does not
   move unless it asks to; batterysafety.co.uk is unchanged. A site with mostly
   composed pages wants left, a site of long articles wants centre. */
.bs-align-left .bs-article .entry-content > *,
.bs-align-left .bs-article .entry-title,
.bs-align-left .bs-article .bs-byline,
.bs-align-left .bs-breadcrumb {
	margin-inline: 0 auto;
}

.bs-align-left .bs-article .entry-content > h2,
.bs-align-left .bs-article .entry-content > h3 {
	margin-left: 0;
}

/* The blocks that break out of the measure are centred on the container by
   `margin-left: 50%; transform: translateX(-50%)`, which the rule above must
   not touch - and does not, since it only sets the inline margins that the
   breakout rule overrides. Full-bleed bands keep their own inner container. */

/* ── Breaking out of the measure ─────────────────────────────────────────
   Long-form prose belongs in the 696px column. Section compositions - two-column
   text-and-image blocks, card grids, navy bands - do not: squeezed into the
   measure they stop being compositions. Three widths are available:

     (default)    640px   prose
     .alignwide  1120px   section blocks, tables, card grids
     .alignfull   100vw   full-bleed bands, which set their own inner container

   Tables and the trust bar get alignwide behaviour without needing the class,
   because a standards comparison cramped to the measure is unreadable and that
   table is meant to be citable. */
.bs-article .entry-content > .alignwide,
.bs-article .entry-content > .wp-block-table,
.bs-article .entry-content > .bs-table-wrap,
.bs-article .entry-content > .bs-trust,
.bs-article .entry-content > .bs-split,
.bs-article .entry-content > .bs-cards,
.bs-article .entry-content > .bs-icon-grid,
.bs-article .entry-content > .bs-gallery,
.bs-article .entry-content > .bs-logos,
.bs-article .entry-content > .bs-news-feed,
.bs-article .entry-content > .bs-post-grid,
.bs-article .entry-content > .bs-form-split {
	width: min(var(--wide), calc(100vw - 2 * var(--gutter)));
	max-width: none;
	margin-left: 50%;
	transform: translateX(-50%);
}

/* Full-bleed: escape .inside-article and the grid container's padding entirely.
   100vw rather than 100% because every ancestor is narrower than the viewport.

   --scrollbar-width is measured in theme.js, because 100vw includes the scrollbar
   while the content box does not: without subtracting it the band overhangs by
   ~15px and the page gains a horizontal scroll. It falls back to 0px if JavaScript
   never runs, and the overflow-x: clip below catches that case. */
.bs-article .entry-content > .alignfull {
	width: calc(100vw - var(--scrollbar-width, 0px));
	max-width: none;
	margin-left: 50%;
	transform: translateX(-50%);
}

@media (max-width: 1023px) {
	/* Below the breakout threshold there is nothing to break out of. */
	.bs-article .entry-content > .alignwide,
	.bs-article .entry-content > .wp-block-table,
	.bs-article .entry-content > .bs-table-wrap,
	.bs-article .entry-content > .bs-trust,
	.bs-article .entry-content > .bs-split,
	.bs-article .entry-content > .bs-cards,
	.bs-article .entry-content > .bs-icon-grid,
	.bs-article .entry-content > .bs-gallery,
	.bs-article .entry-content > .bs-logos,
	.bs-article .entry-content > .bs-news-feed,
	.bs-article .entry-content > .bs-post-grid,
	.bs-article .entry-content > .bs-form-split {
		width: 100%;
		margin-left: 0;
		transform: none;
	}
}

.bs-article .entry-content > p,
.bs-article .entry-content > ul,
.bs-article .entry-content > ol {
	margin-bottom: var(--space-6);
}

/*
 * Room for the marker. Written as a descendant rule, not `entry-content > ul`
 * as it was: a list inside the post layout, a band or a text column is no
 * longer a direct child of .entry-content, so the padding silently stopped
 * applying and the bullets hung in the gutter to the left of the text
 * ("die bullets staan voor het tekstblok", 2026-09-10).
 *
 * :not([class*="bs-"]) leaves every list that is a component in its own right
 * - .bs-checks and its plus/minus variants draw their own marker from a
 * pseudo-element and set padding to match it.
 */
.bs-article ul.wp-block-list:not([class*="bs-"]),
.bs-article ol.wp-block-list:not([class*="bs-"]) {
	padding-left: 1.4em;
}

.bs-article .entry-content li + li { margin-top: var(--space-2); }

/* ===========================================================================
   SECTION RHYTHM
   white → muted → white → navy → white. The background alternation is the
   layout mechanism; it gives rhythm without decoration.
   ======================================================================== */

.bs-section {
	padding-block: var(--section-y);
}

.bs-section--muted {
	background: var(--surface-muted);
}

.bs-section--inverse,
.bs-inverse {
	background: var(--brand-primary);
	color: var(--text-on-inverse);
}

.bs-section--inverse h1,
.bs-section--inverse h2,
.bs-section--inverse h3,
.bs-section--inverse h4,
.bs-inverse h1, .bs-inverse h2, .bs-inverse h3, .bs-inverse h4 {
	color: var(--text-on-inverse);
}

/* ===========================================================================
   DIRECT ANSWER BOX - the first thing on every page
   ======================================================================== */

/*
 * ── The blue range has a job ─────────────────────────────────────────────
 *
 * The guidelines' five blues were all defined and then almost none of them were
 * used; the site was navy, orange and grey. Rather than sprinkle blue around for
 * variety, it carries one meaning throughout:
 *
 *     ORANGE  the action                  - the primary CTA, and nothing else
 *     NAVY    the content                 - headings, body, table headers
 *     BLUE    orientation and meta        - "In short", step counters, related
 *                                           links, breadcrumb separators,
 *                                           glossary terms, disclosure markers
 *
 * That is why the step counter below moves off --brand-accent-text: a progress
 * indicator is wayfinding, not an action, and it was the last orange thing on
 * the site that was not a CTA.
 *
 * --blue-700 wherever the colour carries text (7.1:1 on white). --blue-400 only
 * on rules, markers and fills, where 1.4.11 does not apply - it is 2.89:1 and
 * cannot carry text. See the token block.
 */
.bs-answer {
	background: var(--surface-muted);
	border-left: 4px solid var(--blue-400);
	padding: var(--space-6);
	border-radius: 0 var(--radius-card) var(--radius-card) 0;
	margin-bottom: var(--space-8);
}

.bs-answer__label {
	font-family: var(--font-heading);
	font-size: var(--text-small);
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--blue-700);
	margin-bottom: var(--space-2);
}

.bs-answer p:last-child {
	margin-bottom: 0;
	font-size: var(--text-body-lg);
	line-height: var(--leading-body-lg);
}

/* ===========================================================================
   SOURCED STATISTIC CALLOUT
   ======================================================================== */

.bs-stat {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: var(--space-2) var(--space-6);
	align-items: baseline;
	padding: var(--space-6);
	border: 1px solid var(--border-subtle);
	border-radius: var(--radius-card);
	background: var(--surface);
	box-shadow: var(--shadow-2);
	margin-block: var(--space-8);
}

/*
 * ORANGE, AND A DELIBERATE AA FAILURE - decided by the site owner, 6 Sep 2026.
 *
 * #FF8C30 on the white stat panel measures 2.32:1. At 36-52px/800 this counts as
 * large text, which is allowed 3:1, so it misses by a clear margin - this is the
 * one place on the site that does not meet WCAG AA, and the "zero failures"
 * claim in design-system.md no longer holds without this footnote.
 *
 * Two things keep it from being an information failure rather than a contrast
 * one: the figure never carries meaning alone - .bs-stat__text states the fact
 * in a full sentence at 6.6:1 and .bs-stat__source names the source at 5:1 - and
 * the numeral is repeated in that sentence. Nothing is only available in orange.
 *
 * It also breaks the orange-is-CTA-only rule the rest of the system enforces.
 * That rule is not arbitrary: the v2 brief's largest finding was that orange on
 * both the button and every inline link left nothing on the page readable as the
 * destination. Statistics are rare (two, reused eight times) and never sit
 * beside the CTA block, so the collision is limited - but it is real.
 *
 * If this should ever pass while staying this exact orange, the fix is the
 * surface, not the colour: #FF8C30 on --brand-primary measures 5.14:1. Giving
 * .bs-stat a navy ground would keep the figure bright and clear AA outright.
 */
.bs-stat__figure {
	grid-row: span 2;
	font-family: var(--font-heading);
	font-size: clamp(2.25rem, 1.8rem + 2vw, 3.25rem);
	font-weight: 800;
	line-height: 1;
	color: var(--brand-accent);
	margin: 0;
	font-variant-numeric: tabular-nums;
}

.bs-stat__text {
	margin: 0;
	font-size: var(--text-body-lg);
	line-height: var(--leading-body-lg);
}

.bs-stat__source {
	margin: 0;
	font-size: var(--text-small);
	line-height: var(--leading-body);
	color: var(--text-muted);
}

.bs-stat__source a { color: var(--text-muted); }
.bs-stat__source a:hover { color: var(--brand-accent-text); }

@media (max-width: 639px) {
	.bs-stat { grid-template-columns: 1fr; }
	.bs-stat__figure { grid-row: auto; }
}

/* ===========================================================================
   TABLES
   ======================================================================== */

.bs-table-wrap,
.wp-block-table {
	overflow-x: auto;                 /* wide tables scroll inside themselves */
	margin-block: var(--space-12);
	border-radius: var(--radius-card);
}

.bs-spec-table,
.wp-block-table table {
	width: 100%;
	border-collapse: collapse;
	font-size: var(--text-small);
	line-height: var(--leading-body);
	background: var(--surface);
}

.bs-spec-table caption,
.wp-block-table caption {
	text-align: left;
	font-size: var(--text-small);
	color: var(--text-muted);
	padding-bottom: var(--space-2);
}

.wp-block-table figcaption,
.bs-table-wrap figcaption {
	font-size: var(--text-small);
	color: var(--text-muted);
	margin-top: var(--space-3);
}

.bs-spec-table th,
.bs-spec-table td,
.wp-block-table th,
.wp-block-table td {
	border: 1px solid var(--border-subtle);
	padding: var(--space-3) var(--space-4);
	text-align: left;
	vertical-align: top;
}

.bs-spec-table th[scope='row'],
.wp-block-table th[scope='row'] {
	background: var(--surface-muted);
	width: 30%;
	font-weight: 600;
	color: var(--text-heading);
}

.wp-block-table thead th {
	background: var(--brand-primary);
	color: var(--text-on-inverse);
	font-family: var(--font-heading);
	font-weight: 700;
	border-color: var(--brand-primary);
}

/*
 * ── Wide tables on a phone ──────────────────────────────────────────────
 *
 * The container already scrolls, and for the ten-column model comparison that
 * is the only honest answer: 956px of specification does not fit in 327px and
 * no amount of reflowing makes it. What was missing is everything around the
 * scroll. Three changes:
 *
 *   1. Cell padding drops from 12/16px to 8/12px. On a four-column table that
 *      is 128px of padding recovered out of 327 - enough that the smaller
 *      tables stop needing to scroll at all.
 *   2. The row header sheds its 30% width, which was forcing the first column
 *      wide on tables that had no room to give it.
 *   3. The first column is sticky, so the model number or the row label stays
 *      on screen while the rest scrolls under it. Without it the reader loses
 *      track of which row they are reading two columns in.
 *
 * theme.js adds .is-scrollable to any container that actually overflows, and
 * the hint below is the affordance - a scrollbar on iOS is invisible until you
 * are already scrolling, which is too late to tell you there is more.
 */
@media (max-width: 1023px) {
	.bs-spec-table th,
	.bs-spec-table td,
	.wp-block-table th,
	.wp-block-table td {
		padding: var(--space-2) var(--space-3);
	}

	.bs-spec-table th[scope='row'],
	.wp-block-table th[scope='row'] {
		width: auto;
	}

	.bs-spec-table th:first-child,
	.bs-spec-table td:first-child,
	.wp-block-table th:first-child,
	.wp-block-table td:first-child {
		position: sticky;
		left: 0;
		z-index: 1;
		background: var(--surface);
		box-shadow: 1px 0 0 var(--border-subtle);
	}

	/* Sticky cells lose the backgrounds they inherit from the row, so both
	   header variants are restated here. */
	.wp-block-table thead th:first-child { background: var(--brand-primary); }

	.bs-spec-table th[scope='row'],
	.wp-block-table th[scope='row'] { background: var(--surface-muted); }
}

.bs-scroll-hint {
	font-size: var(--text-small);
	color: var(--text-muted);
	margin: 0 0 var(--space-2);
}

.bs-scroll-hint::before { content: '↔ '; }

/* ===========================================================================
   FAQ ACCORDION - core Details blocks
   ======================================================================== */

.wp-block-details {
	border: 1px solid var(--border-subtle);
	border-radius: var(--radius-button);
	/* The vertical padding used to be var(--space-4). It moved onto the summary
	   itself as a min-height, so the whole row is the target rather than the
	   23px of text in the middle of it; the box ends up ~4px taller than before. */
	padding: var(--space-2) var(--space-6);
	margin-bottom: var(--space-3);
	background: var(--surface);
	transition: border-color var(--duration) var(--ease), box-shadow var(--duration) var(--ease);
}

.wp-block-details:hover {
	border-color: var(--border-strong);
}

.wp-block-details[open] {
	border-color: var(--brand-primary);
	box-shadow: var(--shadow-1);
}

/*
 * An FAQ toggle is not inline text, so WCAG 2.2's target-size minimum applies to
 * it. At 18px/1.3 the summary box measured 23.4px high - it failed the 24px
 * minimum by six tenths of a pixel, and on a phone it simply felt like a
 * mis-hit. min-height brings it to 44px.
 *
 * display: flex removes the browser's own disclosure triangle in both Blink and
 * WebKit, so the chevron is drawn here instead and rotates on open. list-style
 * and the two marker pseudo-elements are belt and braces for the engines that
 * keep drawing it anyway.
 */
.wp-block-details summary {
	font-family: var(--font-heading);
	font-size: var(--text-h4);
	font-weight: 700;
	line-height: var(--leading-h4);
	color: var(--brand-primary);
	cursor: pointer;
	display: flex;
	align-items: center;
	gap: var(--space-2);
	min-height: 44px;
	list-style: none;
}

.wp-block-details summary::marker { content: ''; }
.wp-block-details summary::-webkit-details-marker { display: none; }

.wp-block-details summary::before {
	content: '';
	flex: 0 0 auto;
	width: 0.5em;
	height: 0.5em;
	border-right: 2px solid var(--blue-400);
	border-bottom: 2px solid var(--blue-400);
	transform: rotate(-45deg);
	transition: transform var(--duration) var(--ease);
}

.wp-block-details[open] summary::before { transform: rotate(45deg); }

.wp-block-details[open] summary { margin-bottom: var(--space-3); }
.wp-block-details p:last-child { margin-bottom: 0; }

/* ===========================================================================
   TRUST BAR
   ======================================================================== */

.bs-trust {
	background: var(--brand-primary);
	color: var(--text-on-inverse);
	padding: var(--space-8);
	border-radius: var(--radius-card);
	gap: var(--space-6);
	margin-block: var(--space-16);
}

.bs-trust__item {
	flex: 1 1 240px;
	margin: 0;
	font-size: var(--text-body);
	line-height: var(--leading-body);
	color: var(--text-on-inverse);
}

.bs-trust__item strong {
	display: block;
	font-family: var(--font-heading);
	font-weight: 700;
	margin-bottom: var(--space-1);
}

/*
 * Light variant. Navy is the strongest emphasis in the system and the page rhythm
 * uses it once; a navy trust bar directly below the navy band read as one
 * undifferentiated block with a white gap through it.
 */
.bs-trust--light {
	background: var(--surface);
	border: 1px solid var(--border-subtle);
	color: var(--text-body-color);
	box-shadow: var(--shadow-1);
}

.bs-trust--light .bs-trust__item { color: var(--text-body-color); }
.bs-trust--light .bs-trust__item strong { color: var(--brand-primary); }
.bs-trust--light a { color: var(--brand-primary); }
.bs-trust--light a:hover { color: var(--brand-accent-text); }
.bs-trust--light :focus-visible { outline-color: var(--brand-primary); }

/* ===========================================================================
   SECTION COMPOSITIONS
   The page rhythm from the design brief: two-column text-and-image blocks with
   the image alternating sides, card grids, and full-bleed navy bands. The
   background alternation (white → muted → white → navy → white) is the layout
   mechanism - it gives rhythm without decoration.
   ======================================================================== */

/* ── Split: text one side, media the other ───────────────────────────────
   .bs-split--flip puts the media on the left. On mobile both collapse to a
   single column with the text first, so the reading order never depends on
   which side the image happens to be on.

   60/40, text over media, on every split. This was 50/50 with the hero as the
   one 60/40 exception; on 2026-09-09 the exception became the rule, because the
   copy is the argument on every one of these blocks, not only the first, and a
   page whose splits change ratio as you scroll reads as inconsistent. 3fr 2fr
   of the 1168px left after the 64px gap puts the text column at 701px and the
   media at 467px. --flip only swaps the order, so it becomes 40/60 for free. */
.bs-split {
	display: grid;
	grid-template-columns: 3fr 2fr;
	gap: var(--space-16);
	align-items: center;
	margin-block: var(--space-20);
}

.bs-split__text > *:first-child { margin-top: 0; }
.bs-split__text > *:last-child { margin-bottom: 0; }

/* A button at the foot of a text column gets its own air: at paragraph spacing
   it read as the next line of the paragraph rather than as the thing to do
   next ("meer witruimte tussen Over ons en de knop", 2026-09-10). */
.bs-split__text > p:last-child:has(> .bs-button),
.bs-form-split__text > p:last-child:has(> .bs-button),
.bs-band__inner > p:last-child:has(> .bs-button) { margin-top: var(--space-8); }

/* Inside a column the measure is the column, so the cap has to lift or the text
   would sit in a 696px box inside a 520px column and overflow it. */
.bs-split__text > *,
.bs-split__text h2,
.bs-split__text h3 {
	max-width: none;
	margin-inline: 0;
}

/*
 * Direct child (`>`), not descendant: a media column can now carry a small
 * badge above the picture (see .bs-cert below), and a plain descendant
 * selector would reach into it and force its own tiny logo into a photo
 * frame - 4:3, cropped, cover-fit. Every existing page already had the image
 * as a direct child, so this changes nothing for them.
 */
.bs-split__media > img,
.bs-split__media > video {
	width: 100%;
	height: auto;
	border-radius: var(--radius-card);
	display: block;
}

/* Fixed ratio so a row of these never looks ragged. */
.bs-split__media > figure,
.bs-split__media > .wp-block-image {
	margin: 0;
	aspect-ratio: 4 / 3;
	overflow: hidden;
	border-radius: var(--radius-card);
}

.bs-split__media > figure img,
.bs-split__media > .wp-block-image img {
	height: 100%;
	object-fit: cover;
}

/* The badge sits flush above the picture, not floating with a block's usual
   top margin - it is announcing the photo beneath it, not a paragraph of its
   own. WordPress's default block gap still gives the picture its own space
   below the badge. */
.bs-split__media > .bs-cert:first-child { margin-top: 0; }

/* A split whose media column carries a badge above the photo wants that badge
   level with the top of the heading beside it; .bs-split's usual
   align-items: center instead centres the whole (taller) badge+photo group,
   which left the badge sitting below the title
   ("verticaal uitlijnen met ECB.S gecertificeerd", 2026-09-10). Every other
   split - a single photo - keeps the centred alignment. */
.bs-split:has(.bs-split__media > .bs-cert) { align-items: start; }

/* Same problem, opt-in: a split whose text column is simply longer than one
   photo is tall - several paragraphs and a list, not the two or three short
   ones the centred default assumes. Centring left the photo starting well
   below the heading it was meant to sit beside, which read as crooked rather
   than as "a long paragraph with a photo" (Fiscaal voordeel, "een beetje gek
   en scheef, krom uitgelijnd", 2026-09-10). */
.bs-split--top { align-items: start; }

/* Picture left and narrow, text right and wide. --flip alone mirrors the
   order but leaves the wide column on the left, so the picture inherits the
   60% - fine where the picture is the point, wrong where a paragraph is and
   the picture is only keeping it company ("foto links op 40 en tekst rechts
   op 60", 2026-09-10). Stated before the single-column query below, so a
   phone still collapses it.

   No page uses it since 2026-09-11, when accukluis put the text left and the
   picture right on every split ("overal omwisselen, of consistent maken") and
   --flip went with it. Kept because it is the only way to put a picture on
   the left without giving it the wide column; a site that wants that asks for
   the class rather than inventing one. */
@media (min-width: 1024px) {
	.bs-split--media-left { grid-template-columns: 2fr 3fr; }
	.bs-split--media-left .bs-split__text { order: 2; }
	.bs-split--media-left .bs-split__media { order: 1; }
}

@media (max-width: 1023px) {
	.bs-split {
		grid-template-columns: 1fr;
		gap: var(--space-8);
		margin-block: var(--space-12);
	}

	/* Text first regardless of source order. */
	.bs-split__text { order: 1; }
	.bs-split__media { order: 2; }
}

@media (min-width: 1024px) {
	.bs-split--flip .bs-split__text { order: 2; }
	.bs-split--flip .bs-split__media { order: 1; }
}

/* ── Heading left, body right ────────────────────────────────────────────
   The accukluis pattern where a large heading occupies its own column beside the
   body copy. Gives a long heading room to breathe without stretching the text.

   The two columns are stated inside a min-width query rather than on the base
   rule. Unqualified, `grid-template-columns: 5fr 6fr` sits *after* the
   max-width: 1023px block above and carries the same specificity, so it won it:
   this was the one split that never collapsed, and on a 375px phone it laid its
   heading and its body out as two 157px columns. Every other split stacked
   around it, which is what made the mobile page look like it had lost its
   alignment mid-scroll. */
.bs-split--heading {
	grid-template-columns: 1fr;
	align-items: start;
}

@media (min-width: 1024px) {
	.bs-split--heading { grid-template-columns: 5fr 6fr; }
}

.bs-split--heading .bs-split__text h1,
.bs-split--heading .bs-split__text h2 {
	margin-top: 0;
	max-width: 18ch;   /* headings in their own column want a tighter cap */
}

/* ── Full-bleed band ─────────────────────────────────────────────────────
   Sets its own inner container, because the block itself is 100vw wide. */
.bs-band {
	padding-block: var(--section-y);
	margin-block: var(--space-20);
}

.bs-band__inner {
	max-width: var(--wide);
	margin-inline: auto;
	padding-inline: var(--gutter);
}

.bs-band--muted { background: var(--surface-muted); }

.bs-band--navy {
	background: var(--brand-primary);
	color: var(--text-on-inverse);
	/* The Lock below bleeds past the right edge; clip rather than hidden so this
	   never becomes a scroll container. */
	position: relative;
	overflow: clip;
}

//*
 * ── "The Lock" ───────────────────────────────────────────────────────────
 *
 * Section 04 of the brand guidelines asks for the emblem to be used "in a way
 * where only a part of the symbol is shown enlarged". It was used twice - a
 * ring behind the hero photograph, and a watermark bleeding out of the right
 * edge of the navy bands at 0.14 opacity.
 *
 * Both were removed on 2026-09-07 at Martijn's request. The device is not used
 * on the site. `assets/lock.svg` is kept - it is the emblem group lifted out of
 * MarzanSecurity_logo_RGB.svg, eight paths and no wordmark, which is a nuisance
 * to re-extract - but nothing requests it, so it costs no bandwidth. See the
 * "The Lock" section of docs/design-system.md for the placement measurements if
 * it is ever brought back.
 */
.bs-band--navy h2,
.bs-band--navy h3,
.bs-band--navy h4 { color: var(--text-on-inverse); }
.bs-band--navy .bs-split__text > * { color: var(--text-on-inverse); }

.bs-band .bs-split { margin-block: 0; }

/* ── Card grid ─────────────────────────────────────────────────────────────
   minmax(min(280px, 100%), 1fr) rather than minmax(280px, 1fr). A bare pixel
   floor is a floor even when the container is narrower than it: inside the
   24px gutter a 320px phone leaves 272px, and the track stayed 280px and hung
   8px over the edge. min() lets the floor give way once it no longer fits, and
   changes nothing at any width where it does. The same guard is on every other
   auto-fit grid in this file. */
.bs-cards {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
	gap: var(--card-gap);
	margin-block: var(--space-16);
}

.bs-cards > * { margin: 0; }

/* A card built from a Group block. */
.bs-cards .wp-block-group {
	background: var(--surface);
	border: 1px solid var(--border-subtle);
	border-radius: var(--radius-card);
	padding: var(--space-6);
	box-shadow: var(--shadow-card);
	transition: box-shadow var(--duration) var(--ease), transform var(--duration) var(--ease);
}

/*
 * Hover: the card lifts, and its heading takes the accent. The tile is the
 * link, so the whole card has to say so - a "Bekijk project" line underneath
 * repeated it and gave the keyboard a second stop, and was removed on
 * 2026-09-10. Subtle on purpose: a lift, a shadow, a blue edge and the title
 * in orange, no colour wash.
 */
.bs-cards .wp-block-group:hover {
	box-shadow: var(--shadow-card-hover);
	transform: translateY(-2px);
	border-color: var(--blue-400);
}

.bs-cards h3 > a {
	text-decoration: none;
	color: var(--brand-primary);
}

.bs-cards .wp-block-group:hover h3 > a { color: var(--brand-accent-text); }

/* The heading is the card's link, and it covers the card. */
.bs-cards h3 > a::before {
	content: '';
	position: absolute;
	inset: 0;
	border-radius: var(--radius-card);
}

.bs-cards .wp-block-group > *:first-child { margin-top: 0; }
.bs-cards .wp-block-group > *:last-child { margin-bottom: 0; }

.bs-cards h3 {
	max-width: none;
	margin-top: 0;
	font-size: var(--text-h3);
}

.bs-cards img {
	border-radius: var(--radius-card);
	width: 100%;
	height: auto;
}

/* A card whose picture is an Image block. The photographs the cards carry come
   in every proportion - a 4:3 project photo beside a 3:2 one beside a portrait
   product shot - and a row of them lined up on the top edge and ended anywhere.
   The frame is fixed at 4:3 and the picture fills it, so the row is a row. */
.bs-cards .wp-block-group > .wp-block-image {
	margin: 0 0 var(--space-4);
	aspect-ratio: 4 / 3;
	overflow: hidden;
	border-radius: var(--radius-card);
}

.bs-cards .wp-block-group > .wp-block-image img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* Product shots are the exception: a cabinet cropped to 4:3 loses its top or its
   base. They sit whole inside the frame, with the warning lamp and the plinth
   both in view.

   On white, not on the muted surface: a grey panel behind every cut-out turned
   the overview into a row of boxes inside boxes ("achtergrondkleur is niet
   handig", 2026-09-10). The cabinets are dark and the card is white, so the
   picture already has all the separation it needs. */
.bs-cards--products .wp-block-group > .wp-block-image {
	background: transparent;
	/* The cabinet fills the frame: it is the product, and at 4:3 with padding
	   it was a small object in a large box ("blaas ze op", 2026-09-10). The
	   frame is taller than wide because the cabinets are. */
	aspect-ratio: 4 / 5;
	padding: 0;
	box-sizing: border-box;
}

.bs-cards--products .wp-block-group > .wp-block-image img {
	object-fit: contain;
	transform: scale(1.14);   /* the source files carry generous white margins */
}

/* Title and button centred over the picture; the specification list stays
   left, where a list belongs. */
.bs-cards--products .wp-block-group > h3,
.bs-cards--products .wp-block-group > p:last-child { text-align: center; }

.bs-cards--products .wp-block-group > p:last-child > .bs-button { min-width: 60%; }

/* The card is a column, so a button at the foot of a short card lines up with
   the button at the foot of a long one. */
.bs-cards .wp-block-group {
	display: flex;
	flex-direction: column;
	position: relative;
}

.bs-cards .wp-block-group > p:last-child { margin-top: auto; }

/* One target per card. When the last thing in a card is a button, the whole
   card becomes that button's hit area - the heading is plain text, so keyboard
   users get one stop, not two. The button's own ::after is taken (the tertiary
   arrow), so the stretch uses ::before. */
.bs-cards .wp-block-group > p:last-child > a.bs-button::before {
	content: '';
	position: absolute;
	inset: 0;
	border-radius: var(--radius-card);
}

/* Option tiles: a picture and a name, the name the link. */
.bs-cards--tiles .wp-block-group > p:last-child {
	font-weight: 600;
	font-size: var(--text-body);
	margin-top: auto;
}

.bs-cards--tiles .wp-block-group > p:last-child > a {
	text-decoration: none;
	color: var(--brand-primary);
}

.bs-cards--tiles .wp-block-group > p:last-child > a::before {
	content: '';
	position: absolute;
	inset: 0;
	border-radius: var(--radius-card);
}

.bs-cards--tiles .wp-block-group:hover > p:last-child > a { color: var(--brand-accent-text); }

/* A card that is a call, not a product: the muted fill says "this one is
   different" without introducing a second colour. */
.bs-cards .bs-card--cta {
	background: var(--surface-muted);
	border-color: transparent;
	justify-content: center;
}

/* A product shot beside its text: whole, not cropped. The split's frame is
   4:3 with the picture filling it; a cabinet needs the frame to fit the
   picture instead, on the muted surface so the cut-out has an edge. Direct
   child, for the same reason as the base rule above: a cert badge sharing
   this column must not be squared off and given a grey backdrop too. */
.bs-split__media--contain > figure,
.bs-split__media--contain > .wp-block-image {
	aspect-ratio: 1;
	background: var(--surface-muted);
}

.bs-split__media--contain figure img,
.bs-split__media--contain .wp-block-image img {
	object-fit: contain;
	padding: var(--space-6);
	box-sizing: border-box;
}

/* A row of exactly two, three or four. auto-fit stretches the last card of a
   short final row to the full width; for a set whose count is known, the count
   is stated and the final row keeps its columns. */
@media (min-width: 1024px) {
	.bs-cards--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.bs-cards--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
	.bs-cards--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
	.bs-cards--5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}

/* ── Icon grid ───────────────────────────────────────────────────────────
   A row of sectors or options: an icon over a label, every tile a link. The
   tiles are small, so the grid packs as many as fit and leaves the last row
   left-aligned rather than stretched.

   220px tracks and a 132px icon. First set at 150px (legend, not a way in),
   then at 96px to match accukluis.nl's tile size - which on this theme's
   plainer line icons read as too small ("een tikkie een slagje groter",
   2026-09-10). Bigger again, deliberately short of the live site's icons,
   which fill most of the card and read as illustration rather than a mark. */
.bs-icon-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr));
	gap: var(--space-5);
	margin-block: var(--space-12);
}

.bs-icon-grid > * { margin: 0; }

.bs-icon-grid__item {
	position: relative;
	background: var(--surface);
	border: 1px solid var(--border-subtle);
	border-radius: var(--radius-card);
	padding: var(--space-5) var(--space-4);
	text-align: center;
	box-shadow: var(--shadow-1);
	transition: box-shadow var(--duration) var(--ease), transform var(--duration) var(--ease), border-color var(--duration) var(--ease);
}

.bs-icon-grid__item:hover {
	box-shadow: var(--shadow-2);
	transform: translateY(-2px);
	border-color: var(--border-control);
}

.bs-icon-grid__item .wp-block-image {
	width: 132px;
	height: 132px;
	margin: 0 auto var(--space-4);
}

.bs-icon-grid__item img {
	width: 100%;
	height: 100%;
	object-fit: contain;
	display: block;
}

.bs-icon-grid__item p {
	margin: 0;
	font-size: var(--text-body);
	font-weight: 600;
	line-height: var(--leading-body);
	max-width: none;
}

.bs-icon-grid__item { padding: var(--space-6) var(--space-4) var(--space-5); }

.bs-icon-grid__item a {
	color: var(--brand-primary);
	text-decoration: none;
}

.bs-icon-grid__item a::after {
	content: '';
	position: absolute;
	inset: 0;
	border-radius: inherit;
}

.bs-icon-grid__item:hover a { color: var(--brand-accent-text); }

/* ── Logo strip ──────────────────────────────────────────────────────────
   "These customers already chose ..." - the client marks in one row, in
   monochrome, so they read as a reference list and not as a second set of
   brands competing with ours. Colour returns on hover. */
.bs-logos {
	margin-block: var(--space-12);
	text-align: center;
}

.bs-logos__title {
	font-family: var(--font-heading);
	font-size: var(--text-small);
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--blue-700);
	margin: 0 0 var(--space-5);
	max-width: none;
}

.bs-logos__row {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: center;
	gap: var(--space-5) var(--space-8);
}

.bs-logos__row .wp-block-image { margin: 0; }

.bs-logos__row img {
	height: 48px;
	width: auto;
	max-width: 170px;
	object-fit: contain;
	filter: grayscale(1);
	opacity: 0.8;
	transition: filter var(--duration) var(--ease), opacity var(--duration) var(--ease);
}

.bs-logos__row img:hover {
	filter: none;
	opacity: 1;
}

/* ── Logo carousel ───────────────────────────────────────────────────────
   The static monochrome row read as a footnote ("beetje suf", 2026-09-10).
   In colour, larger, and moving slowly it reads as a client list again, the
   way accukluis.nl showed it.

   A marquee rather than a slider with arrows and dots: there is nothing to
   choose between, so a control that has to be operated adds work without
   adding information. theme.js clones the row once and sets the duration from
   the measured width, so the loop is seamless whatever the logo count.

   It stops on hover and on keyboard focus, and prefers-reduced-motion turns it
   back into the wrapping row it degrades to without JavaScript. */
.bs-logos--carousel .bs-logos__viewport {
	overflow: hidden;
	/* The row runs off both edges of the container rather than stopping at the
	   text column, which is what makes it read as a strip that continues. */
	margin-inline: calc(var(--gutter) * -1);
	padding-inline: var(--gutter);
	-webkit-mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
	        mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
}

.bs-logos--carousel .bs-logos__track {
	display: flex;
	gap: var(--space-12);
	width: max-content;
}

.bs-logos--carousel .bs-logos__row {
	flex-wrap: nowrap;
	justify-content: flex-start;
	gap: var(--space-12);
	width: max-content;
}

/* Each row moves left by exactly its own width plus the gap, so the clone
   lands where the original started and the seam is invisible. */
.bs-logos--carousel.is-running .bs-logos__row {
	animation: bs-logo-marquee var(--bs-marquee-duration, 45s) linear infinite;
}

.bs-logos--carousel:hover .bs-logos__row,
.bs-logos--carousel:focus-within .bs-logos__row { animation-play-state: paused; }

.bs-logos--carousel img {
	/* Bumped from 56px: next to the icon grid and product photos elsewhere on
	   the page, the logos read as too small for how much they carried
	   ("te klein in verhouding tot de andere onderdelen", 2026-09-10). */
	height: 76px;
	max-width: 220px;
	filter: none;
	opacity: 1;
}

/* The clone exists only to make the loop seamless; it must not be announced
   or reached by a screen reader or the tab order. */
.bs-logos__row[aria-hidden='true'] { pointer-events: none; }

@keyframes bs-logo-marquee {
	from { transform: translateX(0); }
	to   { transform: translateX(calc(-100% - var(--space-12))); }
}

@media (prefers-reduced-motion: reduce) {
	.bs-logos--carousel.is-running .bs-logos__row { animation: none; }
	.bs-logos--carousel .bs-logos__track { flex-wrap: wrap; justify-content: center; width: auto; }
	.bs-logos--carousel .bs-logos__row { flex-wrap: wrap; justify-content: center; width: auto; }
	.bs-logos--carousel .bs-logos__row[aria-hidden='true'] { display: none; }
}

/* ── Page header: title and actions left, introduction right ─────────────
   The pattern accukluis.nl opens its section pages with, asked for on
   2026-09-10: the H1 and the buttons in the left column, the introduction in
   the right, so the top of the page is one composition instead of a title
   above a paragraph above two buttons.

   The H1 is moved into this block by batterysafety_pagehead() rather than
   written into the page, because the title belongs to WordPress: an editor
   renaming the page in wp-admin must not have to edit the body as well. A page
   opts in by opening with .bs-pagehead; every other page is untouched. */
.bs-pagehead {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--space-6);
	align-items: start;
	margin-bottom: var(--space-12);
}

@media (min-width: 1024px) {
	.bs-pagehead {
		/* 40/60, not the 45/55 the other heading-left splits use: the left
		   column here holds only the title and a button, not body copy, so it
		   can be narrower and give the introduction more room (2026-09-10). */
		grid-template-columns: 2fr 3fr;
		gap: var(--space-16);
		/* Top-aligned, not centred: the title and the introduction start on the
		   same line, which is what makes the two columns read as one header
		   (2026-09-10). Centring hung the title halfway down the paragraph. */
		align-items: start;
	}

	/* The H1's first line box sits lower than the paragraph's because of its
	   size and leading; this pulls the optical top edges level. */
	.bs-pagehead__text { padding-top: 0.6rem; }
}

.bs-pagehead .entry-title {
	/* More air under the title than a heading normally gets: the buttons below
	   it are a separate act, not the next line of the sentence. */
	margin: 0 0 var(--space-8);
	max-width: 14ch;   /* a heading in its own column wants a tighter cap */
}

/*
 * WordPress's own block-spacing rule - `.wp-block-group.is-layout-flow > * + *`
 * - puts the theme's default block gap (24px) on top of the second grid item,
 * stacking on top of the grid's own `gap` and the 0.6rem optical nudge above.
 * Measured: title top and text top were 33.6px apart when only ~10px was
 * intended, and the 24px turned out to be exactly that rule ("tekst rechts
 * uitlijnen verticaal op titel", 2026-09-10). Three classes to outrank its two.
 */
.bs-pagehead > .wp-block-group.bs-pagehead__text { margin-top: 0; }

.bs-pagehead__main > *,
.bs-pagehead__text > * {
	max-width: none;
	margin-inline: 0;
}

.bs-pagehead__main > :last-child,
.bs-pagehead__text > :last-child { margin-bottom: 0; }
.bs-pagehead__text > :first-child { margin-top: 0; }

/* The introduction is the lead: one size up from body copy, so the right
   column carries weight against the heading beside it. */
.bs-pagehead__text > p:first-child {
	font-size: var(--text-body-lg);
	line-height: var(--leading-body-lg);
	color: var(--text-heading);
}

/* A page that wants its opening centred instead - the product overview, where
   the title sits over a grid rather than beside a paragraph. */
.bs-pagehead--center {
	grid-template-columns: 1fr;
	justify-items: center;
	text-align: center;
	max-width: 44rem;
	margin-inline: auto;
}

.bs-pagehead--center .entry-title { max-width: 20ch; }
.bs-pagehead--center .bs-buttons { justify-content: center; }

@media (min-width: 1024px) {
	.bs-pagehead--center { grid-template-columns: 1fr; gap: var(--space-4); }
}

/* ── Section header: same split, mid-page ─────────────────────────────────
   The home page's "branches" and "projecten" openers used to stack heading,
   paragraph and a button in one column; asked to match the .bs-pagehead
   rhythm instead, title and CTA left, introduction right (2026-09-10). A
   separate class rather than reusing .bs-pagehead itself, because that one is
   reserved for the page's own H1 - batterysafety_pagehead() looks for a single
   match per page and injects the real post title into it. */
.bs-section-head {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--space-6);
	align-items: start;
	margin-bottom: var(--space-8);
}

@media (min-width: 1024px) {
	.bs-section-head {
		grid-template-columns: 2fr 3fr;
		gap: var(--space-16);
		align-items: start;
	}

	.bs-section-head__text { padding-top: 0.6rem; }
}

/* Same WordPress block-gap override as .bs-pagehead - see the note above it. */
.bs-section-head > .wp-block-group.bs-section-head__text { margin-top: 0; }

.bs-section-head__main > *,
.bs-section-head__text > * {
	max-width: none;
	margin-inline: 0;
}

.bs-section-head__main > :last-child,
.bs-section-head__text > :last-child { margin-bottom: 0; }
.bs-section-head__text > :first-child { margin-top: 0; }

.bs-section-head__main h2 {
	margin: 0 0 var(--space-6);
	/* No character cap, unlike .bs-pagehead's title: at this column width
	   (467px) a 16ch cap forced "Een accukluis voor diverse branches" onto
	   three cramped lines instead of wrapping naturally at the column edge
	   ("niet op twee/drie regels", 2026-09-10). */
	max-width: none;
}

.bs-section-head__text > p:first-child {
	font-size: var(--text-body-lg);
	line-height: var(--leading-body-lg);
	color: var(--text-heading);
}

/* ── Pull quote ──────────────────────────────────────────────────────────
   One person saying one thing, to take the corporate edge off an About page.
   Blue rule rather than quotation marks: the mark would be the only decorative
   glyph on the site.

   The portrait is optional. `.bs-quote__portrait` renders a 96px round photo
   beside the text when a page supplies one; without it the quote is simply
   text and a name, which is what an About page has until someone is
   photographed. */
.bs-quote {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-6) var(--space-8);
	max-width: 52rem;
	border-left: 4px solid var(--blue-400);
	padding-left: var(--space-8);
}

.bs-quote__portrait {
	flex: 0 0 auto;
	width: 96px;
	height: 96px;
	margin: 0;
	border-radius: 50%;
	overflow: hidden;
}

.bs-quote__portrait img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

.bs-quote > :not(.bs-quote__portrait) { flex: 1 1 20rem; }

.bs-quote__text {
	margin: 0 0 var(--space-4);
	max-width: none;
	font-size: var(--text-body-lg);
	line-height: var(--leading-body-lg);
	color: var(--text-heading);
}

.bs-quote__by {
	margin: 0;
	max-width: none;
	font-size: var(--text-small);
	color: var(--text-muted);
}

.bs-quote__by strong {
	display: block;
	color: var(--brand-primary);
	font-size: var(--text-body);
}

@media (max-width: 599px) {
	.bs-quote { padding-left: var(--space-5); }
}

/* ── Certification ───────────────────────────────────────────────────────
   ECB.S is the one certification a buyer checks for, so it is a component
   rather than a sentence: the mark beside the claim, on every page that makes
   the claim. "ECB.S certificering is zeer belangrijk", 2026-09-10. */
.bs-cert {
	display: flex;
	align-items: center;
	gap: var(--space-5);
	background: var(--surface);
	border: 1px solid var(--border-subtle);
	border-left: 4px solid var(--blue-400);
	border-radius: var(--radius-card);
	padding: var(--space-5) var(--space-6);
	margin-block: var(--space-8);
	box-shadow: var(--shadow-1);
}

.bs-cert .wp-block-image,
.bs-cert figure {
	flex: 0 0 auto;
	width: 72px;
	margin: 0;
}

.bs-cert img {
	width: 100%;
	height: auto;
	display: block;
	mix-blend-mode: multiply;   /* the mark ships on white */
}

.bs-cert p {
	margin: 0;
	max-width: none;
	font-size: var(--text-body);
	line-height: var(--leading-body);
	color: var(--text-body-color);
}

.bs-cert strong {
	display: block;
	font-family: var(--font-heading);
	font-size: var(--text-h4);
	line-height: var(--leading-h4);
	color: var(--brand-primary);
	margin-bottom: 2px;
}

/* On navy the card keeps its white ground - the mark needs it - so only the
   spacing changes. */
.bs-band--navy .bs-cert { border-left-color: var(--brand-accent); }

/* A compact form for a product card: the mark and one line. */
.bs-cert--sm {
	padding: var(--space-3) var(--space-4);
	gap: var(--space-3);
	margin-block: var(--space-4);
}

.bs-cert--sm .wp-block-image,
.bs-cert--sm figure { width: 44px; }
.bs-cert--sm p { font-size: var(--text-small); }
.bs-cert--sm strong { font-size: var(--text-body); }

/* ── Gallery ─────────────────────────────────────────────────────────────
   Three project photographs in a row, one frame ratio. */
.bs-gallery {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
	gap: var(--space-4);
	margin-block: var(--space-12);
}

.bs-gallery .wp-block-image {
	margin: 0;
	aspect-ratio: 4 / 3;
	overflow: hidden;
	border-radius: var(--radius-card);
}

.bs-gallery img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* ── Form beside its introduction ─────────────────────────────────────────
   An embedded form (Zoho) lays itself out inside its iframe and cannot be
   asked to line up with the page. Given a column of its own in a white card,
   with the heading and the argument beside it, it reads as a designed block
   instead of a form floating mid-page. Inside a navy band the card is the one
   light object, which is what a conversion block wants. */
.bs-form-split {
	display: grid;
	/* Even, not 40/60: the form card is a full composition in its own right,
	   not a footnote to the text beside it, and 50/50 reads calmer than the
	   card crowding one side (2026-09-10). */
	grid-template-columns: 1fr 1fr;
	gap: var(--space-12);
	align-items: start;
}

.bs-form-split__text > *,
.bs-form-split__text h2,
.bs-form-split__text h3 {
	max-width: none;
	margin-inline: 0;
}

.bs-form-split__text > :first-child { margin-top: 0; }

.bs-form-split__form {
	background: var(--surface);
	border-radius: var(--radius-card);
	padding: var(--space-6);
	box-shadow: var(--shadow-2);
}

.bs-form-split__form > * { margin: 0; }

@media (max-width: 1023px) {
	.bs-form-split {
		grid-template-columns: 1fr;
		gap: var(--space-8);
	}
}

/* Variant for the "which cabinet fits" block on Accukluizen: the heading runs
   full width above the columns instead of sitting inside .bs-form-split__text,
   and the form comes first (left), the argument beside it (right) - the other
   .bs-form-split blocks (home, regelgeving) keep title+text left, form right,
   this is the one Martijn asked to read differently (2026-09-10). */
.bs-form-split--intro > h2 {
	grid-column: 1 / -1;
	max-width: none;
	margin: 0 0 var(--space-6);
	/* h2's default margin-top (var(--space-16)) assumes there is something
	   above it; here it is the first thing in the band. */
	margin-top: 0;
}

@media (max-width: 1023px) {
	.bs-form-split--intro > h2 { margin-bottom: var(--space-4); }
}

/* A form on its own (the questionnaire page): a card at a form's width, not a
   frame stretched across the whole measure. */
.bs-article .entry-content > .bs-zoho-form {
	max-width: 48rem;
	background: var(--surface);
	border: 1px solid var(--border-subtle);
	border-radius: var(--radius-card);
	padding: var(--space-6);
	box-shadow: var(--shadow-1);
	margin-block: var(--space-8);
}

/* ── Check list ──────────────────────────────────────────────────────────
   The advantages list under a product or a sector: a tick instead of a
   bullet. Blue, because it is wayfinding, not an action. */
.bs-checks {
	list-style: none;
	padding: 0;
	margin: var(--space-6) 0;
	display: grid;
	gap: var(--space-2);
}

.bs-article .entry-content > .bs-checks { padding-left: 0; }

.bs-checks li {
	position: relative;
	padding-left: 1.9em;
	margin: 0;
}

.bs-checks li::before {
	content: '';
	position: absolute;
	left: 0.15em;
	top: 0.4em;
	width: 0.8em;
	height: 0.45em;
	border-left: 2.5px solid var(--blue-400);
	border-bottom: 2.5px solid var(--blue-400);
	transform: rotate(-45deg);
}

/* Comparison variant (Regelgeving: safety cabinet vs SafetyFirst accukluis) -
   a red minus for the ordinary cabinet, a green plus for the accukluis side,
   an explicit con/pro pair rather than the wayfinding blue tick .bs-checks
   uses everywhere else ("groene plusjes ... rode minnetjes, niet grijs zoals
   bij Kluisstore.nl", 2026-09-10). */
.bs-checks--plus li::before,
.bs-checks--minus li::before {
	left: 0;
	top: 0.05em;
	width: auto;
	height: auto;
	border: none;
	transform: none;
	font-family: var(--font-heading);
	font-size: 1.15em;
	font-weight: 800;
	line-height: 1;
}

.bs-checks--plus li::before { content: '+'; color: #1a7f4b; }
.bs-checks--minus li::before { content: '\2212'; color: #c0392b; }

/* ── Key facts ───────────────────────────────────────────────────────────
   Three or four figures a buyer compares first - capacity, outlets, shelves,
   certification - as tiles under a product heading. Figures only where the
   manufacturer states them. */
.bs-facts {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(130px, 100%), 1fr));
	gap: var(--space-3);
	margin: var(--space-6) 0;
}

.bs-facts > * { margin: 0; }

.bs-fact {
	background: var(--surface-muted);
	border-radius: var(--radius-button);
	padding: var(--space-4);
	line-height: var(--leading-body);
}

.bs-fact strong {
	display: block;
	font-family: var(--font-heading);
	font-size: var(--text-h3);
	font-weight: 800;
	line-height: 1.1;
	color: var(--brand-primary);
	font-variant-numeric: tabular-nums;
}

.bs-fact span {
	display: block;
	font-size: var(--text-small);
	color: var(--text-muted);
	margin-top: var(--space-1);
}

/* ── Badge ───────────────────────────────────────────────────────────────
   A small label: a news item's region, a post's category. Pale blue on navy
   text, never orange. */
.bs-badge {
	display: inline-block;
	padding: 2px 10px;
	border-radius: var(--radius-badge);
	background: var(--blue-100);
	color: var(--brand-primary);
	font-family: var(--font-heading);
	font-size: 0.75rem;
	font-weight: 700;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	line-height: 1.6;
	white-space: nowrap;
}

/* ── Contact card ────────────────────────────────────────────────────────
   The publisher's address, telephone and mailbox as one block on the contact
   page, beside the form. */
.bs-contact-card {
	background: var(--surface-muted);
	border-radius: var(--radius-card);
	padding: var(--space-6);
	margin-block: var(--space-6);
}

.bs-contact-card .bs-company {
	font-size: var(--text-body);
	color: var(--text-body-color);
}

.bs-contact-card .bs-company p { margin: 0 0 var(--space-4); }
.bs-contact-card .bs-company p:last-child { margin-bottom: 0; }
.bs-contact-card .bs-company p:first-child { font-weight: 600; color: var(--brand-primary); }
.bs-contact-card .bs-company a { font-weight: 600; }

/* ── Bands: the blocks that sit inside them ──────────────────────────────
   A band provides the vertical rhythm, so the first block inside it starts on
   the band's own top edge and the grids drop their outer margins. */
.bs-band__inner > :first-child,
.bs-band__inner > .bs-form-split:first-child > .bs-form-split__text > :first-child { margin-top: 0; }

.bs-band__inner > :last-child { margin-bottom: 0; }

.bs-band .bs-cards,
.bs-band .bs-icon-grid,
.bs-band .bs-gallery,
.bs-band .bs-post-grid,
.bs-band .bs-news-feed { margin-block: var(--space-8) 0; }

.bs-band .bs-cards + p,
.bs-band .bs-icon-grid + p,
.bs-band .bs-news-feed + p { margin: var(--space-8) 0 0; }

/* On navy everything that is not inside a white card inverts: the copy, the
   list ticks, and an outline button, which becomes the ghost variant without
   the author having to know that name. */
.bs-band--navy .bs-band__inner > p,
.bs-band--navy .bs-band__inner > ul,
.bs-band--navy .bs-form-split__text > *,
.bs-band--navy .bs-checks li { color: var(--text-on-inverse); }

.bs-band--navy .bs-band__inner a:not(.bs-button):not(.bs-cta__button) { color: var(--text-on-inverse); }
.bs-band--navy .bs-band__inner a:not(.bs-button):not(.bs-cta__button):hover { color: var(--brand-accent); }

.bs-band--navy .bs-checks li::before { border-color: var(--brand-accent); }

/* A white card on a navy band is not part of the band: its text has to go back
   to the light-ground colours. Without this the card inherited the band's
   near-white body colour and its list sat at about 1.1:1 on white - the
   comparison block on /regelgeving/ was unreadable (2026-09-10). */
.bs-band--navy .bs-cards .wp-block-group,
.bs-band--navy .bs-cards .wp-block-group > *,
.bs-band--navy .bs-cards li,
.bs-band--navy .bs-form-split__form,
.bs-band--navy .bs-form-split__form > * { color: var(--text-body-color); }

/* Stated on the child selector too: the rule above reaches every direct child
   of the card, headings included, and would otherwise win on specificity. */
.bs-band--navy .bs-cards .wp-block-group > h2,
.bs-band--navy .bs-cards .wp-block-group > h3,
.bs-band--navy .bs-cards .wp-block-group > h4 { color: var(--text-heading); }

.bs-band--navy .bs-cards .bs-checks li::before { border-color: var(--blue-400); }

.bs-band--navy .bs-cards a:not(.bs-button) { color: var(--brand-primary); }
.bs-band--navy .bs-cards a:not(.bs-button):hover { color: var(--brand-accent-text); }

/* Same white-card-on-navy-band problem as .bs-cards above, this time the
   sector icon grid on Over ons: the band's near-white link colour landed on
   the icon tiles' own white background, so the label under each icon was
   unreadable ("kan tekst onder icoon niet zien", 2026-09-10).
   Qualified with .bs-band__inner too, not just .bs-band--navy: the rule it
   overrides is `.bs-band--navy .bs-band__inner a:not(...):not(...)`, and
   each :not() counts toward specificity, so a plain 3-class selector here
   still lost to that one's effective 4 classes. */
.bs-band--navy .bs-band__inner .bs-icon-grid .bs-icon-grid__item,
.bs-band--navy .bs-band__inner .bs-icon-grid .bs-icon-grid__item a { color: var(--brand-primary); }
.bs-band--navy .bs-band__inner .bs-icon-grid .bs-icon-grid__item:hover a { color: var(--brand-accent-text); }

.bs-band--navy .bs-split__text .bs-button--secondary,
.bs-band--navy .bs-form-split__text .bs-button--secondary,
.bs-band--navy .bs-band__inner > p > .bs-button--secondary {
	color: var(--text-on-inverse);
	border-color: var(--border-subtle);
}

.bs-band--navy .bs-split__text .bs-button--secondary:hover,
.bs-band--navy .bs-form-split__text .bs-button--secondary:hover,
.bs-band--navy .bs-band__inner > p > .bs-button--secondary:hover {
	background: rgba(245, 245, 245, 0.1);
	border-color: var(--text-on-inverse);
	color: var(--text-on-inverse);
}

.bs-band--navy .bs-band__inner > .bs-lead { color: var(--text-on-inverse); }

/* Two buttons in one paragraph sit side by side with a gap, and wrap on a phone. */
.bs-buttons {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-3);
	align-items: center;
}

/* ── News feed ───────────────────────────────────────────────────────────
   Headlines that link out, three across. Region and date on the first line,
   the headline, the source under it. The arrow says "leaves this site". */
.bs-news-feed {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
	gap: var(--space-6);
	margin-block: var(--space-8);
}

.bs-news-card--feed { padding: var(--space-5) var(--space-6); }

.bs-news-card__meta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-3);
	margin: 0 0 var(--space-3);
	font-size: var(--text-small);
	color: var(--text-muted);
}

.bs-news-card--feed .bs-news-card__title {
	font-size: var(--text-h4);
	line-height: var(--leading-h4);
	margin: 0 0 var(--space-2);
}

.bs-news-card--feed .bs-news-card__title a { color: var(--brand-primary); }

.bs-news-card--feed .bs-news-card__title a::after {
	content: ' \2197';
	font-size: 0.8em;
	color: var(--blue-700);
}

.bs-news-card--feed .bs-news-card__source { margin-top: auto; }

.bs-news-feed__more { margin: 0; }

/* ── Post cards - the blog ───────────────────────────────────────────────
   The image fills the top of the card edge to edge; the body sits under it.
   The title is the one link and it covers the whole card. */
.bs-post-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
	gap: var(--card-gap);
	margin-block: var(--space-12);
}

.bs-post-card {
	position: relative;
	display: flex;
	flex-direction: column;
	background: var(--surface);
	border: 1px solid var(--border-subtle);
	border-radius: var(--radius-card);
	overflow: hidden;
	box-shadow: var(--shadow-card);
	transition: box-shadow var(--duration) var(--ease), transform var(--duration) var(--ease);
}

.bs-post-card:hover {
	box-shadow: var(--shadow-card-hover);
	transform: translateY(-2px);
}

.bs-post-card:focus-within {
	outline: 2px solid var(--brand-primary);
	outline-offset: 4px;
}

.bs-post-card__media {
	display: block;
	aspect-ratio: 16 / 10;
	overflow: hidden;
	background: linear-gradient(135deg, var(--brand-primary), var(--blue-700));
}

.bs-post-card__media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
	transition: transform var(--duration-slow) var(--ease);
}

.bs-post-card:hover .bs-post-card__media img { transform: scale(1.03); }

.bs-post-card__body {
	display: flex;
	flex-direction: column;
	flex: 1;
	padding: var(--space-5) var(--space-6) var(--space-6);
}

.bs-post-card__meta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-3);
	margin: 0 0 var(--space-3);
	font-size: var(--text-small);
	color: var(--text-muted);
}

.bs-post-card__title {
	font-size: var(--text-h4);
	line-height: var(--leading-h4);
	margin: 0 0 var(--space-3);
	max-width: none;
}

.bs-post-card__title a {
	color: var(--brand-primary);
	text-decoration: none;
}

.bs-post-card__title a::after {
	content: '';
	position: absolute;
	inset: 0;
}

.bs-post-card:hover .bs-post-card__title a { color: var(--brand-accent-text); }

.bs-post-card__excerpt {
	margin: 0 0 var(--space-4);
	font-size: var(--text-body);
	line-height: var(--leading-body);
	color: var(--text-body-color);
}

.bs-post-card__more {
	margin: auto 0 0;
	font-size: var(--text-small);
	font-weight: 600;
	color: var(--blue-700);
}

/* The newest post, one wide card: picture left, text right, bigger title. */
.bs-post-featured { margin-block: var(--space-8) 0; }

.bs-post-card--featured { display: grid; grid-template-columns: 1fr; }

@media (min-width: 1024px) {
	.bs-post-card--featured { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }

	.bs-post-card--featured .bs-post-card__media {
		aspect-ratio: auto;
		height: 100%;
		min-height: 320px;
	}

	.bs-post-card--featured .bs-post-card__body { padding: var(--space-8); }

	.bs-post-card--featured .bs-post-card__title {
		font-size: var(--text-h2);
		line-height: var(--leading-h2);
	}

	.bs-post-card--featured .bs-post-card__excerpt { font-size: var(--text-body-lg); }
}

.bs-post-featured + .bs-post-grid { margin-top: var(--space-8); }

.bs-blog__intro { margin-bottom: var(--space-6); }

/* Category chips */
.bs-chips {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2);
	margin: var(--space-6) 0 var(--space-4);
}

.bs-chip {
	display: inline-flex;
	align-items: center;
	min-height: 36px;
	padding: 6px 14px;
	border-radius: var(--radius-badge);
	border: 1px solid var(--border-control);
	background: var(--surface);
	color: var(--brand-primary);
	font-size: var(--text-small);
	font-weight: 600;
	text-decoration: none;
	transition: background-color var(--duration) var(--ease), color var(--duration) var(--ease);
}

.bs-chip:hover {
	background: rgba(16, 57, 91, 0.06);
	color: var(--brand-primary);
}

.bs-chip.is-active {
	background: var(--brand-primary);
	border-color: var(--brand-primary);
	color: var(--text-on-inverse);
}

/* Pagination. GeneratePress replaces WordPress's navigation wrapper with its
   own (navigation_markup_template), so the class the_posts_pagination() was
   given never reaches the page; the rules hang off the blog article instead. */
.bs-blog .nav-links {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--space-2);
	margin-block: var(--space-12);
}

.bs-blog .paging-navigation,
.bs-blog .navigation { max-width: none; }

.bs-blog .nav-links .page-numbers {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 44px;
	min-height: 44px;
	padding: 0 var(--space-3);
	border: 1px solid var(--border-control);
	border-radius: var(--radius-button);
	color: var(--brand-primary);
	font-weight: 600;
	text-decoration: none;
}

.bs-blog .nav-links a.page-numbers:hover { background: rgba(16, 57, 91, 0.06); }

.bs-blog .nav-links .page-numbers.current {
	background: var(--brand-primary);
	border-color: var(--brand-primary);
	color: var(--text-on-inverse);
}

.bs-blog .nav-links .page-numbers.dots { border: none; }

.bs-blog .navigation .screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
}

/* A single post: the featured image at the top of the column, then the byline. */
.bs-post-hero { margin: 0 0 var(--space-8); }

.bs-post-hero img {
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 9;
	object-fit: cover;
	border-radius: var(--radius-card);
	display: block;
}

.bs-related-posts { margin-block: var(--space-16) var(--space-8); }
.bs-related-posts h2 { margin-top: 0; max-width: none; }
.bs-related-posts .bs-post-grid { margin-block: var(--space-6) 0; }

/* ── A single post ───────────────────────────────────────────────────────
   The article was one long left column with the CTA stuck on the end
   ("alles staat nu links en onder elkaar", 2026-09-10). The body keeps the
   reading measure and the space beside it now carries the next step: from
   1024px the article and an aside sit side by side, and the aside sticks
   while the article scrolls past it.

   Below 1024px it is one column again, with the aside after the article -
   the order it is written in, so nothing depends on the grid. */
.bs-postlayout {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--space-10);
	margin-block: var(--space-8);
}

@media (min-width: 1024px) {
	.bs-postlayout {
		/* 60/40, asked for on 2026-09-10. Was the reading measure plus
		   whatever remained, which measured 640/528 - close enough to even
		   that the aside read as a second column rather than as a margin.
		   At this container width 60% lands around 670px, so the article
		   column is still within a few characters of the measure. */
		grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
		gap: var(--space-16);
		align-items: start;
	}

	/*
	 * The CTA sticks, the picture above it does not: sticking the whole
	 * column kept the picture parked beside the article for its entire
	 * length, long after it had been looked at ("alleen blok advies nodig
	 * laten staan", 2026-09-11). The column stretches to the row height so
	 * the CTA has somewhere to travel - align-items: start above would
	 * otherwise shrink it to its contents and sticky would have no room.
	 */
	.bs-postlayout__aside { align-self: stretch; }

	.bs-postlayout__aside .bs-cta {
		position: sticky;
		top: 7rem;   /* clears the sticky header */
	}
}

/* Inside the grid the article column is the measure, so the children stop
   capping themselves and the wide blocks stop breaking out - the column is
   already the width they would break out to. `.bs-article .bs-byline` is
   named too, because it carries the measure at two classes and would
   otherwise stop short of the column it now sits in. */
.bs-postlayout__body > *,
.bs-article .bs-postlayout__body > .bs-byline {
	max-width: none;
	margin-inline: 0;
}

.bs-postlayout__body > :first-child { margin-top: 0; }

/* The picture lifted out of the foot of the article (see
   batterysafety_post_components()). It sits above the CTA and shares its
   corner radius, so the aside reads as one column of two objects. */
.bs-postlayout__photo {
	margin-bottom: var(--space-6);
}

.bs-postlayout__photo figure {
	margin: 0;
	border-radius: var(--radius-card);
	overflow: hidden;
}

.bs-postlayout__photo img {
	display: block;
	width: 100%;
	height: auto;
}

.bs-postlayout__aside .bs-cta {
	margin-block: 0;
	background: var(--surface-muted);
}

.bs-postlayout__aside .bs-cta__options { grid-template-columns: 1fr; }

/* The byline sits above the two columns, under the hero, where it belongs to
   the article as a whole. */
.bs-article .bs-post-hero + .bs-byline { margin-top: 0; }

/* ── Editorial image placeholder ─────────────────────────────────────────
   Marks where photography is needed. Deliberately obvious: an empty grey box
   that looks intentional would ship to production unnoticed.
   Per the design brief, imagery must be product-in-context - the cabinet in a
   real setting - never a product shot on white. */
.bs-image-placeholder {
	display: grid;
	place-items: center;
	aspect-ratio: 4 / 3;
	border: 2px dashed var(--border-control);
	border-radius: var(--radius-card);
	background: var(--surface-muted);
	padding: var(--space-6);
	text-align: center;
	color: var(--text-muted);
	font-size: var(--text-small);
}

.bs-image-placeholder p { margin: 0; max-width: 32ch; }
.bs-image-placeholder strong { display: block; color: var(--brand-primary); }

/* ── Hero block ──────────────────────────────────────────────────────────
   The front page carries its H1 inside the hero, so GeneratePress's own page
   title is suppressed there - see functions.php. Everywhere else the entry
   title is the H1 and this block is not used. */
.bs-hero-block {
	margin-top: var(--space-8);
	margin-bottom: var(--space-20);
}

/* The hero used to carry its own 3fr 2fr override here, the one 60/40 split on
   a page of 50/50s. Since 2026-09-09 .bs-split itself is 60/40, so the override
   went; the hero inherits its ratio like every other split. */

/*
 * 22ch, and the number is the break point rather than a taste.
 *
 * "Stop a battery fire becoming a building fire" sets 1043px at 48px/800. Greedy
 * line breaking gives three lines at the old 20ch (562px) - it takes "becoming a
 * building" and leaves "fire" on its own. Two lines need a cap of at least 588px,
 * the width of "becoming a building fire", and less than 694px, the width of
 * "Stop a battery fire becoming" - past that the break moves to after "becoming"
 * and splits the phrase in the wrong place. 22ch is 619px, in the middle of that
 * window, and breaks it where the sentence does: the fire, then the building.
 */
.bs-hero-block h1 {
	margin-top: 0;
	max-width: 22ch;
}

/*
 * The emblem was also used as a watermark here, as a ring showing above and
 * below the hero photograph. Removed 2026-09-07, along with the navy-band
 * placement - see "The Lock" above. It is not used anywhere now.
 */
.bs-lead {
	font-size: var(--text-body-lg);
	line-height: var(--leading-body-lg);
	color: var(--text-heading);
	font-weight: 600;
}

/* ── Two statistics side by side inside a band ───────────────────────────── */
.bs-stat-row {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
	gap: var(--card-gap);
	margin-bottom: var(--space-8);
}

.bs-stat-row .bs-stat { margin-block: 0; }

.bs-band__note {
	max-width: var(--measure);
	margin-bottom: 0;
	color: var(--text-body-color);
}

.bs-band--muted h2 { margin-top: 0; }

/* ── Hero (pattern variant) ──────────────────────────────────────────────
   On mobile the image moves below the text so the CTA stays above the fold. */
.bs-hero { margin-block: var(--space-8) var(--space-16); }

.bs-hero .wp-block-column > *:first-child { margin-top: 0; }

.bs-hero__media img {
	border-radius: var(--radius-card);
	width: 100%;
	height: auto;
}

@media (max-width: 1023px) {
	.bs-hero { flex-direction: column; }
	.bs-hero .wp-block-column:last-child { order: 2; }
}

/* Certification badges: monochrome, beside the CTA rather than three
   paragraphs above it, where they do nothing at the moment of clicking. */
.bs-badges {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-5);
	margin-top: var(--space-6);
}

.bs-badges img,
.bs-badges svg {
	height: 36px;
	width: auto;
	filter: grayscale(1);
	opacity: 0.75;
}

/* ===========================================================================
   BUTTONS
   One scale. Buttons that sit next to each other are always the same size -
   the difference is fill versus outline, never height.
   ======================================================================== */

.bs-button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-2);
	font-family: var(--font-body);
	font-size: var(--text-button);
	font-weight: 600;
	line-height: 1;
	padding: 14px 28px;
	border-radius: var(--radius-button);
	border: 1px solid transparent;
	text-decoration: none;
	cursor: pointer;
	transition: background-color var(--duration) var(--ease),
	            color var(--duration) var(--ease),
	            border-color var(--duration) var(--ease),
	            transform var(--duration) var(--ease),
	            box-shadow var(--duration) var(--ease);
}

.bs-button--sm { padding: 10px 18px; font-size: var(--text-small); }
.bs-button--lg { padding: 17px 32px; font-size: var(--text-body); }

/* The most important button on the site should not carry a 15px label. */
.bs-button--hero { padding: 18px 36px; font-size: var(--text-body-lg); }

/* ── Primary - the CTA, and the only orange element on the page ────── */
.bs-button--primary {
	background: var(--brand-accent);
	color: var(--on-accent);       /* 6.57:1 - see the token */
	/* One step heavier than the other buttons. A 15px label on the site's most
	   important control was reading soft against the orange; the weight does as
	   much for that as the darker colour does. */
	font-weight: 700;
	border-color: transparent;
}

/*
 * Hover: lighter fill, a 2px lift and an orange-tinted shadow the button seems
 * to cast on the page, so the control reads as raised rather than merely
 * recoloured. Asked for on 2026-09-10 ("hover CTA duidelijker, aantrekkelijker").
 *
 * The fill goes lighter, not darker, because the label is navy on orange: at
 * #FFA050 it measures 5.91:1 against 5.14:1 at rest. Darkening would have taken
 * it the wrong way - see "A pre-existing failure this surfaced" in the design
 * system. The shadow is the accent at low alpha rather than the neutral
 * elevation token, which is what makes it read as the button's own light.
 */
.bs-button--primary:hover {
	background: var(--brand-accent-hover);
	color: var(--on-accent);
	transform: translateY(-2px);
	box-shadow: 0 6px 16px rgba(255, 140, 48, 0.42), 0 2px 4px rgba(16, 57, 91, 0.14);
}

.bs-button--primary:active {
	background: var(--brand-accent-active);
	transform: translateY(0);
	box-shadow: 0 1px 2px rgba(16, 57, 91, 0.2);
}

/* The outline button gains the same lift, in navy, so a pair of buttons behaves
   as one control set instead of one moving and one not. */
.bs-button--secondary:hover {
	transform: translateY(-2px);
	box-shadow: 0 6px 16px rgba(16, 57, 91, 0.18);
}

.bs-button--secondary:active {
	transform: translateY(0);
	box-shadow: none;
}

/* ── Secondary on light - navy outline, never orange ───────────────── */
.bs-button--secondary {
	background: transparent;
	color: var(--brand-primary);
	border-color: var(--brand-primary);
}

.bs-button--secondary:hover {
	background: rgba(16, 57, 91, 0.06);
	color: var(--brand-primary);
}

.bs-button--secondary:active { background: rgba(16, 57, 91, 0.12); }

/* ── Secondary on navy ────────────────────────────────────────────── */
.bs-button--ghost {
	background: transparent;
	color: var(--text-on-inverse);
	/* A button edge is a control boundary; on navy the light border already
	   clears 3:1 comfortably. */
	border-color: var(--border-subtle);
}

.bs-button--ghost:hover {
	background: rgba(245, 245, 245, 0.1);
	border-color: var(--text-on-inverse);
	color: var(--text-on-inverse);
}

/* ── Tertiary - a link with direction, not a button ────────────────── */
.bs-button--tertiary {
	background: transparent;
	border: none;
	padding: 0;
	color: var(--brand-primary);
	text-decoration: underline;
	text-underline-offset: 4px;
}

.bs-button--tertiary:hover { color: var(--brand-accent-text); }
.bs-button--tertiary::after {
	content: '→';
	transition: transform var(--duration) var(--ease);
}
.bs-button--tertiary:hover::after { transform: translateX(4px); }

.bs-button[disabled],
.bs-button--disabled {
	opacity: 0.45;
	cursor: not-allowed;
	transform: none;
}

.bs-button:focus-visible {
	outline: 2px solid var(--brand-primary);
	outline-offset: 2px;
}

.bs-button--ghost:focus-visible { outline-color: var(--text-on-inverse); }

/* White space around a CTA raises the click rate: the button reads as its own
   object rather than as part of the paragraph. */
.bs-cta-block { margin-block: var(--space-8); }

/* ===========================================================================
   DUAL-DESTINATION CTA
   ======================================================================== */

.bs-cta {
	background: var(--surface-muted);
	border-radius: var(--radius-card);
	padding: var(--space-8);
	margin-block: var(--space-16);
}

.bs-cta__heading {
	font-size: var(--text-h3);
	margin-top: 0;
	margin-bottom: var(--space-3);
	max-width: var(--measure-heading);
}

.bs-cta__intro {
	max-width: var(--measure-intro);
	margin-bottom: var(--space-6);
}

/* This one overflowed its own box rather than the page: the CTA is padded, so at
   375px the options sat in 251px and the 260px floor pushed 9px of every button
   out through the rounded corner. */
.bs-cta__options {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
	gap: var(--space-4);
	margin-bottom: var(--space-6);
}

.bs-cta__button {
	display: block;
	padding: var(--space-6);
	border-radius: var(--radius-button);
	text-decoration: none;
	border: 1px solid var(--brand-primary);
	background: var(--surface);
	color: var(--brand-primary);
	transition: box-shadow var(--duration) var(--ease),
	            transform var(--duration) var(--ease),
	            background-color var(--duration) var(--ease);
}

/* The primary destination carries the orange fill. */
.bs-cta__button--primary {
	background: var(--brand-accent);
	border-color: transparent;
	color: var(--on-accent);
}

.bs-cta__button--primary:hover {
	background: var(--brand-accent-hover);
	color: var(--on-accent);
	box-shadow: 0 8px 22px rgba(255, 140, 48, 0.4);
}

.bs-cta__button--secondary:hover {
	background: rgba(16, 57, 91, 0.06);
	color: var(--brand-primary);
}

.bs-cta__button:hover {
	transform: translateY(-2px);
	box-shadow: var(--shadow-2);
}

.bs-cta__button:active { transform: none; box-shadow: none; }

.bs-cta__label {
	display: block;
	font-family: var(--font-heading);
	font-weight: 700;
	font-size: var(--text-h4);
	line-height: var(--leading-h4);
	margin-bottom: var(--space-1);
}

.bs-cta__blurb {
	display: block;
	font-size: var(--text-small);
	line-height: var(--leading-body);
	color: var(--text-muted);
}

/* Full colour, not 80%: at 80% opacity this measured 3.21:1 on the orange fill,
   below AA for 14px text. Semi-transparent text also shifts colour with whatever
   sits behind it, which is exactly what the brief warns against. Now on
   --on-accent, which takes this 14px line from 5.04:1 to 6.57:1 - the blurb is
   the smallest text anywhere on an orange ground, so it gains the most. */
.bs-cta__button--primary .bs-cta__blurb { color: var(--on-accent); }

.bs-cta__note {
	margin: 0;
	font-size: var(--text-small);
	line-height: var(--leading-body);
	color: var(--text-muted);
	max-width: var(--measure-intro);
}

.bs-cta__note a { color: var(--text-muted); }
.bs-cta__note a:hover { color: var(--brand-accent-text); }

/* ===========================================================================
   BYLINE - named author, credentials, both dates
   ======================================================================== */

.bs-byline {
	display: flex;
	align-items: center;
	gap: var(--space-4);
	border-top: 1px solid var(--border-subtle);
	border-bottom: 1px solid var(--border-subtle);
	padding-block: var(--space-4);
	margin-block: var(--space-8);
	font-size: var(--text-small);
	line-height: var(--leading-body);
}

.bs-byline__avatar {
	flex: 0 0 auto;
	width: 48px;
	height: 48px;
	border-radius: var(--radius-badge);
	overflow: hidden;
	background: var(--brand-primary);
	color: var(--text-on-inverse);
	display: grid;
	place-items: center;
	font-family: var(--font-heading);
	font-weight: 700;
	font-size: var(--text-body);
	letter-spacing: 0.02em;
}

.bs-byline__avatar img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

.bs-byline__text { min-width: 0; }
.bs-byline p { margin: 0; }
.bs-byline__author { color: var(--text-heading); font-weight: 600; }
.bs-byline__dates { color: var(--text-muted); }

/* ===========================================================================
   END OF PAGE - the FAQ beside the next step

   The markup is assembled by batterysafety_pair_faq_and_next() in
   functions.php; see the note there for why the three closing blocks are
   regrouped rather than written this way in the page.

   1.4fr / 1fr rather than equal columns: the left column holds question text
   that wraps, the right a card whose content is short. Measured on the
   regulations pillar, the tallest page of the set, that split leaves the two
   columns 18px apart in height.

   align-items: start, because a card must not stretch to the height of the
   accordion beside it - and the accordion grows when a question is opened.
   ======================================================================== */

.bs-endgrid {
	display: grid;
	grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
	gap: var(--card-gap);
	align-items: start;
	margin-block: var(--space-16);
}

.bs-endgrid__faq  { grid-column: 1; }
.bs-endgrid__next { grid-column: 2; }

/* The related list crosses both columns rather than sitting under one of them,
   which is what keeps the two-column row square. */
.bs-endgrid__read { grid-column: 1 / -1; }

/* The blocks carried their own vertical rhythm for life in a single column. In a
   grid row the row itself provides it, and the first item in each column has to
   start on the same line as the other. */
.bs-endgrid__faq > :first-child,
.bs-endgrid__next > :first-child {
	margin-top: 0;
}

.bs-endgrid .bs-cta { margin-block: 0; }
.bs-endgrid .bs-related { margin-block: 0; }

/* A list of four links down the left edge of a 1232px card would be a thin line
   of text in a wide box. Multi-column rather than a grid, because the list
   markers survive it and a grid-item <li> loses them. */
.bs-endgrid__read .bs-related ul {
	columns: 2;
	column-gap: var(--space-12);
}

.bs-endgrid__read .bs-related li { break-inside: avoid; }

/* The 640px measure is applied by .bs-article .entry-content > *, which reaches
   direct children only - inside the grid these are grandchildren, so the cap
   lifts on its own. The headings inside the CTA keep their own shorter caps,
   which still read correctly in a 500px column. */

@media (max-width: 1023px) {
	/* One column, and the original order: questions, next step, read next. */
	.bs-endgrid {
		grid-template-columns: 1fr;
		gap: var(--space-12);
	}

	.bs-endgrid__faq,
	.bs-endgrid__next,
	.bs-endgrid__read {
		grid-column: 1;
	}

	.bs-endgrid__read .bs-related ul { columns: 1; }
}
/* ===========================================================================
   RELATED LINKS
   ======================================================================== */

.bs-related {
	background: var(--surface-muted);
	border-radius: var(--radius-card);
	padding: var(--space-6);
	margin-block: var(--space-12);
}

.bs-related__heading {
	font-size: var(--text-h4);
	margin: 0 0 var(--space-3);
	color: var(--blue-700);
}

.bs-related ul { margin: 0; padding-left: 1.2em; }
.bs-related li { margin-bottom: var(--space-2); }

/* Navigational links in a list, not links inside a sentence, so the 24px target
   minimum applies. At 14-18px they measured 20-22px; the padding clears it
   without moving anything, because the line box already had the room. */
.bs-related li a,
.bs-glossary-list dt a,
.bs-cards h3 a,
.bs-news-card h3 a {
	display: inline-block;
	padding-block: 4px;
}
.bs-related__note { color: var(--text-muted); font-size: var(--text-small); }

.bs-news-quote {
	border-left: 4px solid var(--brand-accent);
	padding-left: var(--space-4);
	margin: 0 0 var(--space-4);
	font-size: var(--text-body-lg);
	font-style: italic;
	color: var(--text-heading);
}

/* ===========================================================================
   CARDS
   ======================================================================== */

.bs-news-list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
	gap: var(--card-gap);
}

.bs-card,
.bs-news-card {
	display: flex;
	flex-direction: column;
	border: 1px solid var(--border-subtle);
	border-radius: var(--radius-card);
	padding: var(--space-6);
	background: var(--surface);
	box-shadow: var(--shadow-card);
	transition: box-shadow var(--duration) var(--ease), transform var(--duration) var(--ease);
}

.bs-card:hover,
.bs-news-card:hover {
	box-shadow: var(--shadow-card-hover);
	transform: translateY(-2px);
}

.bs-card:focus-within,
.bs-news-card:focus-within {
	outline: 2px solid var(--brand-primary);
	outline-offset: 4px;
}

/* A ragged grid of differently proportioned images reads as unfinished. */
.bs-card__media {
	aspect-ratio: 4 / 3;
	overflow: hidden;
	border-radius: var(--radius-card);
	margin-bottom: var(--space-4);
}

.bs-card__media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform var(--duration-slow) var(--ease);
}

.bs-card:hover .bs-card__media img { transform: scale(1.03); }

/* ── Glossary index ────────────────────────────────────────────────── */

.bs-glossary-list { margin: 0; }

.bs-glossary-list dt {
	font-family: var(--font-heading);
	font-size: var(--text-h4);
	font-weight: 700;
	margin-top: var(--space-6);
}

.bs-glossary-list dt a { text-decoration: none; color: var(--blue-700); }
.bs-glossary-list dt a:hover { text-decoration: underline; color: var(--brand-accent-text); }

.bs-glossary-list dd {
	margin: var(--space-1) 0 0;
	color: var(--text-body-color);
}

.bs-news-card__date,
.bs-news-card__source {
	font-size: var(--text-small);
	color: var(--text-muted);
	margin: 0;
}

.bs-news-card__title {
	font-size: var(--text-h3);
	line-height: var(--leading-h3);
	margin: var(--space-2) 0;
	max-width: none;
}

.bs-news-card__title a { text-decoration: none; }
.bs-news-card__title a:hover { color: var(--brand-accent-text); text-decoration: underline; }
.bs-news-card:hover .bs-news-card__title a { color: var(--brand-accent-text); }
.bs-news-card__summary { margin: 0 0 var(--space-3); font-size: var(--text-body); }

/* ===========================================================================
   WIZARD
   ======================================================================== */

.bs-wizard {
	max-width: var(--measure);
	margin-inline: auto;
}

.bs-wizard__step {
	border: 1px solid var(--border-subtle);
	border-radius: var(--radius-card);
	padding: var(--space-8);
	margin-bottom: var(--space-6);
	background: var(--surface);
	box-shadow: var(--shadow-2);
}

.bs-wizard__legend {
	font-family: var(--font-heading);
	font-size: var(--text-h2);
	font-weight: 800;
	line-height: var(--leading-h2);
	color: var(--brand-primary);
	max-width: var(--measure-heading);
}

.bs-wizard__number {
	display: block;
	font-family: var(--font-body);
	font-size: var(--text-small);
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--blue-700);
	margin-bottom: var(--space-2);
}

.bs-wizard__help {
	color: var(--text-muted);
	font-size: var(--text-body);
	max-width: var(--measure-intro);
	margin-bottom: var(--space-6);
}

.bs-wizard__answers { display: grid; gap: var(--space-3); }

.bs-wizard__answer {
	display: flex;
	align-items: flex-start;
	gap: var(--space-3);
	min-height: 48px;              /* comfortable touch target */
	border: 1px solid var(--border-control);
	border-radius: var(--radius-input);
	padding: var(--space-4);
	cursor: pointer;
	transition: border-color var(--duration) var(--ease), background-color var(--duration) var(--ease);
}

.bs-wizard__answer:hover { border-color: var(--border-strong); }

/* Selected is a state, not an action - blue, and doubled as a border plus an
   inset ring so it does not depend on colour alone. */
.bs-wizard__answer:has(input:checked) {
	border-color: var(--blue-700);
	background: var(--surface-muted);
	box-shadow: inset 0 0 0 1px var(--blue-700);
}

.bs-wizard__answer:has(input:focus-visible) {
	outline: 2px solid var(--brand-primary);
	outline-offset: 2px;
}

.bs-wizard__nav {
	display: flex;
	gap: var(--space-3);
	flex-wrap: wrap;
}

.bs-wizard__outcome {
	border: 1px solid var(--brand-primary);
	border-radius: var(--radius-card);
	padding: var(--space-8);
	margin-bottom: var(--space-8);
	background: var(--surface);
	box-shadow: var(--shadow-3);
}

.bs-wizard__outcome h2 { margin-top: 0; }
.bs-wizard__because { font-size: var(--text-small); color: var(--text-muted); }
.bs-wizard__guidance { padding-left: 1.2em; }
.bs-wizard__guidance li { margin-bottom: var(--space-2); }
.bs-wizard__restart { font-size: var(--text-small); }

/* ===========================================================================
   FORMS
   One column, labels above the field, 48px targets, 16px text so iOS does not
   zoom on focus.
   ======================================================================== */

.bs-field,
.fluentform .ff-el-group {
	display: flex;
	flex-direction: column;
	gap: 6px;
	margin-bottom: var(--space-5);
}

.bs-field label,
.fluentform .ff-el-input--label label {
	font-size: var(--text-small);
	font-weight: 600;
	color: var(--text-heading);
}

.bs-field input,
.bs-field select,
.bs-field textarea,
.fluentform input[type='text'],
.fluentform input[type='email'],
.fluentform input[type='tel'],
.fluentform select,
.fluentform textarea {
	min-height: 48px;
	width: 100%;
	background: var(--surface);
	border: 1px solid var(--border-control);
	border-radius: var(--radius-input);
	padding: 12px 16px;
	font-family: var(--font-body);
	font-size: 1rem;
	font-weight: 500;
	line-height: var(--leading-body);
	color: var(--text-body-color);
	transition: border-color var(--duration) var(--ease), box-shadow var(--duration) var(--ease);
}

.bs-field textarea,
.fluentform textarea { min-height: 120px; }

.bs-field input:hover,
.fluentform input:hover,
.fluentform textarea:hover { border-color: var(--border-strong); }

.bs-field input:focus,
.bs-field select:focus,
.bs-field textarea:focus,
.fluentform input:focus,
.fluentform select:focus,
.fluentform textarea:focus {
	border-color: var(--brand-primary);
	box-shadow: 0 0 0 3px rgba(16, 57, 91, 0.15);
	outline: none;   /* replaced by the ring above, not removed */
}

.bs-field [aria-invalid='true'],
.fluentform .ff-el-is-error input {
	border-color: var(--color-error);
}

/* Colour alone is not a message. */
.bs-field__error,
.fluentform .error-text {
	font-size: var(--text-small);
	font-weight: 600;
	color: var(--color-error);
}

.bs-field input:disabled,
.fluentform input:disabled {
	background: var(--surface-muted);
	color: var(--border-control);
}

.fluentform .ff-btn-submit {
	background: var(--brand-accent);
	color: var(--on-accent);
	font-family: var(--font-body);
	font-size: var(--text-button);
	/* Matches .bs-button--primary: this is the same control in a different
	   plugin's markup, and it is the actual end of the funnel. */
	font-weight: 700;
	padding: 14px 28px;
	border: none;
	border-radius: var(--radius-button);
	/* 14px of padding either side of a 15px line box comes to 42px - two short
	   of the touch minimum, on the one control the whole page exists for. */
	min-height: 48px;
}

.fluentform .ff-btn-submit:hover {
	background: var(--brand-accent-hover);
	color: var(--on-accent);
}

/* ===========================================================================
   MOBILE STICKY CTA
   On a 375px screen the hero pushes the CTA below the fold. Two actions only -
   a three-way choice in a 375px bar does not work.
   ======================================================================== */

.bs-mobile-cta {
	position: fixed;
	inset-inline: 0;
	bottom: 0;
	z-index: 50;
	display: grid;
	grid-auto-flow: column;
	grid-auto-columns: 1fr;
	gap: var(--space-2);
	padding: 12px 16px;
	padding-bottom: max(12px, env(safe-area-inset-bottom));
	background: var(--brand-primary);
	box-shadow: 0 -4px 16px rgba(16, 57, 91, 0.18);
	transform: translateY(110%);
	transition: transform var(--duration-slow) var(--ease);
}

.bs-mobile-cta.is-visible { transform: translateY(0); }

.bs-mobile-cta .bs-button { width: 100%; }

@media (min-width: 1024px) {
	.bs-mobile-cta { display: none; }
}

/*
 * The bar is fixed, so it covers whatever the page ends with - on most pages the
 * last line of the footer disclaimer, on the wizard its own Back/Next row. The
 * body class comes from functions.php and is only present on the pages that
 * actually render a bar, so the four that opt out keep their normal footer.
 */
@media (max-width: 1023px) {
	.bs-has-mobile-cta .site-footer {
		padding-bottom: calc(72px + env(safe-area-inset-bottom));
	}
}

/* ===========================================================================
   INSET BOXES ON A PHONE
   A 32px inset is right on a 1120px band and wrong on a 327px screen, where it
   leaves a 251px text column inside a card that is itself already 24px in from
   the edge. Halved below 640px: the boxes still read as inset, and the text
   inside them lands on a single consistent 44px line rather than at 62px.
   ======================================================================== */

@media (max-width: 639px) {
	.bs-cta,
	.bs-trust,
	.bs-wizard__step,
	.bs-wizard__outcome,
	.bs-related,
	.bs-cards .wp-block-group,
	.bs-card,
	.bs-news-card {
		padding: var(--space-5);
	}

	.wp-block-details { padding-inline: var(--space-5); }
}

/* ===========================================================================
   FOOTER
   ======================================================================== */

/* Navy, like the header - the page is bracketed by the brand colour instead of
   fading out into white. The band is painted on .site-footer so it reaches the
   viewport edge; .inside-site-info is a GeneratePress .grid-container and is
   already held to --container-max by the PAGE WIDTH section above. */
/* .site-info as well as .site-footer: GeneratePress paints the copyright strip
   white from its own dynamic CSS, and .site-info is the child, so painting only
   the wrapper left the navy completely covered - with the negative logo, whose
   wordmark is white, invisible on it. */
.site-footer,
.site-footer .site-info,
.site-footer .inside-footer-widgets {
	background: var(--brand-primary);
	color: var(--text-on-inverse);
}

.site-footer .inside-site-info,
.site-footer .copyright-bar {
	text-align: left;
}

/* GeneratePress centres the copyright with display:flex on .inside-site-info, so
   .copyright-bar shrank to the width of its contents and the footer started
   132px inside the page edge. The footer is a full-width composition now and
   takes the whole container, so the columns line up with the article above. */
.site-footer .inside-site-info { display: block; }

.bs-footer { padding-block: var(--space-16) var(--space-12); }

/* Four equal columns on desktop, two at tablet, one on a phone. Equal rather
   than a wider contact column: the address is the shortest content of the four
   and gains nothing from the extra width. */
.bs-footer__cols {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: var(--space-12) var(--space-8);
}

@media (max-width: 899px) {
	.bs-footer__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Two columns all the way down, not one. Stacked in a single column the four
   sections ran 1324px on a 390px phone; in two they run 957px, and the only
   cost is that the longest label - "Which cabinet do I need?" - wraps to two
   lines, which measured 45px and stays comfortably over the 24px target
   minimum. The column gap comes in, because at 155px per column the space
   between them is doing less work than the space inside them. */
@media (max-width: 439px) {
	.bs-footer__cols { gap: var(--space-8) var(--space-4); }

	/* The contact block takes the full row. In a 163px column the address broke
	   after almost every word and the email address ran out through the right
	   edge; across both columns it sets on four clean lines. */
	.bs-footer__col--contact { grid-column: 1 / -1; }
}

/* The email address is one unbroken 27-character token, so it needs an explicit
   licence to break - on a phone it is wider than its own column. */
.bs-footer__contact a { overflow-wrap: anywhere; }

/* Telephone and mailbox sit as close together as the address lines above them:
   they are one block of contact details, not a list of separate links. The
   32px target minimum still applies, so the rows keep their height and lose
   only the space between them.
   Qualified with the parent column class, not just `.bs-footer__contact li`:
   that had the same specificity as the generic `.bs-footer__col li` rule
   below it, so source order let the generic margin win back
   ("wat dichter op elkaar", 2026-09-10 - still 12px apart after the first
   attempt at this). */
.bs-footer__col--contact .bs-footer__contact li {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--space-2);
	margin-bottom: 0;
	line-height: var(--leading-body);
}

/* Same specificity trap as the `li` rule above: `.bs-footer__col a` further
   down sets min-height: 32px at equal specificity, and source order let it
   win the row height back up from the intended line-height. Qualified with
   the column class to actually outrank it. */
.bs-footer__col--contact .bs-footer__contact a { min-height: 0; }

.bs-footer__label { color: var(--blue-100); }

.bs-footer__heading {
	font-size: var(--text-h4);
	color: var(--text-on-inverse);
	margin: 0 0 var(--space-4);
}

.bs-footer__col ul,
.bs-footer__contact {
	list-style: none;
	margin: 0;
	padding: 0;
}

.bs-footer__col li { margin-bottom: var(--space-1); }

/* A list of links is not inline text, so WCAG 2.2's 24px minimum target applies.
   The block padding takes each row to 32px without changing how the column
   looks, because the line box already had the room. */
.bs-footer__col a,
.bs-footer-menu a {
	display: inline-flex;
	align-items: center;
	min-height: 32px;
	color: var(--blue-100);
	text-decoration: none;
}

.bs-footer__col a:hover,
.bs-footer-menu a:hover {
	color: var(--text-on-inverse);
	text-decoration: underline;
}

.bs-footer__col--contact address {
	font-style: normal;
	line-height: var(--leading-body);
	color: var(--blue-100);
	margin-bottom: var(--space-3);
}

.bs-footer__col--contact address strong {
	display: block;
	color: var(--text-on-inverse);
}

/* The bar under the columns: the mark on the left, the statutory text on the
   right. It reads as a separate register from the navigation above it, so it
   gets a rule rather than more space.

   Same four-column grid and the same column gap as .bs-footer__cols above, on
   purpose. The bar used to be its own `auto 1fr` grid, so the legal text began
   wherever the logo happened to end - 24px past the logo's right edge - and that
   point lined up with nothing above it. Sharing the grid means the legal block
   starts exactly on the second column, under "Choosing a cabinet", whatever
   width the logo has. Changed 2026-09-09. */
.bs-footer__bar {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	align-items: start;
	column-gap: var(--space-8);
	row-gap: var(--space-6);
	margin-top: var(--space-12);
	padding-top: var(--space-8);
	border-top: 1px solid var(--blue-700);
}

.bs-footer__bar .bs-logo { grid-column: 1; }
.bs-footer__bar .bs-footer-legal { grid-column: 2 / -1; }

@media (max-width: 899px) {
	.bs-footer__bar { grid-template-columns: 1fr; }

	.bs-footer__bar .bs-logo,
	.bs-footer__bar .bs-footer-legal { grid-column: auto; }
}

/* Marzan Security's mark (200x64 viewBox): 70px tall gives 219px wide, which
   sits inside the first column at every width the four-column grid exists at
   (the column is 284px at the 1232px container). Was 44px when the site's own
   logo stood here as a signature; the publisher's mark is the one place on the
   page that says who Marzan is, so it gets the room. 70px since 2026-09-09.
   width:auto is already on .bs-logo img, so the ratio holds. */
.bs-footer__bar .bs-logo img { height: 70px; }

.bs-footer-legal {
	font-size: var(--text-small);
	line-height: var(--leading-body);
	color: var(--blue-100);
}

.bs-footer-legal p { margin-bottom: var(--space-3); }
.bs-footer-legal p:last-child { margin-bottom: 0; }

.bs-footer-menu {
	list-style: none;
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2) var(--space-5);
	margin: 0 0 var(--space-4);
	padding: 0;
}

.bs-footer-menu a { font-weight: 600; }

.bs-footer-legal a { color: var(--blue-100); text-decoration: underline; }
.bs-footer-legal a:hover { color: var(--text-on-inverse); }

.bs-company { font-size: var(--text-small); color: var(--text-muted); }
.bs-empty { color: var(--text-muted); font-style: italic; }

/* CookieYes puts a floating "manage consent" badge in the bottom-left corner,
   over the page, on every view. The banner itself is the consent mechanism and
   the privacy notice carries the same link, so the badge is hidden: it was
   sitting on top of the content and reading as a stray graphic (2026-09-10).
   Hidden rather than removed - the plugin still runs, and unhiding is one
   line if the badge is ever wanted back. */
.cky-btn-revisit-wrapper { display: none !important; }

/* ===========================================================================
   MOTION
   ======================================================================== */

@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		animation-duration: 0.01ms !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}

	/* Every transform-based hover is removed, not just shortened. */
	.bs-button:hover,
	.bs-card:hover,
	.bs-news-card:hover,
	.bs-cta__button:hover {
		transform: none;
	}

	.bs-card:hover .bs-card__media img { transform: none; }
	.bs-button--tertiary:hover::after { transform: none; }
	.bs-post-card:hover,
	.bs-icon-grid__item:hover,
	.bs-cards .wp-block-group:hover { transform: none; }
	.bs-post-card:hover .bs-post-card__media img { transform: none; }
}

/* ===========================================================================
   PRINT - regulations pages get printed and taken into meetings
   ======================================================================== */

@media print {
	.site-header,
	.site-footer,
	.bs-cta,
	.bs-wizard__nav,
	.bs-related,
	.bs-mobile-cta,
	.bs-skip-link { display: none; }

	.inside-article { max-width: none; padding: 0; }

	.bs-article .entry-content > *,
	.bs-article .entry-content > .wp-block-table,
	.bs-article .entry-content > .bs-table-wrap {
		max-width: none;
		width: 100%;
		margin-left: 0;
		transform: none;
	}

	.wp-block-details { break-inside: avoid; }
	.wp-block-details[open] summary { margin-bottom: var(--space-2); }

	/* Sources have to survive the printer. */
	a[href^='http']::after {
		content: ' (' attr(href) ')';
		font-size: 0.75em;
		color: #555;
	}
}
