/*
 * pm-header — the Poortman site header (WO-16).
 *
 * Compiled to assets/css/components/header.css by src/build-tokens.mjs,
 * which substitutes the breakpoint markers (a comment holding `@bp <name>`)
 * with the pixel value from tokens/tokens.json. Custom properties cannot
 * resolve inside @media conditions and a bare integer is invalid CSS, so the
 * build appends the unit. Never type a breakpoint width here.
 *
 * Every colour is a --pm-color-* token; spacing, motion and font values come
 * from tokens/tokens.json. No hex value and no literal breakpoint width may
 * appear in this file.
 *
 * Variants (WO-16 AC 4): the header element carries only .pm-header; the
 * variant class is emitted on <body> by the body_class filter in functions.php
 * (pm-header--light | pm-header--negative) and keyed off here.
 *
 * Mobile-first (WO-16 AC 7): the hamburger is the default; the desktop menu
 * appears at the laptop breakpoint and above.
 */

/*
 * The bar spans the viewport. The scaffold stylesheet (style.css) constrains
 * body to a centred 72rem column with --pm-space-8 padding; the header escapes
 * that box with the standard full-bleed technique and re-aligns its own
 * content to the body content box, so logo, menu and CTA line up with the page
 * content at every width. The negative top margin cancels the scaffold body's
 * padding-top (--pm-space-8) so the bar reaches the viewport top. Revisit both
 * when the scaffold body styles are replaced by the layout epic.
 */
.pm-header {
	box-sizing: border-box;
	width: 100vw;
	margin-inline: calc(50% - 50vw);
	margin-top: calc(-1 * var(--pm-space-8));
	padding-inline: calc((100vw - 100%) / 2);
	background-color: var(--pm-color-surface);
	border-bottom: 1px solid var(--pm-color-divider);
	color: var(--pm-color-ink);
	font-family: var(--pm-font-family-base), system-ui, sans-serif;
}

/*
 * Layout (WO-16 AC 3, mechanism corrected by WO-77 AC 1 — 
 * 16-header.json#measured.nav.alignment). The frame centres the menu on the
 * TRACK between the logo's right edge and the right cluster's left edge —
 * not on the container's midpoint, which the earlier `1fr auto 1fr` grid
 * produced and which does not move when the right cluster's width changes.
 * `auto 1fr auto` puts logo and right cluster in the auto tracks and the nav
 * in the middle 1fr track, centred on that track (its midpoint is the
 * midpoint between the two edges either side — the track gaps cancel).
 * Explicit column placement keeps the controls on the right when the menu is
 * display:none below the laptop breakpoint.
 *
 * Min-height is 64px below the laptop breakpoint — WO-19 AC 1,
 * 19-mobile-nav.json#measured.nav_bar.height — and 80px at laptop and above,
 * the ClickUp card's figure. Both are component heights typed here like the
 * button's 48px reference height in button.css (WO-09) — no size token
 * exists for component heights. The mobile panel hangs from the same 64
 * (mobile-menu.css's top).
 */
.pm-header__inner {
	display: grid;
	grid-template-columns: auto 1fr auto;
	align-items: center;
	gap: var(--pm-space-2);
	min-height: 64px; /* WO-19 — 19-mobile-nav.json#measured.nav_bar.height */
}

.pm-header__logo {
	grid-column: 1;
	justify-self: start;
	display: inline-flex;
	align-items: center;
	/* The scaffold stylesheet colours every anchor --pm-color-accent; the logo
	   is not a text link — it inherits the bar's colour (ink on light, surface
	   on negative) so the wordmark reads dark-on-light and light-on-dark. */
	color: inherit;
}

/*
 * The wordmark: 119 x 12 below the laptop breakpoint (WO-19 AC 1 —
 * 19-mobile-nav.json#measured.nav_bar.logo, "the header wordmark scaled"),
 * its designed export size 137 x 14.453 (16-header.json#measured.logo) at
 * laptop and above. Width drives; height auto keeps the wordmark's ratio
 * (119/137 x 14.453 = the frame's 12). WO-74 retired the laptop-breakpoint
 * enlargement, which made this asset 66% too tall.
 */
.pm-header__logo svg {
	display: block;
	width: 119px;
	height: auto;
}

/* Mobile-first: the desktop menu is hidden by default (WO-16 AC 7). */
.pm-header__nav {
	grid-column: 2;
	display: none;
}

/*
 * The right cluster. Below the laptop breakpoint (WO-19 AC 1) it shows the
 * language switcher then, gap 24, the toggle — the CTA is hidden. At laptop
 * and above the frame's 48px gap (16-header.json#measured.right_cluster.gap)
 * — switcher to CTA — returns with the CTA.
 */
.pm-header__right {
	grid-column: 3;
	justify-self: end;
	display: flex;
	align-items: center;
	gap: var(--pm-space-6); /* 24 — 19-mobile-nav.json#measured.nav_bar.right */
}

/*
 * The header CTA hides below the laptop breakpoint (WO-19 AC 1 — the bar is
 * logo, switcher, burger; the Contact button is the panel's full-width CTA
 * there) and returns at laptop. Scoped to the right cluster — the only
 * pm-button in the bar.
 */
.pm-header__right .pm-button {
	display: none;
}

/*
 * Language-switcher slot (WO-16 contract, shaped by WO-77 AC 1 —
 * 16-header.json#measured.right_cluster.language). WO-19: the switcher shows
 * at EVERY width now — the frame's 375 bar is logo, switcher, burger
 * (19-mobile-nav.json#measured.nav_bar.right) — so the frame's `▾ ENGLISH`
 * treatment (chevron then native label, uppercase 14px, 4px apart) is the
 * base state. The 14px is typed here like every other header font size — no
 * font-size token exists.
 */
.pm-header__lang {
	display: inline-flex;
	align-items: center;
	gap: var(--pm-space-1);
	/* WO-82 AC 1 — the language trigger's row box: the frame's 8px block
	   padding on the switcher like the menu items' (16-header.json#reaffirmed),
	   the same 32px row = the 16px line + 2×8, centred in the 80px bar. The
	   slot is the switcher row in BOTH render states — the fallback (this
	   stack: chevron + label directly in the slot) and the control
	   (lang.php's .pm-lang > button inside it) — so the padding lives here
	   once; padding the button too would double the row to 48px. The button
	   inherits the 16px line-height through its font: inherit
	   (lang-switcher.css) and keeps its own 0 padding. The popover's 8px
	   offset (--pm-space-2) is anchored to .pm-lang — the trigger's content
	   box — so it is unchanged by this padding (lang-switcher.css). */
	padding: var(--pm-space-2) 0;
	line-height: 16px;
	color: inherit;
	font-size: 0.875rem;
	font-weight: var(--pm-font-weight-400);
	text-transform: uppercase;
	/* WO-78 — 16-header.json#measured.right_cluster.language.white_space:
	   the switcher label is nowrap in the frame too, so its longest
	   realistic value ("NEDERLANDS") can never take a second line. */
	white-space: nowrap;
}

.pm-header__lang svg {
	display: block;
}

/*
 * The menu toggle (WO-16 AC 7, wired by WO-19): the mobile navigation
 * panel's trigger. The 44px box is the WCAG 2.5.5 touch target, not a token
 * scale step; the frame's 24px icon (19-mobile-nav.json#measured.nav_bar
 * .right) sits centred inside it. The icons draw with currentColor (the
 * logo.php convention); the swap is positional — burger first, close second,
 * the accordion's convention — driven by aria-expanded, which mobile-menu.js
 * mirrors.
 */
.pm-header__toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	padding: 0;
	border: 0;
	background: none;
	color: inherit;
	cursor: pointer;
}

.pm-header__toggle svg {
	display: block;
	width: 24px; /* the frame's icon — 19-mobile-nav.json#measured.nav_bar.right */
	height: 24px;
}

.pm-header__toggle svg:nth-child(2) {
	display: none;
}

.pm-header__toggle[aria-expanded="true"] svg:nth-child(1) {
	display: none;
}

.pm-header__toggle[aria-expanded="true"] svg:nth-child(2) {
	display: block;
}

/* The desktop menu shows at laptop and above; the toggle and the CTA-hiding
   retire. WO-77 AC 1: the nav centres its items in the middle 1fr track
   (16-header.json#measured.nav.alignment), and the right cluster's gap is
   the frame's 48px (16-header.json#measured.right_cluster.gap) — switcher
   to CTA. */
@media (min-width: 1024px) {
	.pm-header__inner {
		gap: var(--pm-space-4);
		min-height: 80px; /* the ClickUp card's figure (WO-16) */
	}

	.pm-header__logo svg {
		width: 137px; /* the designed export size — 16-header.json#measured.logo */
	}

	.pm-header__nav {
		display: flex;
		justify-content: center;
	}

	.pm-header__right {
		gap: var(--pm-space-12);
	}

	.pm-header__right .pm-button {
		display: inline-flex;
	}

	.pm-header__toggle {
		display: none;
	}
}

/*
 * The menu (WO-16 AC 5). WordPress's own current-menu-item class carries the
 * selected state — no custom detection here. Links inherit the header colour,
 * so the negative variant recolours them by changing one property on the bar.
 */
.pm-header__menu-list {
	display: flex;
	align-items: center;
	/* WO-78 AC 3 — measured under the Dutch locale at the laptop breakpoint
	   (1024, the first width at which the desktop menu is shown): with the
	   items nowrap, 32px is the largest spacing-scale gap at which the four
	   Dutch labels, the logo, the switcher and the CTA all sit on one line
	   with the track still centred — 25.7px of slack; 48px overflows.
	   Replaces WO-16's unmeasured half step (24px), chosen before the items
	   carried the frame's nowrap. */
	gap: var(--pm-space-8);
	margin: 0;
	padding: 0;
	list-style: none;
}

/* WO-77 AC 6, amended by WO-78 AC 2 — 16-header.json#measured.nav.gap: the
   frame's spacing token "block 96" between the items at the frame's 1440
   width. The frame is English; under the Dutch locale with nowrap the 96px
   gap overflows the 1088px content box by 38px, so per WO-78 AC 2 the gap
   steps down the scale to 72px — the largest value at which the four Dutch
   labels, the logo, the switcher and the CTA sit on one line with the track
   still centred (measured slack 33.7px). The type size is untouched. */
@media (min-width: 1440px) {
	.pm-header__menu-list {
		gap: var(--pm-space-18);
	}
}

/*
 * WO-82 AC 1 — every item's <li> is one flex row, centring its children.
 * WO-18 gave only the Products item this (mega-menu.css's li:has rule); the
 * plain items stayed list-item, whose 18px strut line box (28.8px at the
 * scaffold's 1.6 leading) sat their labels 0.8px below the bar's centre —
 * the 1.3px step against the flex-boxed Products and language items. The
 * links and the mega toggle inside carry the frame's 32px row box
 * themselves; the li only centres them.
 *
 * WO-83 — direct children only. The descendant form reached INTO the mega
 * panel (it renders inside the Products <li>), flexing the panel's card
 * <li>s into 9 × 16 px slivers (83-products-index.json#defect); the bar's
 * own items are the list's direct children, and nothing else in it is.
 */
.pm-header__menu-list > li {
	display: flex;
	align-items: center;
}

/*
 * WO-83 — the label rule, SPLIT. The old single `.pm-header__menu-list a`
 * rule (WO-74/78/82) styled the bar's links; as a descendant selector it
 * also matched the mega panel's card links, whose inherited nowrap and
 * 16px strut collapsed them to slivers (83-products-index.json#defect).
 * The TYPE declarations (colour, case, size, weight, leading, nowrap) are
 * shared with the Products button's label span — the button is the item's
 * row now, and its label is the links' type. The BOX declarations stay on
 * the links' branch alone: the button carries its own box (mega-menu.css),
 * and a padded span inside a padded button would make the row 48px and
 * drop the open-state line 8px.
 */
.pm-header__menu-list > li > a,
.pm-header__mega-label {
	/* WO-74 AC 7 — the frame's Navigation instances: Roboto Regular 14px
	   uppercase, Volcanic Ink, 8px vertical padding. No font-size token
	   exists, so the 14px is typed here like the header's 80px bar height.
	   The colour is the bar's own --pm-color-ink via inherit — stated on the
	   bar, not here, so the negative variant still recolours the menu by
	   changing one property. */
	color: inherit;
	/* WO-78 AC 1 — 16-header.json#measured.nav.white_space: the frame's own
	   setting, whitespace-nowrap on every Navigation instance. The Dutch
	   labels run 20–30% longer than the frame's English ones (MISTAKES.md
	   §E4); without it the grid squeezed "Voor architecten" and "Over ons"
	   onto two lines. A label that no longer fits is the gap's problem
	   (WO-78 AC 2–3), never the type size's. */
	white-space: nowrap;
	/* WO-82 AC 1 — the 16px line of the frame's 32px row (16px line + 2×8
	   block padding). The leading tokens are unitless ratios and none holds
	   the frame's 16px line, so it is typed here like the 14px. */
	line-height: 16px;
	font-size: 0.875rem;
	font-weight: var(--pm-font-weight-400);
	text-transform: uppercase;
}

/* The links' BOX (WO-82 AC 1): the frame's 32px row = the 16px line + 2×8
   block padding (16-header.json#reaffirmed — the Navigation instances' 8px
   block padding), centred in the 80px bar. inline-flex + align-items:
   center centres the label. Replaces the inline box's padding-block, whose
   28.8px strut line box put the label 0.8px below the bar's centre. */
.pm-header__menu-list > li > a {
	display: inline-flex;
	align-items: center;
	padding: var(--pm-space-2) 0;
	text-decoration: none;
	/* WO-16 AC 5 — hover changes colour only, over --pm-motion-duration-fast.
	   The transition-property list is exactly `color` (test plan 5a). WO-83:
	   the Products button declares the same transition on itself
	   (mega-menu.css) — the label inherits colour and the chevron is
	   currentColor, so both follow the button's animated value as one
	   unit; the label declares no transition of its own. */
	transition: color var(--pm-motion-duration-fast) var(--pm-motion-ease-standard);
}

/*
 * TODO(open-question): hover and selected treatment is not verifiable from the
 * cited frames (WO-16 AC 6, open question 2 for Eleonor). Proposal: hover,
 * keyboard focus and the current item all take --pm-color-accent text and
 * nothing else. On the negative variant Terra Sienna on Volcanic Ink measures
 * ~3.4:1 — below AA for text; flagged to the designer under the same question.
 *
 * WO-83 — the Products button joins hover and keyboard focus: the label
 * (color: inherit) and the chevron (currentColor) recolour as one unit,
 * exactly as the links' hover does. The button's colour transition is its
 * own (mega-menu.css, the file the button's base rule lives in). The
 * current-item branch stays links-only — the Products item is not a link,
 * and /producten/ redirects, so it is never the current page.
 */
.pm-header__menu-list > li > a:hover,
.pm-header__menu-list > li > a:focus-visible,
.pm-header__menu-list > .current-menu-item > a,
.pm-header__mega-toggle:hover,
.pm-header__mega-toggle:focus-visible {
	color: var(--pm-color-accent);
}

/*
 * WO-18 AC 3 — the Products item's open state (the trigger's base styles
 * and the panel live in mega-menu.css; this is the open state only). The
 * frame's open item carries a 1px bottom border in the label colour
 * (18-mega-menu.json#measured.trigger), drawn here as an inset shadow so
 * the item's box never grows — no layout shift on open — and the chevron
 * rotates. The class is toggled by mega-menu.js on the item (li).
 *
 * WO-83 — the button IS the item's row now, so the open line extends to
 * it: the inset line runs under chevron + label as one unit. Re-scoped to
 * direct children with the rest of the list's selectors (the panel renders
 * inside the open item; a descendant form would reach into it).
 */
.pm-header__menu-list > .pm-header__item--open > a,
.pm-header__menu-list > .pm-header__item--open > .pm-header__mega-toggle {
	box-shadow: inset 0 -1px 0 currentColor;
}

.pm-header__item--open .pm-header__mega-toggle svg {
	transform: rotate(180deg);
}

/*
 * WO-81 — the header's own uppercase rule for the CTA (WO-74 AC 8) is
 * gone: the component now renders its label uppercase Regular itself
 * (09-buttons.json#contradictions[-1]), so the header CTA is unchanged
 * visually with no rule of its own. Its colour needs nothing here on the
 * light bar either: the anchor carries pm-button pm-button--primary, so
 * WO-74's Volcanic Ink fill and Ochre hover apply.
 *
 * WO-77 AC 5a's below-tablet CTA padding rule is gone with WO-19: the CTA
 * hides below the laptop breakpoint (the panel carries the full-width
 * Contact button there), so a padding rule for the hidden instance is dead
 * CSS.
 */

/*
 * WO-16 AC 10 — the Terra Sienna focus ring, WO-09's convention (2px outline,
 * 2px offset), on every focusable header element. The CTA inherits the same
 * ring from button.css.
 */
.pm-header a:focus-visible,
.pm-header__toggle:focus-visible {
	outline: 2px solid var(--pm-color-accent);
	outline-offset: 2px;
}

/*
 * WO-16 AC 4 — the negative variant. The class is emitted on <body>; the
 * header element itself carries only .pm-header. Surface and ink swap and the
 * divider line disappears.
 */
body.pm-header--negative .pm-header {
	background-color: var(--pm-color-ink);
	border-bottom-color: transparent;
	color: var(--pm-color-surface);
}

/*
 * WO-77 AC 6a — 27-hero.json#measured.navigation_bar.cta: on the negative
 * header the primary button is INVERTED — Blush Linen fill (--pm-color-
 * surface) with Volcanic Ink text, hovering to Ochre text on the same fill.
 * This replaces the uniform-primary reading WO-74 documented above, which
 * was a guess made before the hero frame was opened. (The contact page frame
 * shows a third, outlined variant — recorded as a contradiction in
 * 38-contact.json; the hero's is the one built.)
 *
 * WO-19 fix round — scoped to the right cluster, the bar's own CTA (the
 * show/hide rules' scope above). The original `.pm-header` descendant also
 * matched the mobile panel's Contact button: the panel renders inside the
 * <header> element (header.php), so on the negative variant its button
 * inherited this Blush Linen fill and vanished into the panel's own Blush
 * Linen surface. The panel's CTA is the plain system primary on BOTH
 * variants (WO-19 AC 2/5 — mobile-menu.css restyles nothing but the width);
 * only the bar's CTA inverts.
 */
body.pm-header--negative .pm-header__right .pm-button--primary {
	background-color: var(--pm-color-surface);
	border-color: var(--pm-color-surface);
	color: var(--pm-color-ink);
}

body.pm-header--negative .pm-header__right .pm-button--primary:hover {
	background-color: var(--pm-color-surface);
	border-color: var(--pm-color-surface);
	color: var(--pm-color-accent-hover);
}

/* Reduced motion neutralises the colour transition (MEMORY.md §3.5).
   WO-83: direct children — the button's own transition is neutralised in
   mega-menu.css's reduced-motion block, the file its base rule lives in. */
@media (prefers-reduced-motion: reduce) {
	.pm-header__menu-list > li > a {
		transition: none;
	}
}

/*
 * WO-38 — the overlay variant (the contact page): the photo shows behind
 * the header. The body carries BOTH pm-header--negative and pm-header--overlay
 * (functions.php emits the pair for the allowlisted `negative-overlay` meta
 * value), so this block stands LAST: at equal specificity source order
 * decides, and the transparent background must win over the negative bar's
 * ink fill. The bar leaves the flow — absolute at the viewport top, above
 * the content — and drops the negative top margin: an absolute bar needs
 * top: 0 and no margin (the base rule's margin-top cancels the scaffold
 * body's --pm-space-8 padding-top, which an in-flow bar needs and an
 * absolute one must not). The page's first section then carries the header
 * band as its own padding-top and pulls itself up by --pm-space-8
 * (contact.css). The homepage stays `negative` — in flow, opaque, untouched.
 *
 * WO-94 — the overlay bar's own gutter. The base rule's
 * `calc((100vw - 100%) / 2)` re-alignment works only while the bar is IN FLOW
 * inside the scaffold's narrower 72rem column. Absolute with left: 0/right: 0
 * makes `100%` the viewport, so the expression evaluates to ZERO and this bar
 * — the contact page's — runs edge to edge at EVERY width: 375, 768, 1024,
 * 1280 and 1440 alike. That is the defect the client reported ("the header in
 * contact page is stretch"), and it is not a 1440-only phenomenon, so a
 * desktop-up rule alone would not reach the width they may be viewing at
 * (counter-review round 2, objection B).
 *
 * --pm-space-8 (32) here, unscoped, is the BELOW-desktop value: it restores
 * 19-mobile-nav.json#measured.nav_bar.padding ("0 32") at 375 and gives the
 * same 960px content box at 1024 that the in-flow bar already has, where
 * WO-78 AC 3 measured 25.7px of slack — so it cannot overflow the nav. From
 * the desktop marker up the shared --pm-space-24 replaces it, in the WO-94
 * block after the stuck rule below; that block must name THIS selector too,
 * because `.pm-header` alone is (0,1,0) and would lose to this (0,2,1) rule
 * at every width. A media query changes no specificity.
 */
body.pm-header--overlay .pm-header {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	z-index: 10; /* above the page content — the photo and both panels */
	margin-top: 0;
	padding-inline: var(--pm-space-8);
	background-color: transparent;
}

/*
 * WO-93 AC 1 — the overlay header's 40px inset. On the contact frame the nav
 * bar floats 40px below the top of the photo: the Header wrapper carries
 * pt-40 (93-contact-spacing.json#measured.header_block.padding_top, node
 * 90:4212). 16-header.json measured the bar ALONE — 1440 x 80, no inset —
 * which is why every in-flow page is correct as built and nothing but the
 * overlay variant moves here. The contact page is the only overlay page
 * today (data/site-structure.json holds exactly one `negative-overlay`), so
 * this selector is the narrowest place the inset can live.
 *
 * TODO(open-question): whether the inset belongs to the overlay variant
 * generally or to the contact page specifically; owner Eleonor
 * (93-contact-spacing.json#open_questions). Built on the variant — one
 * selector to change if she says otherwise.
 *
 * `top`, NOT `padding-top` (counter-review objection 4). padding-top would
 * inflate the header's padding box, which is the mega panel's containing
 * block: mega-menu.css anchors the panel at `top: calc(100% + …)` on
 * .pm-header, so the panel would drop to 48px below the bar instead of the
 * frame's 8 — and the same padding would survive into the stuck state,
 * where the bar is a 64px border-box and 40px of padding inside it crushes
 * the content. `top` moves the whole bar down as a unit and leaves both
 * intact.
 *
 * SOURCE ORDER IS LOAD-BEARING HERE: `body.pm-header--stuck .pm-header`
 * below re-sets `top: 0` at the same (0,2,1) weight, so this block must
 * stand BEFORE it — the stuck bar stays flush to the viewport top (AC 11).
 * A media query changes nothing about specificity; only position in the
 * file decides.
 *
 * From the desktop breakpoint up only (counter-review objection 5): below
 * it the mobile bar is `position: fixed` and .pm-mobile-menu opens at
 * `top: 64px` — the bar's own height, typed there — so an inset at those
 * widths would paint the open panel over the bottom 40px of the bar, and
 * AC 7 requires 375 and 768 unchanged. The frame gives no value below 1440.
 */
@media (min-width: 1280px) {
	body.pm-header--overlay .pm-header {
		top: 40px;
	}
}

/*
 * ============================================================================
 * WO-17 — the sticky header on scroll. A PROPOSAL for Eleonor: ticket 17 is
 * not designed, and every value marked TODO(open-question) below is hers to
 * adjust after seeing it (docs/design/frames/17-sticky-header.json#proposal).
 *
 * Mechanism: the bar never leaves the flow here. sticky-header.js toggles
 * body.pm-header--stuck — the only hook other CSS may use (WO-17 AC 6) —
 * when the stick sentinel leaves the viewport upward, and drops it when the
 * hysteresis sentinel (40px higher) re-enters, so the threshold cannot
 * flicker. While stuck the bar is fixed and the body carries the spacer.
 * This block stands after the overlay block on purpose: at equal
 * specificity source order decides, and stuck must win over absolute.
 * ============================================================================
 */

/*
 * TODO(open-question): the stuck height is 64 — the mobile bar's own height
 * (19-mobile-nav.json#measured.nav_bar.height) — so one rule serves every
 * width; proposal, owner Eleonor. Anchors reserve the same below the stuck
 * bar (WO-17 AC 5). Typed here like the bar's own 64/80 component heights —
 * no size token exists for them.
 */
:root {
	--pm-header-stuck-height: 64px;
}

/* WO-17 AC 5 — an anchor target lands below the stuck bar, never under it. */
html {
	scroll-padding-top: var(--pm-header-stuck-height);
}

:target {
	scroll-margin-top: var(--pm-header-stuck-height);
}

/*
 * The scroll sentinels (WO-17). Direct children of <body>, outside the
 * header element — a positioned ancestor would capture them once the bar
 * goes fixed. 1x1, non-interactive, visibility:hidden (IntersectionObserver
 * is geometry-based and still sees them; display:none would not).
 *
 * TODO(open-question): the thresholds are the proposal's — owner Eleonor.
 * One viewport down on the homepage (past the hero), 120px everywhere else;
 * the hysteresis sentinel sits 40px above each stick point, which is what
 * makes the unstick fire 40px early (sticky-header.js).
 */
.pm-header-sentinel {
	position: absolute;
	top: 120px;
	width: 1px;
	height: 1px;
	pointer-events: none;
	visibility: hidden;
}

.pm-header-sentinel--unstick {
	top: 80px; /* 120 − the 40px hysteresis */
}

body.home .pm-header-sentinel {
	top: 100vh; /* past the hero */
}

body.home .pm-header-sentinel--unstick {
	top: calc(100vh - 40px);
}

/*
 * The stuck spacer (WO-17 AC 2): the bar's flow footprint was its height —
 * its negative top margin cancels the scaffold body's padding-top — so the
 * body's padding-top becomes exactly the bar's own height
 * (--pm-header-height, measured by sticky-header.js) and the first section
 * keeps its position to the pixel. CLS 0 across the transition.
 */
body.pm-header--stuck {
	padding-top: var(--pm-header-height, 0px);
}

/*
 * The stuck bar (WO-17 AC 1). The in-flow full-bleed technique at the top of
 * this file does not survive fixed: the containing block becomes the
 * viewport, so the negative top margin and the margin-inline zero out and
 * the content padding re-derives from the scaffold body's centred 72rem
 * column — the same offset the in-flow calc((100vw - 100%) / 2) produced
 * against the body's content box (border-box: min(72rem, 100vw) minus two
 * --pm-space-8, centred, plus the padding). Same coupling to the scaffold
 * body styles as the base rule — revisit both with the layout epic.
 *
 * The background is NOT restated here: the variant's own bar colour keeps
 * applying — surface from the base rule, ink from the negative rule (WO-17:
 * the variant's own bar colour). The border-bottom is untouched for the
 * same reason: as the bar has today.
 *
 * NO resting transform (WO-17 fix round, AC4): the mobile panel is a
 * position:fixed child of the header element (header.php), and any non-none
 * transform on an ancestor becomes the containing block for fixed
 * descendants — the panel's top: 64px / bottom: 0 would resolve against the
 * bar's own 64px box and collapse to a sliver. The slide in therefore runs
 * from the hidden state's translateY(-100%) to `none` — `none` interpolates
 * as the identity, so the transition plays exactly as before and the bar
 * at rest carries no transform at all. The transform exists only while
 * pm-header--hidden holds (the rule below).
 *
 * The 64px is the BAR's own height (border-box, so the rendered bar is
 * exactly the stuck height and the mobile panel's top sits flush), not the
 * inner's — see the centring block below for why the inner must keep its
 * in-flow height.
 *
 * TODO(open-question): the shadow — 0 4px 24px of texte @12, the shadow-soft
 * role in tokens.json — is the proposal's; owner Eleonor.
 */
body.pm-header--stuck .pm-header {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	height: var(--pm-header-stuck-height);
	z-index: 10; /* the overlay variant's own figure — above the page content */
	margin-top: 0; /* the in-flow negative margin is dead under fixed */
	margin-inline: 0;
	padding-inline: calc((100vw - min(72rem, 100vw)) / 2 + var(--pm-space-8));
	box-shadow: 0 4px 24px var(--pm-color-shadow-soft);
	transition: transform var(--pm-motion-duration-base) var(--pm-motion-ease-standard);
}

/*
 * ============================================================================
 * WO-94 — the header's gutter is the DESIGN's 96 from the desktop marker up,
 * on every variant. 16-header.json#measured.padding_sides = 96 (node 32:1004,
 * reaffirmed 2026-09-25); 93-contact-spacing.json#measured.header_block
 * .nav_bar.padding_sides = 96 (node 90:4211). The value is never typed: it is
 * var(--pm-space-24), the same token the footer (footer.css:226), the contact
 * panels (contact.css:43) and the mega panel (mega-menu.css:134-135) already
 * sit at — so bar, footer, panels and panel agree from one source of truth.
 *
 * WHY THIS BLOCK EXISTS INSTEAD OF AN EDIT IN PLACE, and why it stands HERE:
 * the two expressions it replaces are the base bar's `calc((100vw - 100%) / 2)`
 * (top of this file) and the stuck bar's
 * `calc((100vw - min(72rem, 100vw)) / 2 + var(--pm-space-8))` (the rule
 * directly above). Both compute 32 at 1024, 96 at 1280, 176 at 1440 and 416 at
 * 1920 — they agree with each other at every width, and both are wrong above
 * 1280 because they are pegged to the scaffold body's centred 72rem column
 * (style.css:21), not to the design.
 *
 * - Editing either declaration IN PLACE would change the gutter below 1280 as
 *   well. 1280 is the only safe floor: forcing 96 at 1024 takes 128px out of a
 *   nav row that WO-78 AC 3 measured with 25.7px of slack under Dutch labels
 *   and `nowrap` — an overflow of about 102px in the 1024–1126 band.
 * - Putting these declarations in the file's OTHER `@bp desktop` block (the
 *   WO-93 overlay inset, above) would place them BEFORE the stuck rule at
 *   equal specificity — (0,2,1) both — so the stuck rule would win and the
 *   stuck bar would still render 176 at 1440, with no error and no build
 *   warning. SOURCE ORDER is what makes this block win, which is the same
 *   reason the file forbids reordering anything else.
 * - The stuck block above is therefore NOT folded into anything: its `top: 0`
 *   and the overlay block's `top: 40px` (WO-93) sit at equal specificity and
 *   are ordered on purpose. Merging them would put the stuck bar 40px down the
 *   contact page and break WO-17 AC 11.
 *
 * All three selectors are named because specificity, not the media query,
 * decides: `.pm-header` is (0,1,0) and the two variant rules are (0,2,1), so
 * the bare class alone would not reach the overlay or the stuck bar. Below
 * this marker every variant keeps exactly what it has — a no-op at 1280 for
 * the three in-flow pages, which is the proof the breakpoint is right — except
 * the overlay bar, which takes --pm-space-8 unscoped above (its gutter was 0 at
 * every width).
 *
 * Nothing else about the header moves: not WO-93's 40px inset, not the stuck
 * bar's 64px height or its behaviour, not the negative variant's colours, not
 * the mega menu's anchoring, the language switcher, the CTA or the sentinel.
 * The scaffold body column (style.css:21) is ticket 08's to retire — this
 * block only stops the header depending on it.
 * ============================================================================
 */
@media (min-width: 1280px) {
	.pm-header {
		padding-inline: var(--pm-space-24);
	}

	body.pm-header--overlay .pm-header {
		padding-inline: var(--pm-space-24);
	}

	body.pm-header--stuck .pm-header {
		padding-inline: var(--pm-space-24);
	}
}

/*
 * The negative bar — and the overlay pair, whose body carries the negative
 * class too — keeps its own ink when stuck: the overlay's transparent fill
 * must not survive the fix (WO-17: stuck → fixed, ink background). Higher
 * specificity than the overlay rule above, so source order is not
 * load-bearing here.
 */
body.pm-header--stuck.pm-header--negative .pm-header {
	background-color: var(--pm-color-ink);
}

/*
 * WO-17 fix round, AC2 — the content RECENTRES BY TRANSFORM, not by layout.
 * The first round shrank the inner itself (min-height 80 → 64), which moved
 * every centred child 8px IN LAYOUT on the stick frame; the Layout
 * Instability API recorded exactly that (the stick-time entry the logo scale
 * alone could not remove — measured 0.0000126 with the width snap gone). The
 * inner therefore keeps its in-flow height (80px at laptop and above — the
 * base rule; the bar's 64px box comes from the bar's own height above), and
 * the content recentres by translateY((stuck height − 80) / 2) — a transform,
 * which the API exempts. The 80px is the inner's own laptop min-height above;
 * the calc re-derives if Eleonor adjusts the stuck height.
 *
 * The transform sits on the CONTENT PIECES ONLY — the logo, the right
 * cluster, and each menu link and mega chevron — NEVER on the inner or the
 * nav: the mega panel (WO-18) renders inside the Products item, and a
 * transform anywhere up its ancestor chain would make that element the
 * panel's containing block, moving its 8px-below-the-bar anchor (mega-menu.css).
 * The panel is a sibling of the links, so the links are safe to move.
 *
 * The inner's layout box (80px) overflows the 64px bar by the 16px the
 * shrink saved; it is transparent, and pointer-events: none keeps that
 * strip below the bar click-through to the page — the interactive pieces
 * opt back in. Below the laptop breakpoint the bar is already the stuck
 * height and nothing here applies.
 */
@media (min-width: 1024px) {
	body.pm-header--stuck .pm-header__inner {
		pointer-events: none;
	}

	body.pm-header--stuck .pm-header__logo,
	body.pm-header--stuck .pm-header__nav,
	body.pm-header--stuck .pm-header__right {
		pointer-events: auto;
	}

	body.pm-header--stuck .pm-header__logo,
	body.pm-header--stuck .pm-header__right,
	/* WO-83: direct children — the descendant form would lift every card in
	   the open panel once the cards are full-size. The mega toggle branch
	   is the button itself, still the item's row. */
	body.pm-header--stuck .pm-header__menu-list > li > a,
	body.pm-header--stuck .pm-header__mega-toggle {
		transform: translateY(calc((var(--pm-header-stuck-height) - 80px) / 2));
	}
}

/*
 * WO-17 fix round, AC2 — the wordmark scales as a TRANSFORM, never a width.
 * The first round's `width: 119px` moved the svg's own box (it is vertically
 * centred in the anchor) and the nav track with it, and the Layout
 * Instability API recorded exactly that as a layout shift (CLS 0.0000106,
 * source svg). A transform moves nothing in layout, so the shift is gone.
 * The factor is the mobile logo width over the desktop export width —
 * 119 / 137 (19-mobile-nav.json#measured.nav_bar.logo over
 * 16-header.json#measured.logo) — so the rendered size is the mobile bar's
 * 119 × 12; the left origin keeps the wordmark's left edge on the content
 * edge, where both the in-flow bar and the mobile bar have it. Scoped to
 * laptop and above: below it the logo is already 119 and sticking changes
 * nothing. The transition rides the same --pm-motion-duration-base as the
 * bar's slide; reduced motion drops it below.
 */
@media (min-width: 1024px) {
	body.pm-header--stuck .pm-header__logo svg {
		transform: scale(calc(119 / 137));
		transform-origin: left center;
		transition: transform var(--pm-motion-duration-base) var(--pm-motion-ease-standard);
	}
}

/*
 * The exit's held state (sticky-header.js): the bar keeps position fixed —
 * the spacer stays up with it — while it slides to -100%; the classes drop
 * on transitionend, where the hysteresis has kept the in-flow bar above the
 * viewport, so there is no double bar and no vanish.
 */
body.pm-header--stuck.pm-header--hidden .pm-header {
	transform: translateY(-100%);
}

/* Reduced motion neutralises the slide and the wordmark's scale transition
   (MEMORY.md §3.5) — sticky-header.js drops the classes synchronously, so
   the transitionend path never runs. The scale itself still applies (AC 1's
   119 × 12 wordmark holds without motion); it simply snaps. */
@media (prefers-reduced-motion: reduce) {
	body.pm-header--stuck .pm-header,
	body.pm-header--stuck .pm-header__logo svg {
		transition: none;
	}
}
