/* =============================================================================
   ArneIron — design system stylesheet (assets/css/ds.css)
   =============================================================================
   The single source of truth for rendered component CSS. Mobile-first. Locks
   onto the `theme.json` presets via the `var(--wp--preset--*)` cascade so a
   restyle re-points a preset, never refactors markup.

   Budget (perf skill): ≤ 14 KB gzipped. Mobile-first. No web fonts.
   ========================================================================== */

/* ---- 1. Token convenience aliases ------------------------------------------ */

/*  STYLE-VARIATION CONTRACT — read before changing a line in this block.
 *
 *  theme.json declares TWO colour families:
 *
 *    A "ink/bg"       bg ink ink-2 ink-3 accent-strong on-accent link
 *                     link-hover success warning danger
 *    B "base/contrast" base base-2 surface-3 border-strong muted subtle
 *                     contrast contrast-2 accent-contrast on-surface
 *    shared           surface surface-2 border accent
 *
 *  Every style variation in styles/ overrides family B (that is the WordPress
 *  convention the variation format is built around). Family A slugs stay at
 *  their theme.json defaults under EVERY variation, because a variation palette
 *  is applied at the *user* origin and WordPress still emits the theme-origin
 *  presets alongside it — so `var(--wp--preset--color--ink, <fallback>)` can
 *  never fall back: the slug is always defined, just never re-pointed.
 *
 *  Aliasing block CSS to family A therefore produced a HALF restyle: the three
 *  shared slugs followed the variation (frames/borders went dark under Amber)
 *  while every text colour stayed light-theme near-black. Measured on the
 *  gallery block: caption-on-surface contrast fell 4.51 -> 3.86 (WCAG AA fail)
 *  the moment Amber Terminal was selected.
 *
 *  Fix: alias to family B wherever the theme.json DEFAULTS are byte-identical,
 *  so the default look is pixel-unchanged and every variation now reaches the
 *  blocks:
 *
 *      bg -> base            (#ffffff == #ffffff)
 *      ink -> contrast       (#14181f == #14181f)
 *      ink-3 -> subtle       (#6b7280 == #6b7280)
 *      on-accent -> accent-contrast (#ffffff == #ffffff)
 *
 *  Slugs with no exact family-B counterpart (ink-2, accent-strong, link,
 *  link-hover) keep their family-A name and are instead DECLARED BY EVERY
 *  VARIATION — see styles/*.json. tools/lint-block-tokens.php enforces that:
 *  any token a block stylesheet uses must be overridden by every variation.
 *
 *  success/warning/danger are deliberately NOT variation-scoped: they are
 *  semantic status colours and are meant to read the same in every skin.
 */
:root {
	--c-bg:            var(--wp--preset--color--base);
	--c-surface:       var(--wp--preset--color--surface);
	--c-surface-2:     var(--wp--preset--color--surface-2);
	--c-border:        var(--wp--preset--color--border);
	--c-ink:           var(--wp--preset--color--contrast);
	--c-ink-2:         var(--wp--preset--color--ink-2);
	--c-ink-3:         var(--wp--preset--color--subtle);
	--c-accent:        var(--wp--preset--color--accent);
	--c-accent-strong: var(--wp--preset--color--accent-strong);
	--c-on-accent:     var(--wp--preset--color--accent-contrast);
	--c-link:          var(--wp--preset--color--link);
	--c-link-hover:    var(--wp--preset--color--link-hover);
	--c-success:       var(--wp--preset--color--success);
	--c-warning:       var(--wp--preset--color--warning);
	--c-danger:        var(--wp--preset--color--danger);

	--r-sm:     var(--wp--custom--radius--sm);
	--r-md:     var(--wp--custom--radius--md);
	--r-lg:     var(--wp--custom--radius--lg);

	--shadow-sm: var(--wp--custom--shadow--sm);
	--shadow-md: var(--wp--custom--shadow--md);
	--shadow-lg: var(--wp--custom--shadow--lg);

	--space: var(--wp--preset--spacing);
	--space-0: var(--wp--preset--spacing--0);
	--space-1: var(--wp--preset--spacing--1);
	--space-2: var(--wp--preset--spacing--2);
	--space-3: var(--wp--preset--spacing--3);
	--space-4: var(--wp--preset--spacing--4);
	--space-5: var(--wp--preset--spacing--5);
	--space-6: var(--wp--preset--spacing--6);
	--space-7: var(--wp--preset--spacing--7);
	--space-8: var(--wp--preset--spacing--8);
	--space-9: var(--wp--preset--spacing--9);
	--space-10: var(--wp--preset--spacing--10);

	/* WordPress normalizes numeric font slugs to 2-xl/3-xl in CSS output.
	   Keep the contract names usable for theme.json styles and local CSS. */
	--wp--preset--font-size--2xl: var(--wp--preset--font-size--2-xl);
	--wp--preset--font-size--3xl: var(--wp--preset--font-size--3-xl);

	--page-pad: var(--wp--custom--page--pad);
	--section-rhythm: var(--wp--custom--section--rhythm);

	/* ---- Composition seam for bundled ArneIron plugins (wp task 2) --------
	   The connector chat widget (arncon-chat.css) and the portal panels
	   (portal.css) alias a second, CRT-era token family (--c-signal,
	   --c-panel, --c-blood, …) "with safe fallbacks" — hardcoded ArneIron
	   signal-red / blood-black values. Base ds.css never defined those slugs,
	   so the widgets half-inherited the live palette (--c-ink resolved to the
	   theme ink) and half fell back to the red-CRT brand: a grey/white site
	   with a glossy red chat FAB and dark-red dialog surfaces — the exact
	   style mixing wp task 2 removes. Defining the whole family here, mapped
	   onto the theme's own presets, makes every consumer compose with the
	   ACTIVE token system (base or any style variation) instead of falling
	   back to a second palette. */
	--c-base:          var(--wp--preset--color--bg);
	--c-panel:         var(--wp--preset--color--surface);
	--c-panel-2:       var(--wp--preset--color--surface-2);
	--c-panel-3:       var(--wp--preset--color--surface-3);
	--c-well:          var(--wp--preset--color--surface-2);
	--c-signal:        var(--wp--preset--color--accent);
	--c-red-deep:      var(--wp--preset--color--accent);
	--c-blood:         var(--wp--preset--color--accent-strong);
	--c-maroon:        var(--wp--preset--color--accent-strong);
	--c-ink-dim:       var(--wp--preset--color--ink-2);
	--c-grid:          var(--wp--preset--color--border);
	--c-hair:          var(--wp--preset--color--border);
	--glow-red:        color-mix(in srgb, var(--wp--preset--color--accent) 45%, transparent);
	--glow-red-soft:   color-mix(in srgb, var(--wp--preset--color--accent) 18%, transparent);
	--sh-block:        var(--wp--custom--shadow--md);
}

/* ---- 2. Reset essentials --------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

body {
	margin: 0;
	background: var(--c-bg);
	color: var(--c-ink);
	font-family: var(--wp--preset--font-family--ui);
	font-size: var(--wp--preset--font-size--base);
	line-height: 1.6;
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

img, picture, svg, video, canvas { max-width: 100%; height: auto; display: block; }
a { color: var(--c-link); }
a:hover { color: var(--c-link-hover); }

/* Single visible focus ring everywhere (keyboard-focus skill, WCAG 2.4.7). */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
	outline: 2px solid var(--c-accent-strong);
	outline-offset: 2px;
	border-radius: 2px;
}

/* Respect reduced motion globally (accessibility skill). */
@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		animation-duration: 0.001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.001ms !important;
		scroll-behavior: auto !important;
	}
}

/* ---- 3. Skip link ---------------------------------------------------------- */
.skip-link:not(:focus):not(:active) {
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	height: 1px;
	overflow: hidden;
	position: absolute;
	white-space: nowrap;
	width: 1px;
}
.skip-link:focus,
.skip-link:active {
	position: fixed;
	top: var(--space-3);
	left: var(--space-3);
	z-index: 1000;
	background: var(--c-accent-strong);
	color: var(--c-on-accent);
	padding: var(--space-3) var(--space-5);
	border-radius: var(--r-md);
}

/* ---- 4. Layout primitives -------------------------------------------------- */
/* `.site-main` is NOT in this group, deliberately.
   -----------------------------------------------------------------------------
   It used to be, and that double-inset every page. `main` carries WordPress's
   `has-global-padding` (32px inline at 1440) AND `is-layout-constrained`, so it
   already positions its own children: full-width box, root padding, children
   capped at contentSize and centred. Constraining the box to wideSize on top of
   that applied the width twice — measured on dev, the header row sat at x=70
   spanning 1300px while the page content sat at x=102 spanning 1236px, a 32px
   inset on each side that nothing asked for.

   With the constraint removed, `main` runs full width, the root padding does the
   insetting, and the constrained layout centres content at exactly x=70/1300 —
   flush with the header and footer rows. Verified before changing the file by
   injecting the override live and measuring.

   Side effect, and a wanted one: `alignfull` inside the content can now actually
   reach the viewport edge. While `main` was capped at wideSize it could not. */
.site-header,
.site-footer {
	/* The literals are LAST-RESORT fallbacks only: theme.json always declares
	   both globals, so `var()` never falls through on a live page. They were
	   left at the pre-0.6.6 values (1280/768) and therefore documented a layout
	   the theme no longer has; realigned to theme.json settings.layout
	   (wideSize 1300px, contentSize 1300px) so the file cannot mislead a reader
	   about the current geometry. */
	width: min(100% - 2 * var(--page-pad), var(--wp--style--global--wide-size, 1300px));
	margin-inline: auto;
}
.site-main { padding-block: var(--space-6); }

.wp-block-post-content {
	max-width: var(--wp--style--global--content-size, 1300px);
	margin-inline: auto;
}
.wp-block-post-content > * {
	margin-block-start: var(--space-6);
	margin-block-end: 0;
}
.wp-block-post-content > :first-child { margin-block-start: 0; }
.wp-block-post-content :where(h1, h2, h3, h4, h5, h6) {
	color: var(--c-ink);
	margin-block-end: 0;
}

/*
 * hub#1687 — the design-system heading type scale is DEFERENTIAL.
 *
 * A rule inside a cascade layer always loses to an unlayered rule, whatever the
 * specificity. WordPress emits theme.json / Style-Variation typography
 * unlayered (`global-styles-inline-css`), so putting the DS type scale in the
 * `arneiron-ds` layer means: a tenant whose variation pins h1-h6 to its own
 * display face keeps that face, and a site whose variation says
 * nothing about headings still gets this scale. Before this,
 * `.wp-block-post-content :where(h1..h6)` at specificity (0,1,0) beat the bare
 * `h1, h2, ...` selectors WordPress emits and silently repainted the tenant's
 * headings in the UI font.
 *
 * Colour and margins stay unlayered above: they are structural, and no global-
 * styles output competes for them.
 */
@layer arneiron-ds {
	.wp-block-post-content :where(h1, h2, h3, h4, h5, h6) {
		font-family: var(--wp--preset--font-family--ui);
		font-weight: 700;
	}
	.wp-block-post-content h1 {
		font-size: var(--wp--preset--font-size--3-xl);
		line-height: 1.1;
	}
	.wp-block-post-content h2 {
		font-size: var(--wp--preset--font-size--2-xl);
		line-height: 1.15;
	}
	.wp-block-post-content h3 {
		font-size: var(--wp--preset--font-size--xl);
		line-height: 1.15;
	}
	.wp-block-post-content h4 {
		font-size: var(--wp--preset--font-size--lg);
		font-weight: 650;
		line-height: 1.2;
	}
	.wp-block-post-content h5 {
		font-size: var(--wp--preset--font-size--base);
		font-weight: 650;
		line-height: 1.3;
	}
	.wp-block-post-content h6 {
		font-size: var(--wp--preset--font-size--sm);
		font-weight: 650;
		line-height: 1.3;
	}
}
.wp-block-post-content > h1,
.wp-block-post-content > h2,
.wp-block-post-content > h3,
.wp-block-post-content > h4 {
	margin-block-start: var(--space-8);
}
.wp-block-post-content > h1 + *,
.wp-block-post-content > h2 + *,
.wp-block-post-content > h3 + *,
.wp-block-post-content > h4 + *,
.wp-block-post-content > h5 + *,
.wp-block-post-content > h6 + * {
	margin-block-start: var(--space-4);
}
.wp-block-post-content > .wp-block-separator {
	margin-block: var(--space-8);
}
.wp-block-post-content > ul,
.wp-block-post-content > ol {
	padding-inline-start: var(--space-7);
}
.wp-block-post-content li + li { margin-block-start: var(--space-2); }
.wp-block-post-content li > ul,
.wp-block-post-content li > ol { margin-block-start: var(--space-2); }

/* Landing template: no top chrome pad — the hero owns the top fold. The body
   gets full access to alignwide/alignfull sections; the content constrain is
   re-applied inside (per-section) by the blocks/patterns. */
.site-main--landing { padding-block: 0; }
.site-main--landing > .wp-block-post-content { padding-block: var(--section-rhythm, clamp(32px, 4vw, 96px)); }

/* Robust grid that reflows without a media query (grid-spacing skill). */
.features__grid {
	display: grid;
	gap: var(--space-6);
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
	align-items: stretch;
}

/* ---- 5. Component: card ---------------------------------------------------- */
.card {
	background: var(--c-surface);
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	padding: var(--space-6);
	box-shadow: var(--shadow-sm);
}

/* ---- 6. Component: buttons (variants via selector layer) -------------------*/
.wp-block-button__link {
	border-radius: var(--r-md);
	min-height: 44px;          /* WCAG 2.5.5 touch target */
	display: inline-flex;
	align-items: center;
	justify-content: center;
	transition: background-color .15s ease;
}
.wp-block-button__link:hover { text-decoration: none; }

.is-style-outline .wp-block-button__link {
	background: transparent;
	border: 1px solid currentColor;
	color: var(--c-accent);
}
.is-style-outline .wp-block-button__link:hover { color: var(--c-accent-strong); }

/* ---- 7. Empty state ------------------------------------------------------- */
.empty {
	border: 1px dashed var(--c-border);
	border-radius: var(--r-md);
	text-align: center;
	color: var(--c-ink-2);
	background: var(--c-surface);
}
.empty .wp-block-heading { color: var(--c-ink); }

/* ---- 8. Query loop / cards block-level polish ----------------------------- */
.query-compact .query-compact__item { display: flex; flex-direction: column; gap: var(--space-3); }
.query-compact__date { color: var(--c-ink-3); }
.query-compact .wp-block-post-featured-image { margin: 0; }
.query-compact .wp-block-post-title { margin: 0; }

/* ---- 9. Forms (front-end rendered ones, e.g. search + comments) ----------- */
input[type="text"], input[type="search"], input[type="email"], input[type="url"],
input[type="password"], input[type="tel"], textarea, select {
	width: 100%;
	min-height: 44px;            /* touch target */
	padding: var(--space-3) var(--space-4);
	font: inherit;
	color: var(--c-ink);
	background: var(--c-bg);
	border: 1px solid var(--c-border);
	border-radius: var(--r-sm);
	transition: border-color .15s ease, box-shadow .15s ease;
}
input:focus-visible, textarea:focus-visible, select:focus-visible {
	border-color: var(--c-accent);
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-accent) 25%, transparent);
}

/* ---- 10. Pagination ------------------------------------------------------- */
.wp-block-query-pagination { gap: var(--space-3); }
.wp-block-query-pagination a, .wp-block-query-pagination span {
	min-width: 44px; min-height: 44px; display: inline-grid; place-items: center;
	border-radius: var(--r-sm);
}
.wp-block-query-pagination .current { background: var(--c-accent); color: var(--c-on-accent); }

/* ---- 11. Hero ------------------------------------------------------------- */
.hero__inner { display: flex; flex-direction: column; gap: var(--space-4); align-items: center; }

/* ---- 12. Tables ----------------------------------------------------------- */
.table-wrap { overflow-x: auto; }
.wp-block-table table { border-collapse: collapse; width: 100%; }
.wp-block-table th {
	background: var(--c-surface);
	text-align: start;
	padding: var(--space-3) var(--space-4);
	font-weight: 650;
}
.wp-block-table td {
	padding: var(--space-3) var(--space-4);
	border-top: 1px solid var(--c-border);
}
.wp-block-table figcaption {
	font-size: var(--wp--preset--font-size--xs);
	color: var(--c-ink-3);
	margin-top: var(--space-3);
}

/* ---- 13. Sidebar ---------------------------------------------------------- */
.site-sidebar { padding-block: var(--space-6); }

/* ---- 14. Prose atoms + quote polish (DisArnie audit high/med) ----------- */
/* Bring the prose atoms onto the grey ramp so they stop reading as browser
   defaults. Resolves DisArnie audit #2 (mark), #3 (code/kbd), #4 (quote/pullquote). */

mark {
	background: var(--c-surface-2);
	color: var(--c-ink);
	padding: 0 .15em;
	border-radius: var(--r-sm);
}

code, kbd {
	font-family: var(--wp--preset--font-family--ui);
	background: var(--c-surface);
	padding: .1em .35em;
	border-radius: var(--r-sm);
	border: 1px solid var(--c-border);
	font-size: .9em;
}
kbd { font-weight: 600; }

/* <code> inside <pre> keeps the code-block appearance (no inline chrome). */
pre code, pre kbd {
	background: none; border: 0; padding: 0; font-size: inherit;
}
/* R2-3: pull <pre.wp-block-code> + preformatted + verse onto the grey ramp as one
   deliberate 'code surface' (no hue). Satisfies concept.md §4 + decision #5. */
pre.wp-block-code,
pre.wp-block-preformatted,
pre.wp-block-verse {
	background: var(--c-surface);
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	padding: var(--space-4) var(--space-5);
	overflow-x: auto;
	font-family: var(--wp--preset--font-family--ui);
	font-size: var(--wp--preset--font-size--sm);
	line-height: 1.5;
	color: var(--c-ink-2);
}

/* R2-1 fix: scope blockquote so the pullquote's nested <blockquote> doesn't
   inherit the standalone-quote border. Concept.md §4 single-treatment. */
blockquote:not(.wp-block-pullquote blockquote):not(figcaption) {
	margin-inline: 0;
	padding-inline: var(--space-5);
	border-inline-start: 3px solid var(--c-border);
	color: var(--c-ink-2);
}
/* R2-2 durability: scope the cite the same way so the pullquote-cite override at
   the rule below wins cleanly by source order, not by specificity tie. */
blockquote:not(.wp-block-pullquote blockquote) cite {
	display: block;
	margin-top: var(--space-3);
	font-size: var(--wp--preset--font-size--sm);
	color: var(--c-ink-3);
}

.wp-block-pullquote {
	border-top: 2px solid var(--c-border);
	border-bottom: 2px solid var(--c-border);
	padding-block: var(--space-6);
	margin-block: var(--space-7);
	text-align: center;
}
/* R2-1: reset the nested quote so it has no doubled border, no inner padding,
   and full ink color (the pullquote band is the only treatment). */
figure.wp-block-pullquote blockquote {
	border: 0;
	margin-inline: 0;
	padding-inline: 0;
	color: var(--c-ink);
}
.wp-block-pullquote cite,
.wp-block-pullquote .wp-block-pullquote__citation {
	display: block;
	margin-top: var(--space-3);
	font-size: var(--wp--preset--font-size--sm);
	color: var(--c-ink-3);
}

/* Gallery placeholder fallback (DisArnie audit #5): when a gallery has no
   real media, the tiles should render as deliberate grey 4:3 boxes per §5,
   not as a zero-height invisible frame. */
figure.wp-block-image:has(> div[role="img"].image-placeholder) {
	margin: 0;
}
.image-placeholder {
	background: var(--c-surface-2);
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	display: grid;
	place-items: center;
	color: var(--c-ink-3);
}
.image-placeholder::after {
	content: "4:3";
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 600;
}

/* Latest-posts excerpt drops one step in hierarchy (DisArnie audit #11 polish). */
.wp-block-latest-posts__post-excerpt {
	font-size: var(--wp--preset--font-size--sm);
	color: var(--c-ink-2);
	margin-top: var(--space-2);
}

/* Re-create the gallery grid for the core/html placeholder gallery.
   (DisArnie round-2 — gallery regression fix; §4 grid spec + §5 placeholder spec.) */
figure.wp-block-gallery {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: var(--space-4);
}
figure.wp-block-gallery > .wp-block-image { margin: 0; }
@media (max-width: 600px) {
	figure.wp-block-gallery { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
	figure.wp-block-gallery { grid-template-columns: 1fr; }
}

/* R2-8: search input onto the grey ramp (one rule, matches §4 search spec). */
.wp-block-search__input {
	background: var(--c-bg);
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	padding: var(--space-3) var(--space-4);
}
.wp-block-search__button {
	border-radius: var(--r-md);
}

/* R2-9: real disabled-button state for the reference page. Convention:
   component-design.md 'rest/hover/focus-visible/active/disabled' — the disabled
   state must be visible, not just described. */
.wp-block-button .wp-element-button[aria-disabled="true"],
.wp-block-button .wp-element-button.is-disabled,
.wp-block-button .wp-element-button:disabled {
	color: var(--c-ink-3) !important;
	background-color: var(--c-surface-2) !important;
	border-color: var(--c-border) !important;
	cursor: not-allowed;
	opacity: .65;
	text-decoration: none;
	pointer-events: none;
}

/* ---- 14b. Mobile header bar (PB-B5 / §10.5) -------------------------------
   One-row chrome shown below desktop headers on mobile breakpoints only.
   Layout (left -> right): [LOGO] ............ [balance] [account] [cart] [☰]
   The right cluster orders icons so when read right-to-left you get
   hamburger -> cart -> my-account -> balance (UX spec).
   Progressive enhancement: the bar is server-rendered always; CSS hides it on
   desktop and hides desktop headers on mobile (so no parity gap with JS off).
   ------------------------------------------------------------------------ */

/* Default: desktop headers visible, mobile bar hidden. */
.header-mobile.arneiron-mobile-bar { display: none; }

/* On small screens: swap. Breakpoint matches core's default 782 navigation. */
@media (max-width: 782px) {
	/* Hide the desktop header's own chrome (we replace it wholesale). */
	.site-header:not(.header-mobile) {
		display: none !important;
	}
	/* Show the mobile bar. */
	.header-mobile.arneiron-mobile-bar {
		display: block;
	}
	/* Keep it sticky at the top. */
	.header-mobile.arneiron-mobile-bar {
		position: sticky;
		top: 0;
		z-index: 50;
	}
	/* Row: space-between so logo sticks left, icons stick right. */
	.arneiron-mobile-bar__row {
		width: 100%;
		align-items: center;
	}
	/* Right cluster: reversed flex so the LAST child in DOM (hamburger) sits
	   rightmost; reading R->L: hamburger, cart, account, balance. The icons
	   itself wraps each chip; gap keeps them tidy. */
	.arneiron-mobile-bar__icons {
		gap: var(--space-2, 8px);
	}
	.no-js .arneiron-mobile-bar__icons-inner { display: contents; }
}

/* Component: wallet chip (from [arneiron_balance_seam]). */
.arneiron-wallet-chip {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	padding: 4px 8px;
	border-radius: var(--r-md);
	background: var(--c-surface-2);
	color: var(--c-ink);
	text-decoration: none;
	font-size: 0.85em;
	line-height: 1;
}
.arneiron-wallet-chip__label { color: var(--c-ink-2); font-size: 0.85em; }
.arneiron-wallet-chip__value { font-weight: 600; }
.arneiron-wallet-chip .woocommerce-Price-amount { font-size: 0.9em; }

/* Component: my-account toggle + dropdown. */
.arneiron-account-toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 40px;
	border-radius: var(--r-md);
	background: transparent;
	color: var(--c-ink);
	text-decoration: none;
	border: 1px solid transparent;
	cursor: pointer;
	font-size: 1.1em;
}
.arneiron-account-toggle:hover,
.arneiron-account-toggle[aria-expanded="true"] {
	background: var(--c-surface-2);
	border-color: var(--c-border);
}
.arneiron-account-toggle__screen { /* vis: a way to also show the word if needed */ }
.arneiron-account-menu {
	position: absolute;
	right: 0;
	top: calc(100% + 4px);
	list-style: none;
	margin: 0;
	padding: var(--space-2, 8px);
	background: var(--c-bg);
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
	min-width: 200px;
	z-index: 60;
}
.arneiron-account-menu[hidden],
.arneiron-account-menu[aria-hidden="true"] { display: none; }
.arneiron-account-menu__item { margin: 0; }
.arneiron-account-menu__link {
	display: block;
	padding: 6px 10px;
	color: var(--c-ink);
	text-decoration: none;
	border-radius: var(--r-sm, 4px);
}
.arneiron-account-menu__link:hover {
	background: var(--c-surface-2);
}
.arneiron-account-menu__item--logout .arneiron-account-menu__link {
	color: var(--c-danger, #9a3535);
}
.arneiron-mobile-bar__icons .arneiron-account-toggle,
.arneiron-mobile-bar__icons .arneiron-account-menu {
	position: relative;
}
/* Wrap the my-account icon in a relative wrapper so the absolute menu
   positions against the mobile-bar icons group. */
.arneiron-mobile-bar__icons-inner > .arneiron-account-toggle,
.arneiron-mobile-bar__icons-inner > .arneiron-account-menu {
	position: relative;
}

/* Component: cart chip. */
.arneiron-cart-chip {
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: 4px;
	padding: 6px;
	border-radius: var(--r-md);
	background: transparent;
	color: var(--c-ink);
	text-decoration: none;
	line-height: 1;
}
.arneiron-cart-chip:hover { background: var(--c-surface-2); }
.arneiron-cart-chip__count {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 18px;
	height: 18px;
	padding: 0 4px;
	border-radius: 999px;
	background: var(--c-accent, var(--c-ink));
	color: var(--c-on-accent, #fff);
	font-size: 0.75em;
	font-weight: 600;
}
.arneiron-cart-chip__icon,
.arneiron-wallet-chip__icon { font-size: 1.1em; }
.arneiron-cart-chip__total { color: var(--c-ink-2); font-size: 0.85em; }

/* When cart/wallet is empty, hide the empty count so the chip stays tidy. */
.arneiron-cart-chip[data-cart-count="0"] .arneiron-cart-chip__count { display: none; }

/* Hamburger reuse: the theme already styles .nav-toggle; the bar variant uses
   the same .nav-toggle classes so JS hooks (`bindNavDisclosure` in theme.js)
   work without modification. */

/* Account dropdown: hide on outside-click/tap (progressive enhancement).
   Without JS, the menu is still keyboard-reachable via anchor focus. */
.no-js .arneiron-account-menu[hidden] { display: block; }


/* ---- 14c. Mobile drawer (off-canvas) --------------------------------------
   Slides in from the right when the mobile-bar hamburger is tapped. Driven by
   the [data-state] attribute set by theme.js bindMobileMenu(). The drawer
   markup lives in parts/mobile-menu.html.
   ------------------------------------------------------------------------ */

.arneiron-mobile-drawer {
	/* Hidden by default. The drawer uses position: fixed so it never affects
	   desktop layout even when visible — and on desktop it isn't toggled. */
	position: fixed;
	inset: 0;
	z-index: 100;
	pointer-events: none; /* re-enabled below when open */
	visibility: hidden;
}

.arneiron-mobile-drawer[data-state="open"] {
	pointer-events: auto;
	visibility: visible;
}

/* Scrim: click-outside-to-close. */
.arneiron-mobile-drawer__scrim {
	position: fixed;
	inset: 0;
	z-index: 100;
	background: rgba( 10, 13, 18, 0.45 );
	opacity: 0;
	transition: opacity 0.2s ease;
	border: 0;
	padding: 0;
	cursor: default;
}
.arneiron-mobile-drawer[data-state="open"] .arneiron-mobile-drawer__scrim {
	opacity: 1;
}

/* Panel: slides in from the right. */
.arneiron-mobile-drawer__panel {
	position: fixed;
	top: 0;
	right: 0;
	bottom: 0;
	z-index: 110;
	display: flex;
	flex-direction: column;
	gap: var(--space-4, 16px);
	width: min( 320px, 86vw );
	max-width: 360px;
	padding: var(--space-4, 16px);
	background: var(--c-bg);
	color: var(--c-ink);
	border-left: 1px solid var(--c-border);
	box-shadow: -8px 0 24px rgba( 0, 0, 0, 0.08 );
	transform: translateX( 100% );
	transition: transform 0.22s cubic-bezier( 0.22, 0.61, 0.35, 1 ), opacity 0.22s ease;
	overflow-y: auto;
	overscroll-behavior: contain;
}
.arneiron-mobile-drawer[data-state="open"] .arneiron-mobile-drawer__panel {
	transform: translateX( 0 );
}

/* Panel content ---------------------------------------------------------- */
.mobile-menu-head {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: var(--space-2, 8px);
	padding-bottom: var(--space-3, 12px);
	border-bottom: 1px solid var(--c-border);
}
.mobile-menu-head .arneiron-site-logo img { max-height: 32px; width: auto; }
.mobile-menu-close {
	width: 36px;
	height: 36px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	background: transparent;
	color: var(--c-ink);
	cursor: pointer;
	font-size: 1.25em;
	line-height: 1;
}
.mobile-menu-close:hover { background: var(--c-surface-2); }

.arneiron-mobile-drawer__divider {
	border: 0;
	border-top: 1px solid var(--c-border);
	margin: 0;
}

/* Server-rendered mobile nav (from [arneiron_mobile_nav]). */
.arneiron-mobile-nav-wrap { display: block; }
.arneiron-mobile-nav {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 0;
}
.arneiron-mobile-nav .arneiron-mobile-nav { /* nested submenu */
	padding-left: var(--space-3, 12px);
	border-left: 1px solid var(--c-border);
	margin-left: var(--space-2, 8px);
	margin-top: 2px;
}
.arneiron-mobile-nav__item { margin: 0; }
.arneiron-mobile-nav__link {
	display: block;
	padding: var(--space-2, 8px) 0;
	color: var(--c-ink);
	text-decoration: none;
	font-weight: 500;
	border-bottom: 1px solid transparent;
}
.arneiron-mobile-nav__link:hover,
.arneiron-mobile-nav__link.is-current {
	color: var(--c-accent, var(--c-ink));
	border-bottom-color: currentColor;
}

/* Social icons inside the drawer. */
.mobile-menu-social { display: block; }
.arneiron-social {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	gap: var(--space-2, 8px);
}
.arneiron-social__item { margin: 0; }
.arneiron-social__link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 32px;
	border: 1px solid var(--c-border);
	border-radius: var(--r-md);
	background: transparent;
	color: var(--c-ink-2);
	text-decoration: none;
}
.arneiron-social__link:hover {
	background: var(--c-surface-2);
	color: var(--c-ink);
}
.arneiron-social__icon {
	font-size: 0;
	width: 16px; height: 16px;
	background-color: currentColor;
	mask-size: contain;
	mask-repeat: no-repeat;
	mask-position: center;
	-webkit-mask-size: contain;
	-webkit-mask-repeat: no-repeat;
	-webkit-mask-position: center;
}
.arneiron-social__link--x .arneiron-social__icon       { -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z'/></svg>"); mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z'/></svg>"); }
.arneiron-social__link--github .arneiron-social__icon  { -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.56 0-.28-.01-1.02-.02-2-3.2.7-3.88-1.54-3.88-1.54-.53-1.34-1.29-1.7-1.29-1.7-1.05-.72.08-.71.08-.71 1.16.08 1.77 1.19 1.77 1.19 1.03 1.77 2.7 1.26 3.36.96.1-.75.4-1.26.73-1.55-2.55-.29-5.23-1.28-5.23-5.69 0-1.26.45-2.29 1.19-3.1-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.18 1.18a11.04 11.04 0 0 1 5.78 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.11 3.05.74.81 1.19 1.84 1.19 3.1 0 4.42-2.69 5.39-5.25 5.68.41.36.78 1.07.78 2.15 0 1.55-.01 2.8-.01 3.18 0 .31.21.67.8.56A11.51 11.51 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5z'/></svg>"); mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.56 0-.28-.01-1.02-.02-2-3.2.7-3.88-1.54-3.88-1.54-.53-1.34-1.29-1.7-1.29-1.7-1.05-.72.08-.71.08-.71 1.16.08 1.77 1.19 1.77 1.19 1.03 1.77 2.7 1.26 3.36.96.1-.75.4-1.26.73-1.55-2.55-.29-5.23-1.28-5.23-5.69 0-1.26.45-2.29 1.19-3.1-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.18 1.18a11.04 11.04 0 0 1 5.78 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.11 3.05.74.81 1.19 1.84 1.19 3.1 0 4.42-2.69 5.39-5.25 5.68.41.36.78 1.07.78 2.15 0 1.55-.01 2.8-.01 3.18 0 .31.21.67.8.56A11.51 11.51 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5z'/></svg>"); }
.arneiron-social__link--linkedin .arneiron-social__icon { -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M4.98 3.5C4.98 4.88 3.87 6 2.5 6S0 4.88 0 3.5 1.12 1 2.5 1s2.48 1.12 2.48 2.5zM.04 8h4.92v16H.04V8zm8.06 0h4.71v2.19h.07c.66-1.2 2.27-2.46 4.67-2.46 5 0 5.92 3.29 5.92 7.56V24h-4.92v-7.06c0-1.68-.03-3.85-2.34-3.85-2.34 0-2.7 1.83-2.7 3.72V24H8.1V8z'/></svg>"); mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M4.98 3.5C4.98 4.88 3.87 6 2.5 6S0 4.88 0 3.5 1.12 1 2.5 1s2.48 1.12 2.48 2.5zM.04 8h4.92v16H.04V8zm8.06 0h4.71v2.19h.07c.66-1.2 2.27-2.46 4.67-2.46 5 0 5.92 3.29 5.92 7.56V24h-4.92v-7.06c0-1.68-.03-3.85-2.34-3.85-2.34 0-2.7 1.83-2.7 3.72V24H8.1V8z'/></svg>"); }

/* Body-scroll lock when the drawer is open. */
body.mobile-drawer-open { overflow: hidden; }

/* Reduced motion: drop the slide animation. */
@media ( prefers-reduced-motion: reduce ) {
	.arneiron-mobile-drawer,
	.arneiron-mobile-drawer__scrim,
	.arneiron-mobile-drawer__panel {
		transition: none !important;
	}
}

/* Desktop: hide the drawer markup entirely (its parent header-mobile is already
   hidden, but this is a defence-in-depth). */
@media ( min-width: 783px ) {
	.arneiron-mobile-drawer { display: none; }
}

/* ---- 15. Print ----------------------------------------------------------- */
@media print {
	.site-header, .site-footer, .sidebar, .skip-link { display: none !important; }
	body { color: #000; background: #fff; }
	a::after { content: " (" attr(href) ")"; font-size: .9em; color: #444; }
}

/* ---- 4c. Desktop header chrome (.app-header) -------------------------------
   Restored 2026-08-10 (see hub issue for the header regression).

   The header's visual layer used to live in the `arneiron/header` BLOCK
   stylesheet, `blocks/build/header/style-index.css`, with every selector
   scoped to the block wrapper `.wp-block-arneiron-header`. Block
   consolidation removed the header block — correct, a header is theme chrome,
   not a block — and flattened `parts/header.html` into a core/group that kept
   the INNER class names (app-header / brand / app-nav / app-header-cta) but
   not the wrapper. Every rule in that stylesheet stopped matching and the
   header rendered as an unstyled flex row: no bar, no rule, no gutters.

   These are those rules, re-scoped to the class that survived and re-pointed
   at the theme tokens (§1). Two things are deliberately NOT restored:
     - `.menu-toggle` — the flattened part uses core/navigation's own
       responsive overlay, which ships its own trigger.
     - the ≤860px `.app-nav { display: none }` block — it would hide core's
       overlay trigger with it. The desktop header is instead swapped for the
       mobile bar at ≤782px by §14b, which is what `parts/header-mobile.html`
       exists for; that swap keys on `.site-header`, which is why the header
       part carries `site-header app-header`.
   -------------------------------------------------------------------------- */

/* The container rule is two-class so it wins against core's single-class
   `.wp-container-core-group-is-layout-*` gap declaration. */
.wp-block-group.app-header {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	padding-block: var(--space-3);
	color: var(--c-ink);
}

/* The BAND is painted on the wrapper, the CONTENT is constrained by the row.
   -----------------------------------------------------------------------------
   `background` and `border-bottom` used to sit on `.app-header` — which is the
   ROW, and the row is `width: min(100% - 2*pad, wideSize)` (§4). So the bar
   stopped where the content stopped: a 1300px band floating on a 1425px page,
   with the page background showing either side of it. A site header reads as a
   full-bleed band with its contents aligned to the layout column, so the paint
   belongs on the full-bleed wrapper WordPress already emits around a template
   part, and only the paint moves — the row keeps its width and its padding.

   `:has(> .app-header)` rather than a bare `header.wp-block-template-part`,
   because a page renders TWO header parts: this one and the mobile bar, which
   brings its own `has-surface-background-color`. Painting both would double the
   band below 782px. The mobile swap in §14b already keys on `:has()` for the
   same reason. */
header.wp-block-template-part:has(> .app-header) {
	background: var(--c-bg);
	border-bottom: 1px solid var(--c-border);
}

/* ...and the paint has to leave when the row does.
   §14b hides the desktop header at ≤782px with `display: none` on the ROW, not
   on the wrapper. That was invisible while the wrapper was transparent; once it
   carries the band, an emptied wrapper still renders its 1px border — a hairline
   across the top of every mobile page. Measured on dev: wrapper height 1px at
   390px. Neutralising the paint (rather than hiding the box) keeps this rule
   purely cosmetic, so it cannot hide a header some other part put here. */
@media (max-width: 782px) {
	header.wp-block-template-part:has(> .app-header) {
		background: none;
		border-bottom: 0;
	}
}

/* Three zones out of a flat child list: brand left, menu centred, controls
   right.

   The part's layout is `justify-content: space-between`, which spreads every
   child evenly across the full width — and the trailing cluster (wallet chip,
   my-account, cart, CTA) reads as one control group, so it must sit as one.
   WooCommerce hooks account and mini-cart in as SIBLINGS of the CTA (Block
   Hooks, appended to the template part), so they cannot be wrapped in a
   sub-group in the source; the grouping has to happen here.

   Auto margins on BOTH sides of the nav absorb all free space before
   justify-content distributes any: the menu settles in the middle of the space
   between brand and controls, and the controls pack at the end with only
   `gap` between them. Order-independent — it holds however many chips the
   plugins add, and it stays in normal flow, so nothing overlaps when the menu
   is long (the row wraps instead). */
.app-header > .app-nav {
	margin-inline: auto;
}
.app-header.is-sticky {
	position: sticky;
	top: 0;
	z-index: 40;
}

/* Brand — the site title stands in for the block's <a class="brand">. */
.app-header .brand {
	margin-block: 0;
	font-size: var(--wp--preset--font-size--lg);
	font-weight: 700;
	letter-spacing: -0.01em;
	line-height: 1.2;
}
.app-header .brand a {
	color: var(--c-ink);
	text-decoration: none;
}
.app-header .brand a:hover,
.app-header .brand a:focus-visible {
	color: var(--c-accent-strong);
}

/* Primary navigation. `app-nav` sits on both the navigation wrapper and its
   list, so scope link rules through the header, not through `.app-nav` alone. */
.app-header .app-nav a {
	color: var(--c-ink-2);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 500;
	text-decoration: none;
}
.app-header .app-nav a:hover,
.app-header .app-nav a:focus-visible {
	color: var(--c-ink);
}
.app-header .app-nav .current-menu-item > a {
	color: var(--c-accent-strong);
	font-weight: 600;
}

/* Account + cart. WooCommerce hooks `customer-account` and `mini-cart` into
   the header template part (Block Hooks), so they are part of the default
   header even though they are not in the part's source. */
.app-header .wp-block-woocommerce-customer-account,
.app-header .wc-block-mini-cart {
	display: inline-flex;
	align-items: center;
}
.app-header .woocommerce-customer-account,
.app-header .wc-block-mini-cart__button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	/* The vertical target stays 44px; the horizontal one is trimmed to 36px so
	   the icons read as one cluster rather than three separated controls. Still
	   comfortably above the 24x24 WCAG 2.5.8 minimum. */
	min-height: 44px;
	min-width: 36px;
	padding-inline: 0;
	color: var(--c-ink-2);
	text-decoration: none;
}
.app-header .woocommerce-customer-account:hover,
.app-header .wc-block-mini-cart__button:hover {
	color: var(--c-ink);
}
.app-header .wc-block-mini-cart__button:focus-visible,
.app-header .woocommerce-customer-account:focus-visible {
	outline: 2px solid var(--c-accent-strong);
	outline-offset: 2px;
	border-radius: var(--r-sm);
}

/* CTA. The button keeps core's `.wp-element-button` styling (§6); these rules
   only restore the compact `btn--sm` proportion the header CTA had. */
.app-header .app-header-cta {
	margin-block: 0;
}
.app-header .btn--sm .wp-element-button {
	min-height: 40px;
	padding-block: var(--space-2);
	padding-inline: var(--space-5);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 600;
}

/* Narrow desktop / tablet: tighten before the ≤782px swap to the mobile bar. */
@media (max-width: 1024px) {
	.wp-block-group.app-header {
		gap: var(--space-4);
	}
	.app-header .app-nav {
		gap: var(--space-3);
	}
}

/* ---- 4d. Mobile bar label (companion to §14b) ------------------------------
   The desktop header is swapped for `parts/header-mobile.html` at ≤782px, so
   the mobile bar IS the header on a phone. Its account link ships an icon plus
   a visible "Log in" label, and `.arneiron-account-toggle__screen` was left as
   an empty rule ("a way to also show the word if needed"), so the word wrapped
   onto two lines and broke the one-row layout the bar is specified as. Hide it
   the accessible way — the link already carries an aria-label.
   -------------------------------------------------------------------------- */
.arneiron-mobile-bar .arneiron-account-toggle__screen {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* ---- 4e. Mobile bar: admin bar, bleed, icons, hamburger --------------------
   Four fixes to the bar that became the phone header when §14b's swap started
   working again. Each is scoped so nothing above 782px moves.
   -------------------------------------------------------------------------- */

@media (max-width: 782px) {
	/* (1) The WP admin bar is chrome for the editor, not for a phone: it eats
	   46px of a small viewport and pushes the header down. WordPress reserves
	   that space with its own `html { margin-top: 46px !important }` at this
	   width, so `!important` alone is not enough to reclaim it — `:root`
	   (specificity 0,1,0) outranks `html` (0,0,1) and wins whatever the
	   stylesheet order turns out to be. */
	#wpadminbar {
		display: none !important;
	}
	:root {
		margin-top: 0 !important;
	}
	:root body.admin-bar {
		margin-top: 0 !important;
	}

	/* (2) Edge to edge. The bar picks up the `.site-header` width primitive
	   from §4 — `min(100% - 2 * page-pad, 1280px)` plus `margin-inline: auto` —
	   which insets and centres it. That is right for content and wrong for
	   chrome: a header bar runs to the edges of the screen. The block-gap
	   margin above it goes too, so the bar sits flush at the top. */
	.header-mobile.arneiron-mobile-bar {
		width: 100%;
		max-width: none;
		margin: 0;
	}
	.header-mobile.arneiron-mobile-bar.has-global-padding {
		padding-inline: 0; /* the row pads itself; this would double it */
	}
	.wp-site-blocks > :has(> .header-mobile.arneiron-mobile-bar),
	.wp-site-blocks > .header-mobile.arneiron-mobile-bar {
		margin-block-start: 0;
	}
}

/* (3) Icons. The chips emit inline SVG now (inc/shortcodes.php) instead of
   emoji, so they need a box: emoji were sized with `font-size`, which does
   nothing to an <svg>. 20px matches the optical weight of the desktop
   header's account and cart glyphs at the bar's smaller row height. */
.arneiron-mobile-bar__icons svg {
	display: block;
	width: 30px;
	height: 30px;
}
.arneiron-account-toggle__icon,
.arneiron-cart-chip__icon,
.arneiron-wallet-chip__icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	color: var(--c-ink-2);
	font-size: inherit; /* supersedes the 1.1em that sized the old emoji */
}
.arneiron-mobile-bar__wallet:hover .arneiron-wallet-chip__icon,
.arneiron-mobile-bar__cart:hover .arneiron-cart-chip__icon,
.arneiron-mobile-bar__account:hover .arneiron-account-toggle__icon {
	color: var(--c-ink);
}
.arneiron-wallet-chip {
	gap: 6px;
}

/* (4) The hamburger. §10.5's note says "the theme already styles .nav-toggle" —
   it does not: `nav-toggle` appears in no rule in any of the theme's five
   stylesheets, which is why the button rendered as a small empty box. Drawn
   here as one bar plus two pseudo-elements.

   The open state keys on `body.mobile-drawer-open`, which is what
   theme.js bindMobileMenu() actually sets. It does NOT update aria-expanded on
   the hamburger, so an `[aria-expanded="true"]` selector would look correct and
   never fire. */
.nav-toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	padding: 0;
	background: none;
	border: 0;
	border-radius: var(--r-md);
	color: var(--c-ink);
	cursor: pointer;
}
.nav-toggle:hover {
	background: var(--c-surface-2);
}
.nav-toggle:focus-visible {
	outline: 2px solid var(--c-accent-strong);
	outline-offset: 2px;
}
.nav-toggle__box {
	position: relative;
	display: block;
	width: 33px;
	height: 24px;
}
.nav-toggle__bar,
.nav-toggle__bar::before,
.nav-toggle__bar::after {
	position: absolute;
	left: 0;
	width: 100%;
	height: 3px;
	background: currentColor;
	border-radius: 2px;
}
.nav-toggle__bar {
	top: 50%;
	transform: translateY(-50%);
}
.nav-toggle__bar::before {
	content: "";
	top: -10px;
}
.nav-toggle__bar::after {
	content: "";
	top: 10px;
}

/* Open: the middle bar goes, the outer two cross. */
body.mobile-drawer-open .nav-toggle__bar {
	background: transparent;
}
body.mobile-drawer-open .nav-toggle__bar::before {
	top: 0;
	transform: rotate(45deg);
}
body.mobile-drawer-open .nav-toggle__bar::after {
	top: 0;
	transform: rotate(-45deg);
}

/* ---- 4f. Mobile bar brand: title as the logo fallback ----------------------
   The bar's brand slot held `wp:site-logo` alone, and core renders that block
   to an empty string when no logo is set — so on a site without one the left
   third of the bar was blank. `parts/header-mobile.html` now carries a
   site-title next to it as the fallback, and the logo wins whenever it exists:
   `:has()` matches only when the logo block actually rendered, which is the
   same condition core uses to decide whether to emit it at all.
   -------------------------------------------------------------------------- */
.arneiron-mobile-bar__brand:has(.wp-block-site-logo) .arneiron-mobile-bar__title {
	display: none;
}
.arneiron-mobile-bar__title {
	margin-block: 0;
	font-size: var(--wp--preset--font-size--base);
	font-weight: 700;
	line-height: 1.2;
	letter-spacing: -0.01em;
	/* One line, whatever the site is called. */
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.arneiron-mobile-bar__title a {
	color: var(--c-ink);
	text-decoration: none;
}
.arneiron-mobile-bar__title a:hover,
.arneiron-mobile-bar__title a:focus-visible {
	color: var(--c-accent-strong);
}

/* The icon cluster must never wrap. `__icons-inner` is a plain <div> from the
   wp:html block, so its children are inline boxes that break like text — at
   30px icons plus a wallet amount that happens at ~390px, one phone width
   below where it was tested. Make it a nowrap flex row and let the brand
   yield the space instead (a flex item will not shrink below its content
   without `min-width: 0`). */
.arneiron-mobile-bar__icons-inner {
	display: flex;
	align-items: center;
	flex-wrap: nowrap;
	gap: var(--space-2, 4px);
}
.arneiron-mobile-bar__icons {
	flex: 0 0 auto;
}
.arneiron-mobile-bar__brand {
	min-width: 0;
	flex: 0 1 auto;
}

/* ---- 4g. Footer chrome (.app-footer) ---------------------------------------
   Restored 2026-08-12 — hub#4317. This is the SAME defect §4c fixed for the
   header, in the same consolidation commit, left undone on the footer.

   The footer's visual layer used to live in the `arneiron/footer` BLOCK
   stylesheet, `blocks/build/footer/style.css`, with every selector scoped to
   the block wrapper `.wp-block-arneiron-footer`. Block consolidation
   (642e9958fc, "header and footer become template parts, not blocks") removed
   the block — correct, a footer is theme chrome — and flattened
   `parts/footer.html` into a core/group that kept the INNER class names
   (app-footer / status / status--ok) but not the wrapper. Every rule in that
   stylesheet stopped matching.

   Rules below are RECOVERED from that deleted file at ref 8c80117d96
   (= 642e9958fc^), path design/theme-build/arneiron/blocks/build/footer/style.css,
   re-scoped to `.app-footer` and re-pointed at the §1 aliases. They are not
   invented.

   Measured on dev.arneiron.net BEFORE this block, at 1440/1280/768/390:
     .app-footer  width = viewport width at every breakpoint (1440/1280/768/390)
                  background transparent, border-top 0px, padding 0px
     .app-header  width = 1360 / 1216 / (swapped for the mobile bar at <=782)
     .app-footer .status  color = ink, font-weight 400, ::before content = none
   i.e. an unstyled full-bleed row, exactly as the header rendered before §4c.

   Deliberately NOT restored (same reasoning §4c used for `.menu-toggle`):
     - `.footer-cols` / `.footer-col` — those came from the block's `columns`
       attribute. The flattened part cannot emit that markup, so restoring the
       rules would ship CSS that matches nothing and cannot be verified.
       Re-add them WITH the markup if footer columns come back.

   The `> span` rule from the block is dropped for the same reason: the part
   emits <p>, and core's `.is-layout-flex > * { margin: 0 }` already does the
   job that rule did.
   -------------------------------------------------------------------------- */

/* Two-class, matching §4c: it must win against core's single-class
   `.wp-container-core-group-is-layout-*` gap declaration. */
.wp-block-group.app-footer {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-5);
	padding-block: var(--space-6);
	color: var(--c-ink-2);
	font-size: var(--wp--preset--font-size--sm);
}

/* Band on the wrapper, content on the row — the header's counterpart above.
   `margin-top` moves WITH the paint: left on the row it would push the row down
   INSIDE an already-painted band, opening a strip of footer colour above the
   content. On the wrapper it separates the band itself from the page. */
footer.wp-block-template-part:has(> .app-footer) {
	margin-top: var(--section-rhythm);
	background: var(--c-surface);
	border-top: 1px solid var(--c-border);
}

/* The block's `padding: <space-6> <page-pad>` is NOT carried over: the part now
   also carries `site-footer`, and §4's layout primitive already applies the
   page gutter through `width: min(100% - 2 * page-pad, wide-size)`. Keeping the
   inline padding as well would gutter the row twice — the same double-spacing
   trap documented for the account nav in woocommerce.css. */

.app-footer p {
	margin-block: 0;
}
.app-footer a {
	color: var(--c-ink-2);
	text-decoration: none;
}
.app-footer a:hover,
.app-footer a:focus-visible {
	color: var(--c-ink);
	text-decoration: underline;
}
.app-footer a:focus-visible {
	outline: 2px solid var(--c-accent-strong);
	outline-offset: 2px;
	border-radius: var(--r-sm);
	text-decoration: none;
}

/* Status line — muted semantic accents. The dot is decorative; the wording
   carries the meaning, so colour is never the sole signal. */
.app-footer .status {
	flex: 0 0 auto;
	margin-inline-start: auto;
	font-weight: 600;
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
}
.app-footer .status::before {
	content: "";
	width: 0.5em;
	height: 0.5em;
	border-radius: var(--wp--custom--radius--pill);
	background: currentColor;
	flex: 0 0 auto;
}
.app-footer .status--ok    { color: var(--c-success); }
.app-footer .status--warn  { color: var(--c-warning); }
.app-footer .status--alert { color: var(--c-danger); }
.app-footer .status--dim   { color: var(--c-ink-3); }

/* Phone: the row stacks, so `margin-inline-start: auto` on the status would
   leave it stranded against the right edge under the left text. */
@media (max-width: 600px) {
	.wp-block-group.app-footer {
		gap: var(--space-3);
	}
	.app-footer .status {
		margin-inline-start: 0;
	}
}

/* ---- 4h. Language switcher (Polylang) --------------------------------------
   Two shapes from one shortcode (inc/shortcodes.php):
     - `.arneiron-lang--dropdown` in the desktop header, sitting in the control
       cluster immediately BEFORE my-account.
     - `.arneiron-lang--list` at the bottom of the mobile drawer, where the page
       menu lives.

   Both no-op to zero markup when Polylang is inactive or a single language is
   configured, so `.arneiron-lang-seam` can be an empty div. It is therefore
   `display: contents` — an empty seam must not eat a flex `gap` in the header
   row, which is what a plain block-level wrapper would do. `contents` also
   hoists the switcher itself to be a direct flex child of `.app-header`, so the
   ordering rules below can address it.
   -------------------------------------------------------------------------- */

.arneiron-lang-seam {
	display: contents;
}

/* Visual order of the header row is pinned here rather than left to DOM order.
   The account and cart chips arrive in one of two ways depending on the site:
   authored into a database-stored template part (what dev.arneiron.net has, as
   of the 0.7.0 header work), or injected by WooCommerce Block Hooks on a site
   whose part is still the theme file. Those two paths do not guarantee the same
   sibling order, and the switcher has to land before my-account either way.

   `.app-header > *` seeds a middle value so a block someone adds to the header
   later lands between the menu and the controls, instead of jumping to the
   front the way an unset `order: 0` would. */
.app-header > * { order: 2; }
.app-header > .brand { order: 0; }
.app-header > .app-nav { order: 1; }
.app-header > .app-header-search { order: 2; }
/* Descendant combinator, deliberately. The seam above is `display: contents`,
   which removes its BOX but not its NODE — selectors match the DOM tree, so
   `.app-header > .arneiron-lang` would never match, while `.arneiron-lang` is
   nevertheless the real flex item. With a child combinator here the switcher
   falls back to `order: 0` and renders at the far left beside the logo. */
.app-header .arneiron-lang { order: 3; }
/* CHILD, not descendant — unlike the language seam this one is a real box.
   `.arneiron-account-menu` is `position: absolute`, so its seam has to be the
   positioning context; `display: contents` would delete that box and the
   dropdown would anchor to the header instead, opening across the whole bar. */
.app-header > .arneiron-account-seam { order: 4; }
.app-header > .wp-block-woocommerce-customer-account { order: 4; }
.app-header > .wp-block-woocommerce-mini-cart,
.app-header > .wc-block-mini-cart { order: 5; }
.app-header > .app-header-cta { order: 6; }

/* --- Dropdown (desktop header) --- */
.arneiron-lang--dropdown {
	position: relative;
	display: inline-flex;
	align-items: center;
}

/* Matches the 44x36 target the account and cart chips use (§4c), so the three
   controls read as one cluster. */
.arneiron-lang-toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-1);
	min-height: 44px;
	min-width: 36px;
	padding-inline: var(--space-1);
	background: none;
	border: 0;
	cursor: pointer;
	color: var(--c-ink-2);
	font: inherit;
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 500;
	line-height: 1;
}
.arneiron-lang-toggle:hover { color: var(--c-ink); }
.arneiron-lang-toggle:focus-visible {
	outline: 2px solid var(--c-accent-strong);
	outline-offset: 2px;
	border-radius: var(--r-sm);
}
.arneiron-lang-toggle__icon {
	display: inline-flex;
	width: 1.25em;
	height: 1.25em;
}
.arneiron-lang-toggle__icon svg { width: 100%; height: 100%; }

.arneiron-lang-menu {
	position: absolute;
	top: 100%;
	right: 0;
	z-index: 50;
	min-width: 160px;
	margin: 0;
	padding: var(--space-1);
	list-style: none;
	background: var(--c-bg);
	border: 1px solid var(--c-border);
	border-radius: var(--r-sm);
	box-shadow: 0 2px 8px rgb(0 0 0 / 12%);
}
.arneiron-lang-menu[hidden] { display: none; }
.arneiron-lang-menu__item { margin: 0; }
.arneiron-lang-menu__link {
	display: flex;
	align-items: center;
	gap: var(--space-2);
	padding: var(--space-2) var(--space-3);
	border-radius: var(--r-sm);
	color: var(--c-ink-2);
	font-size: var(--wp--preset--font-size--sm);
	text-decoration: none;
	white-space: nowrap;
}
.arneiron-lang-menu__link:hover,
.arneiron-lang-menu__link:focus-visible {
	background: var(--c-surface);
	color: var(--c-ink);
}
.arneiron-lang-menu__item.is-current .arneiron-lang-menu__link {
	color: var(--c-accent-strong);
	font-weight: 600;
}
.arneiron-lang-menu__code {
	min-width: 2.25em;
	font-weight: 600;
	font-size: var(--wp--preset--font-size--xs, 0.75rem);
	letter-spacing: 0.04em;
	color: var(--c-ink-3);
}
.arneiron-lang-menu__item.is-current .arneiron-lang-menu__code { color: inherit; }

/* --- List (mobile drawer) --- */
.arneiron-lang-seam--drawer {
	display: block;
	margin-top: var(--space-4);
	padding-top: var(--space-4);
	border-top: 1px solid var(--c-border);
}
.arneiron-lang--list {
	display: flex;
	align-items: center;
	gap: var(--space-3);
}
.arneiron-lang--list .arneiron-lang__icon {
	display: inline-flex;
	width: 24px;
	height: 24px;
	color: var(--c-ink-3);
}
.arneiron-lang--list .arneiron-lang__icon svg { width: 100%; height: 100%; }
.arneiron-lang-list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2);
	margin: 0;
	padding: 0;
	list-style: none;
}
.arneiron-lang-list__item { margin: 0; }
.arneiron-lang-list__link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 44px;
	min-width: 44px;
	padding-inline: var(--space-2);
	border-radius: var(--r-sm);
	color: var(--c-ink-2);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 600;
	letter-spacing: 0.04em;
	text-decoration: none;
}
.arneiron-lang-list__link:hover,
.arneiron-lang-list__link:focus-visible {
	background: var(--c-surface);
	color: var(--c-ink);
}
.arneiron-lang-list__item.is-current .arneiron-lang-list__link {
	background: var(--c-surface);
	color: var(--c-accent-strong);
}

/* The drawer is the mobile surface; the desktop dropdown never shows there, and
   the drawer list never shows in the desktop header. Both are already scoped by
   where they are placed, so this is belt-and-braces for a site that pastes the
   shortcode somewhere unexpected. */
@media (max-width: 782px) {
	.app-header .arneiron-lang--dropdown { display: none; }
}

/* ---- 4i. Header search and the desktop account control ---------------------
   Two additions to the default header's control cluster, ordered by §4h:

       brand · nav · [search] · language · [account] · cart · CTA

   SEARCH is `core/search` in button-only/icon mode, so it is a single 44px
   target in the row rather than a field competing with the navigation for
   width. Core renders its own button and input; only the chrome is restated
   here, in tokens.

   ACCOUNT reuses `arneiron_account_control()` — the same builder the mobile bar
   uses — so the dropdown, the logged-out branch and the disclosure JS are one
   implementation, not two.

   ⚠ The account SEAM is a real box, deliberately. The language seam is
   `display: contents` because an empty seam must not eat the header's flex gap;
   the account seam cannot be, because `.arneiron-account-menu` is
   `position: absolute` and needs its seam as the containing block. `:empty`
   handles the no-WooCommerce case instead — the shortcode returns an empty
   string there, and an empty seam is removed rather than left holding a gap.
   -------------------------------------------------------------------------- */

.arneiron-account-seam {
	position: relative;
	display: inline-flex;
	align-items: center;
}
.arneiron-account-seam:empty { display: none; }

/* Match the 44x36 target the language switcher and Woo chips use (§4c/§4h). */
.app-header .arneiron-header__account {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 44px;
	min-width: 36px;
	padding-inline: 0;
	background: none;
	border: 0;
	cursor: pointer;
	color: var(--c-ink-2);
	text-decoration: none;
}
.app-header .arneiron-header__account:hover { color: var(--c-ink); }
.app-header .arneiron-header__account:focus-visible {
	outline: 2px solid var(--c-accent-strong);
	outline-offset: 2px;
	border-radius: var(--r-sm);
}
.app-header .arneiron-header__account .arneiron-account-toggle__icon {
	display: inline-flex;
	width: 1.5em;
	height: 1.5em;
}
.app-header .arneiron-header__account .arneiron-account-toggle__icon svg {
	width: 100%;
	height: 100%;
}
/* The label is carried by aria-label; the icon is the visible control. */
.app-header .arneiron-header__account .arneiron-account-toggle__screen {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* --- Search --- */

.app-header .app-header-search {
	margin: 0;
}
.app-header .app-header-search .wp-block-search__button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 44px;
	min-width: 36px;
	padding: 0;
	background: none;
	border: 0;
	color: var(--c-ink-2);
	cursor: pointer;
}
.app-header .app-header-search .wp-block-search__button:hover { color: var(--c-ink); }
.app-header .app-header-search .wp-block-search__button:focus-visible {
	outline: 2px solid var(--c-accent-strong);
	outline-offset: 2px;
	border-radius: var(--r-sm);
}
.app-header .app-header-search .wp-block-search__button svg {
	width: 24px;
	height: 24px;
	fill: currentColor;
}
/* The expandable variant hides the field until the button is pressed; when core
   reveals it, give it the §9 form-control treatment instead of its own. */
.app-header .app-header-search .wp-block-search__input {
	min-height: 44px;
	padding: var(--space-2) var(--space-3);
	background: var(--c-bg);
	border: 1px solid var(--c-border);
	border-radius: var(--r-sm);
	color: var(--c-ink);
	font: inherit;
	font-size: var(--wp--preset--font-size--sm);
}
.app-header .app-header-search .wp-block-search__input:focus-visible {
	border-color: var(--c-accent);
	outline: none;
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-accent) 25%, transparent);
}

/* ---- 4j. My Account tabs ---------------------------------------------------
   Account details, billing address and payment methods behind one tablist, each
   panel rendered by WooCommerce's own function. Chrome only — the forms inside
   keep the §9 form-control treatment they already inherit.
   -------------------------------------------------------------------------- */

.arneiron-account-tabs__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-1);
	margin-bottom: var(--space-5);
	border-bottom: 1px solid var(--c-border);
}

.arneiron-account-tabs__tab {
	appearance: none;
	background: none;
	border: 0;
	border-bottom: 2px solid transparent;
	margin-bottom: -1px;              /* sit the active rule ON the list border */
	padding: var(--space-3) var(--space-4);
	min-height: 44px;
	color: var(--c-ink-2);
	font: inherit;
	font-weight: 500;
	cursor: pointer;
}
.arneiron-account-tabs__tab:hover { color: var(--c-ink); }
.arneiron-account-tabs__tab:focus-visible {
	outline: 2px solid var(--c-accent-strong);
	outline-offset: -2px;
	border-radius: var(--r-sm) var(--r-sm) 0 0;
}
.arneiron-account-tabs__tab[aria-selected="true"] {
	color: var(--c-accent-strong);
	border-bottom-color: var(--c-accent-strong);
	font-weight: 600;
}

.arneiron-account-tabs__panel[hidden] { display: none; }

/* The dashboard now leads with the sites panel; give it the same rhythm as the
   rest of the account content rather than letting the panel's own margins land
   against WooCommerce's intro paragraph. */
.woocommerce-account .woocommerce-MyAccount-content .arnpx-panel {
	margin-top: var(--space-5);
}

/* ---- 4k. TeraWallet block, in the theme's own language ---------------------
   The wallet ships its own look — dashicons, its own card treatment, its own
   nav-tab styling — which lands inside an account area built from §1 tokens and
   reads as a bolted-on plugin screen.

   Restated here rather than forked: the plugin keeps rendering, and every value
   below is a token, so a Style Variation repaints the wallet with everything
   else. Nothing here changes structure or behaviour; if TeraWallet renames a
   class the rule stops matching and the plugin's own styling returns, which is
   the correct failure direction for third-party chrome.
   -------------------------------------------------------------------------- */

.woo-wallet-my-wallet-container {
	color: var(--c-ink);
}

.woo-wallet-header h2,
.woo-wallet-my-wallet-container h2 {
	margin-block: 0 var(--space-4);
	font-size: var(--wp--preset--font-size--lg);
	font-weight: 700;
	letter-spacing: -0.01em;
}
.woo-wallet-my-wallet-container h3 {
	margin-block: var(--space-5) var(--space-3);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	color: var(--c-ink-3);
}

/* --- Balance card --- */

.woo-wallet-balance-card {
	background: var(--c-surface);
	border: 1px solid var(--c-border);
	border-radius: var(--r-md, var(--r-sm));
	padding: var(--wp--preset--spacing--6);
}
.woo-wallet-price,
.woo-wallet-balance-card .woocommerce-Price-amount {
	font-size: var(--wp--preset--font-size--xl, 2rem);
	font-weight: 700;
	color: var(--c-ink);
	letter-spacing: -0.02em;
}

/* --- Stat cards --- */

.woo-wallet-stat-card {
	background: var(--c-bg);
	border: 1px solid var(--c-border);
	border-radius: var(--r-sm);
	padding: var(--wp--preset--spacing--5);
}
.woo-wallet-stat-card__label {
	color: var(--c-ink-3);
	font-size: var(--wp--preset--font-size--sm);
}
.woo-wallet-stat-card__value {
	color: var(--c-ink);
	font-weight: 700;
}
.woo-wallet-stat-card__icon {
	color: var(--c-ink-3);
}

/* --- The wallet's own nav tabs: match §4j's tablist so the two do not read as
       two different UIs stacked on each other. --- */

.woo-wallet-nav-tabs {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-1);
	margin-block: var(--space-4);
	border-bottom: 1px solid var(--c-border);
}
.woo-wallet-nav-tab {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	min-height: 44px;
	padding: var(--space-3) var(--space-4);
	margin-bottom: -1px;
	border-bottom: 2px solid transparent;
	color: var(--c-ink-2);
	font-weight: 500;
	text-decoration: none;
}
.woo-wallet-nav-tab:hover { color: var(--c-ink); }
.woo-wallet-nav-tab:focus-visible {
	outline: 2px solid var(--c-accent-strong);
	outline-offset: -2px;
}
.woo-wallet-nav-tab.active,
.woo-wallet-nav-item-wrapper.active .woo-wallet-nav-tab {
	color: var(--c-accent-strong);
	border-bottom-color: var(--c-accent-strong);
	font-weight: 600;
}
/* Dashicons carry their own 20px box and baseline; align them to the label. */
.woo-wallet-nav-tab .dashicons {
	width: 20px;
	height: 20px;
	font-size: 20px;
	line-height: 1;
}

/* --- The portal's usage panel sits directly under the wallet in the same tab;
       give its heading the same weight as the wallet's, not an h1's. --- */

.woocommerce-MyAccount-content .arnpx-portal__title {
	margin-block: var(--space-6) var(--space-3);
	font-size: var(--wp--preset--font-size--lg);
	font-weight: 700;
	letter-spacing: -0.01em;
}

/* ---- 4l. Sticky footer ------------------------------------------------------
   A page with little content left the footer floating mid-screen with empty
   space beneath it. Measured on dev at 1440x900: footer bottom at y=372, so
   528px of nothing below it.

   `.wp-site-blocks` is the wrapper WordPress puts around every template part —
   header(s), main, footer — and it was `display: block` with `min-height: 0`,
   so nothing held the page to the viewport.

   Flex column + a growing `main` rather than `margin-top: auto` on the footer:
   the footer carries its own `--section-rhythm` top margin (§4g), and auto
   margin would replace that, collapsing the gap to zero on TALL pages where the
   rhythm is doing real work. Growing main leaves the rhythm intact and only
   adds space when there is space to add.

   `100dvh` after `100vh`, in that order: mobile browsers shrink the visual
   viewport as their chrome hides, and `vh` is the large-viewport value, which
   overshoots and produces a scrollbar on a page that should not have one. A
   browser without `dvh` keeps the `vh` line.
   -------------------------------------------------------------------------- */

.wp-site-blocks {
	display: flex;
	flex-direction: column;
	min-height: 100vh;
	min-height: 100dvh;
}

.wp-site-blocks > main,
.wp-site-blocks > .site-main {
	flex-grow: 1;
}

/* The admin bar is a fixed 32px band ABOVE the document, added as a body offset.
   Without subtracting it, 100vh overflows by exactly that much and every admin
   page gains a scrollbar it does not need. */
body.admin-bar .wp-site-blocks {
	min-height: calc(100vh - 32px);
	min-height: calc(100dvh - 32px);
}

/* Below 782px §4e hides the admin bar and zeroes its offset, so the subtraction
   above would leave a 32px shortfall instead of correcting one. */
@media (max-width: 782px) {
	body.admin-bar .wp-site-blocks {
		min-height: 100vh;
		min-height: 100dvh;
	}
}
