/* =========================================================================
 * Maxi Starter — main.css
 * The single source of truth for reusable theme styling.
 * Loaded on the frontend and inside the block editor.
 * ========================================================================= */

/* -------------------------------------------------------------------------
 * 1. Design tokens
 *
 * The VALUES live once in theme.json (the source of truth for design
 * primitives). This block only RE-EXPORTS the generated WordPress variables
 * under short, ergonomic names so the rest of the CSS — and every pattern —
 * can keep writing var(--color-primary), var(--space-md), etc.
 *
 *   theme.json  →  --wp--preset--*  /  --wp--custom--*  →  these aliases  →  CSS
 *
 * To change a colour, spacing step, width or radius, edit theme.json — not the
 * literals here. Nothing is duplicated, so the two can never drift. Editor-facing
 * colours (text/background/primary) come through as presets; internal-only
 * colours and layout tokens come through as --wp--custom--* (never offered in
 * the editor).
 * ---------------------------------------------------------------------- */
:root {
	/* Palette — the four colours offered in the editor */
	--color-text: var(--wp--preset--color--text);
	--color-background: var(--wp--preset--color--background);
	--color-primary: var(--wp--preset--color--primary);
	--color-secondary: var(--wp--preset--color--secondary);

	/* Internal colours — not editor-selectable */
	--color-muted: var(--wp--custom--color--muted);
	--color-surface: var(--wp--custom--color--surface);
	--color-border: var(--wp--custom--color--border);
	--color-border-alt: var(--wp--custom--color--border-alt);
	--color-primary-dark: var(--wp--custom--color--primary-dark);
	/* Ink over dark surfaces — photo bands, dark cards, an inverted section. White is
	   also `background` in the palette, but this is NOT a background: it is the inverse
	   ink, and it must stay light even if the page ground is ever darkened. Express
	   partial opacities as `color-mix(in srgb, var(--color-on-dark) N%, transparent)`
	   so the colour keeps one source. */
	--color-on-dark: var(--wp--custom--color--on-dark);
	/* Semantic state — invalid (a failed field, an error notice) and affirmative (a
	   confirmation, a ticked feature in a pricing table). Defaults, not brand colours:
	   a site whose palette clashes overrides `error` / `success` in its own theme.json.
	   Semantic colour is separate from the palette on purpose, so "this is wrong" and
	   "this is included" never depend on which brand hue happens to be primary. */
	--color-error: var(--wp--custom--color--error);
	--color-success: var(--wp--custom--color--success);

	/* Layout widths.
	   --reading-width = the reading measure (the Narrow template's text column),
	     from settings.custom.readingWidth (no WordPress equivalent).
	   --container-width = the .site-container cap (the site's normal band width). It
	     ALIASES WordPress's own layout.contentSize (--wp--style--global--content-size)
	     so the site width has ONE source of truth: set contentSize in theme.json and
	     both WordPress's constrained content AND the theme's .site-container follow it
	     — no duplicate value, no drift. (87.5rem is only a fallback for the rare
	     context where that var is absent; it equals the default 1400px.) WordPress's
	     layout.wideSize stays the separate "wide" alignment tier. */
	--reading-width: var(--wp--custom--reading-width);
	--container-width: var(--wp--style--global--content-size, 87.5rem);
	--gutter: var(--wp--custom--gutter);
	--sidebar-width: var(--wp--custom--sidebar-width);

	/* Spacing scale — ONE source, declared in theme.json. Each step is written
	   DIRECTLY as the value you want: a pure vw (fully liquid — set `xl: 4vw` to match
	   a 4vw gutter), a px/rem, or a simple clamp(min, Nvw, max) to bound it — no slope
	   math. Because the value lives in theme.json, BOTH the editor presets
	   (--wp--preset--spacing--*, a block's gap/padding/margin) AND these component
	   aliases are that same value: no fixed/fluid split, no drift. md/lg/xl are fluid;
	   xs/sm stay fixed for exact small gaps. Plain aliases below. */
	--space-xs: var(--wp--preset--spacing--xs);
	--space-sm: var(--wp--preset--spacing--sm);
	--space-md: var(--wp--preset--spacing--md);
	--space-lg: var(--wp--preset--spacing--lg);
	--space-xl: var(--wp--preset--spacing--xl);

	/* Surfaces (composites stay here — they combine primitives, not define them) */
	--radius: var(--wp--custom--radius);
	--border: 1px solid var(--color-border);
}

/* -------------------------------------------------------------------------
 * 1a. Breakpoints (the theme's responsive vocabulary)
 *
 * Two responsive strategies, by design:
 *   • FLUID by default — tokens (spacing above, heading clamps in §3) and layout
 *     primitives (.site-container) scale continuously, so most things need NO
 *     breakpoint at all.
 *   • BREAKPOINTS only for LAYOUT SHIFTS fluid can't express — stacking columns,
 *     collapsing a sidebar, swapping the nav. Use ONE of these three widths so the
 *     whole theme shifts at consistent, predictable points:
 *
 *       sm   40rem   640px    small phone → larger phone
 *       md   48rem   768px    phone → tablet   (desktop nav / mega menu appear)
 *       lg   64rem   1024px   tablet → desktop (sidebar goes two-column)
 *
 * CSS @media can't read custom properties, so these live as a documented
 * convention (rem, so they respect the user's font-size), not runtime tokens.
 * Prefer max-width for "…and below", min-width for "…and up". Don't invent new
 * breakpoints — reach for fluid first, then the nearest of these three. The
 * mobile-nav block's breakpoint option uses the same md/lg widths.
 * ---------------------------------------------------------------------- */

/* -------------------------------------------------------------------------
 * 2. Reset & base
 * ---------------------------------------------------------------------- */
*,
*::before,
*::after {
	box-sizing: border-box;
	/* Squared-off by default: no element is rounded unless a component opts in by
	   setting its own radius. The theme's components use the --radius token (0 by
	   default); raise --radius, or set border-radius on a specific component, to
	   opt in. This low-specificity reset is overridden by any real radius rule, so
	   opt-in (e.g. a Rounded image style) still works. */
	border-radius: 0;
}

body {
	margin: 0;
	color: var(--color-text);
	background: var(--color-background);
	/* The body's font-family, font-size and line-height are owned by theme.json
	   (styles.typography: fontFamily, fontSize, lineHeight), so a child re-skins
	   type through its tokens — the documented workflow. The parent's theme.json
	   sets the defaults (system-ui, the "base" preset, 1.6). Do NOT set any of the
	   three here: a value in this file would shadow the child's theme.json, and
	   changing it there would silently do nothing. */
	-webkit-font-smoothing: antialiased;
}

img {
	max-width: 100%;
	height: auto;
}

a {
	color: var(--color-primary);
	text-underline-offset: 0.15em;
}

/* -------------------------------------------------------------------------
 * 2a. Miscellaneous utility classes
 * ---------------------------------------------------------------------- */
.has-radius {
	border-radius: var(--radius)!important;
}

/* -------------------------------------------------------------------------
 * 3. Typography
 *
 * Headings are a COMPONENT: their sizes are owned here as bespoke clamp()s and
 * are the source of truth for structural heading scale. They deliberately do NOT
 * read the theme.json font-size presets (--wp--preset--font-size--*), which are a
 * separate system for one-off, author-picked content sizes. Resize headings by
 * editing the rules below; editing theme.json font sizes will not move them.
 * See AGENTS.md → "Typography: two systems, one boundary".
 * ---------------------------------------------------------------------- */
h1,
h2,
h3,
h4 {
	margin: 0 0 var(--space-sm);
	line-height: 1.15;
	font-weight: 700;
	letter-spacing: -0.01em;
}

h1 {
	font-size: clamp(2.2rem, 1.4rem + 3vw, 3.4rem);
}

h2 {
	font-size: clamp(1.8rem, 1.3rem + 2vw, 2.5rem);
}

h3 {
	font-size: clamp(1.3rem, 1.1rem + 1vw, 1.6rem);
}

p {
	margin: 0 0 var(--space-sm);
}

/* -------------------------------------------------------------------------
 * 4. Layout primitives
 * ---------------------------------------------------------------------- */

/* Horizontal container: centers content, caps width, keeps a gutter. */
.site-container {
	width: min(100% - (var(--gutter) * 2), var(--container-width));
	margin-inline: auto;
}

/*
 * Full-width utility: break an element out of a .site-container cap so it spans
 * the full width of its (full-width) flow parent. It cancels the container's
 * width/centering — no viewport units, so it only ever spans the real content
 * area and never causes a horizontal scrollbar. Meaningful only inside a
 * full-width parent (e.g. the archive's .product-archive-page). Toggle a band
 * between contained and full-bleed by adding/removing this class. Declared after
 * .site-container so it wins on the shared (0,1,0) specificity.
 */
.fullwidth {
	width: calc(100% + (var(--gutter) * 2));
	transform: translateX(calc(-1 * var(--gutter)));
	max-width: none;
	margin-inline: 0;
}

/* Vertical rhythm for full-width bands and content blocks. */
.section {
	padding-block: var(--space-xl);
	/* Sections own their vertical rhythm via padding-block; kill the block-gap
	   margin core adds between constrained-layout children so it isn't
	   double-counted on top of the padding. (:where() blockGap has 0 specificity.) */
	margin-block: 0;
}

.section--sm {
	padding-block: var(--space-lg);
}

.section--lg {
	padding-block: calc(var(--space-xl) + var(--space-md));
}

.section--center {
	text-align: center;
}

/* Tinted band — alternate a section from the default white background. */
.section--surface {
	background: var(--color-surface);
}

.section__title-wrapper {
	margin-bottom: var(--space-lg, 4rem);
}

.section__title {
	margin-bottom: var(--space-md);
}

/*
 * Main content wrapper for the non-sidebar templates (page, page-narrow, single,
 * index, 404). These put content straight into .site-main with no
 * .site-container, so .site-main itself carries the horizontal gutter — giving
 * every page one consistent gutter site-wide, matching the .site-container
 * gutter used by full-width sections and the sidebar templates.
 *
 * Full-width (.alignfull) blocks get a compensating negative margin so their
 * backgrounds still break out edge to edge past the gutter. The values match
 * (padding var(--gutter) ↔ margin calc(-1 * var(--gutter))), so full-bleed is
 * exact and no horizontal overflow is introduced. Sidebar/archive templates use
 * .site-container as their main instead, so they are unaffected by these rules.
 */
.site-main,
body main#wp--skip-link--target,
.woocommerce main,
.woocommerce-page main {
	display: block;
	padding-inline: var(--gutter);
	margin-block-start:0;
	padding-top:0;
}

.site-main .alignfull {
	margin-inline: calc(-1 * var(--gutter));
}

/*
 * Editor-canvas parity for the gutter above.
 * The editor canvas has no .site-main — page/post content renders directly in
 * .wp-block-post-content.is-root-container. Mirror the .site-main gutter there so
 * editing matches the front end, with the same negative margin so full-bleed
 * (.alignfull) blocks still reach the canvas edges. The selectors deliberately
 * mirror the front-end pair above (descendant .alignfull, not direct-child) so
 * nested full-bleed breaks out identically in both places.
 *
 * .is-root-container is an editor-only class (never on the front end), so these
 * rules are inert there; and main.css is already loaded into the canvas via
 * add_editor_style (inc/setup.php), so no new enqueue is needed. Kept next to the
 * front-end rule so the two halves of the gutter mechanism can't drift apart.
 */
.wp-block-post-content.is-root-container {
	padding-inline: var(--gutter);
}

.wp-block-post-content.is-root-container .alignfull {
	margin-inline: calc(-1 * var(--gutter));
}



/* -------------------------------------------------------------------------
 * 5. Buttons
 * ---------------------------------------------------------------------- */
/*
 * One primary-button appearance for core button surfaces
 * (.wp-block-button__link / .wp-element-button). WooCommerce's classic buttons
 * carry .wp-element-button too, so they inherit this; any WooCommerce-specific
 * button selectors live in assets/css/woocommerce.css.
 *
 * The selectors are intentionally doubled (.x.x) to raise specificity to (0,2,0).
 * WordPress core's global styles ship
 *   :root :where(.wp-element-button, .wp-block-button__link){background:#32373c}
 * which is only (0,1,0) — `:where()` contributes nothing — so a single class
 * merely TIES it and the winner is decided by source order. That order differs
 * between contexts: the frontend loads this file after global styles (theme
 * wins), the editor injects global styles after the theme's editor styles (core
 * wins, buttons turn dark grey). Doubling the class wins outright in both, so
 * the editor matches the frontend. Do not reduce to a single class.
 */
/* Component tokens. Every default resolves to what the button already looked like,
   so this layer is purely additive — nothing renders differently until something
   sets one.

   The point is the OVERRIDE SURFACE. The doubled selectors below are (0,2,0), so a
   child restyling a button used to have to know that and double its own class to
   win; both child themes carry such rules, with a comment saying as much. Setting a
   variable needs no specificity at all — `.site-header__cta { --button-bg: … }` is
   (0,1,0) and still wins, because it is not competing with this rule, it is feeding
   it.

   `--button-border-color` is deliberately NOT declared here. A custom property that
   references another resolves where it is DECLARED, so `--button-border-color:
   var(--button-bg)` on :root would freeze to the root's background and ignore any
   scoped override — the border would keep the old colour while the fill changed.
   Referencing it as a var() FALLBACK inside the rule resolves on the button itself,
   so a child that sets only `--button-bg` gets a matching border for free, and one
   that wants them different simply declares `--button-border-color`. */
:root {
	--button-bg: var(--color-primary);
	--button-color: var(--color-on-dark);
	--button-border-width: 2px;
	--button-radius: var(--radius);
	--button-padding: 0.75em 1.6em;
	--button-font-weight: 600;

	/* Outline variant. Named `outline`, not `secondary`: the block style is called
	   "Secondary" in the editor and there is also a `secondary` palette colour, and
	   the three are unrelated — a token called `--button-secondary-bg` would invite
	   wiring the palette colour into this variant by mistake. */
	--button-outline-bg: transparent;
	--button-outline-color: var(--color-primary);
}

.wp-block-button__link.wp-block-button__link,
.wp-element-button.wp-element-button {
	display: inline-block;
	padding: var(--button-padding);
	border: var(--button-border-width) solid var(--button-border-color, var(--button-bg));
	border-radius: var(--button-radius);
	background: var(--button-bg);
	color: var(--button-color);
	font-weight: var(--button-font-weight);
	line-height: 1.2;
	text-decoration: none;
	cursor: pointer;
	transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

/* No border on the Close button in the Cart drawer */
.wp-element-button.wc-block-components-drawer__close {
	border:none;
	background:none;
}
/* Rotate the button on hover  */
.wp-element-button.wc-block-components-drawer__close:hover {
	transform: rotate(180deg);
	transition: transform 0.3s ease-in-out;
}
/* No shadow inset on the "View Cart" button in the Cart drawer */
.wp-element-button.wc-block-mini-cart__footer-cart {
	box-shadow:none!important;
}

/* Hover re-declares the TOKENS rather than the properties, so there is no parallel
   `--button-*-hover` set to keep in sync — and a child retunes hover the same way it
   retunes rest, by setting the same variable under its own `:hover`. */
.wp-block-button__link:hover,
.wp-block-button__link:focus,
.wp-element-button:hover,
.wp-element-button:focus {
	--button-bg: var(--color-primary-dark);
	--button-color: var(--color-on-dark);
}

/* Outline variant (registered block style, shown as "Secondary" in the editor).
   It only remaps the base tokens, so everything else — padding, radius, weight —
   stays whatever the base is set to. */
.is-style-secondary .wp-block-button__link {
	--button-bg: var(--button-outline-bg);
	--button-color: var(--button-outline-color);
	--button-border-color: var(--button-outline-color);
}

.is-style-secondary .wp-block-button__link:hover,
.is-style-secondary .wp-block-button__link:focus {
	--button-bg: var(--button-outline-color);
	--button-color: var(--color-on-dark);
	--button-border-color: var(--button-outline-color);
}

/* -------------------------------------------------------------------------
 * 5a. Group link (inc/group-link.php)
 * A Group given a URL in its "Group link" panel is rendered as an <a> on the
 * front end — the group's own <div> is renamed, not wrapped, so no extra box
 * enters the layout (a wrapper would break any percentage height on the group).
 *
 * Two things have to be undone, because the element is now an anchor:
 *
 * 1. DISPLAY. <a> is inline. A flex or grid group is unaffected — core's layout
 *    class sets `display` — but a flow/constrained group has no display rule, so
 *    it would collapse to an inline box. Scoped to those two layouts only, so it
 *    can never beat the layout classes that legitimately set display.
 * 2. COLOUR. The card's contents must keep their own colours; without this they
 *    would all inherit the global link colour and underline.
 * ---------------------------------------------------------------------- */
a.wp-block-group.is-layout-flow,
a.wp-block-group.is-layout-constrained {
	display: block;
}

a.wp-block-group {
	color: inherit;
	text-decoration: none;
}

/* -------------------------------------------------------------------------
 * 5b. Text roll — hover effect (.is-text-roll, inc/text-roll.php)
 * A COMPOSABLE effect (an Additional CSS class) on a link, a button, or anything
 * holding them. assets/js/text-roll.js splits each label into characters; each
 * character is a clip (.text-roll__char) around a glyph that GSAP moves up, whose
 * ::after is the copy waiting one line below. None of these classes exist until the
 * script has run, so without JS (or under reduced motion) nothing here applies.
 *
 * The clip must hug the letters, not the line. A clip as tall as a nav's 1.6
 * line box leaves empty bands above and below the text, and a rolling letter shows
 * through them, hanging outside the line mid-roll. So the characters take their
 * own line-height (--text-roll-line-height), plus a little vertical padding for
 * descenders — which is why the copy sits the padding further down than one line,
 * and why the script moves the glyph by -100% AND that padding.
 *
 * The negative margin keeps all of that out of the line box: an inline-block is
 * placed by its baseline, so its margins only decide how much height it claims,
 * and a margin larger than the padding claims none. The surrounding line keeps
 * exactly the height it had, whether its own line-height is above or below ours.
 *
 * The tuning variables live on :root rather than on .is-text-roll: declared on the
 * class, they would block any value set on an ancestor (a header, a nav wrapper).
 * The script reads them from each link. Durations are UNITLESS seconds and the ease
 * is a GSAP ease name — they feed GSAP, not a CSS transition.
 *
 * Underlines do not reach inside inline-blocks, so a link that relies on
 * text-decoration loses it once split; give rolling links no underline, or draw one
 * with a border or background on the link.
 * ---------------------------------------------------------------------- */
:root {
	--text-roll-duration: 0.5;
	--text-roll-stagger: 0.025;
	--text-roll-ease: power3.out;
	--text-roll-from: start;
	--text-roll-leave: reverse;
	--text-roll-line-height: 1.15;
	--text-roll-pad: 0.1em;
}

.text-roll__word {
	white-space: nowrap;
}

.text-roll__char {
	display: inline-block;
	line-height: var(--text-roll-line-height);
	padding-block: var(--text-roll-pad);
	margin-block: calc(var(--text-roll-pad) * -1 - 0.5em);
	/* Clip vertically only; the sideways overhang keeps italic and wide glyphs whole. */
	clip-path: inset(0 -0.15em);
}

.text-roll__glyph {
	display: inline-block;
	position: relative;
}

.text-roll__glyph::after {
	content: attr(data-char);
	position: absolute;
	left: 0;
	top: calc(100% + var(--text-roll-pad));
}

.text-roll__sr {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* -------------------------------------------------------------------------
 * 6. Header
 * ---------------------------------------------------------------------- */
/*
 * The header bar (background + bottom border) sits on the header template-part
 * wrapper. Our own templates add the .site-header class to that wrapper, but
 * WooCommerce's default templates (e.g. Single Product) reference the header
 * part WITHOUT it — so the class alone is unreliable. Also match the wrapper by
 * the .site-header__inner it always contains, so the bar renders consistently on
 * every page regardless of how the template references the part. Scoped to that
 * inner class so it never touches other template parts (e.g. checkout-header).
 */
.site-header,
.wp-block-template-part:has(> .site-header__inner) {
	background: var(--color-background);
	border-bottom: var(--border);
}

.site-header__inner {
	align-items: center;
	gap: var(--space-md);
	padding-block: var(--space-sm);
}

/* Brand lockup: logo + wordmark sit together on the left. */
.site-header__brand {
	align-items: center;
	gap: var(--space-sm);
}

/* Logo size lives in CSS (not a block width attribute) so it stays a reusable
   header treatment rather than an inline style. Applies once a logo image is set;
   until then the Site Logo block renders nothing. */
.site-header__brand .wp-block-site-logo img {
	width: 120px;
	height: auto;
}

.site-header__title a {
	font-size: 1.25rem;
	font-weight: 700;
	color: var(--color-text);
	text-decoration: none;
}

.site-header__nav a {
	color: var(--color-text);
	text-decoration: none;
	font-weight: 500;
}

.site-header__nav a:hover,
.site-header__nav a:focus {
	color: var(--color-primary);
}

/* Default spacing between core Navigation block items (header and footer). */
.wp-block-navigation__container {
	gap: 2rem;
}

/* -------------------------------------------------------------------------
 * 6b. Header behaviour — sticky / auto-hide / transparent (opt-in via body class)
 *
 * Modes are opt-in through a BODY class set by the site's body_class filter, so
 * there are NO per-template edits and no override surface:
 *   • header-sticky      pin the bar to the top (CSS only, below).
 *   • header-auto-hide   pin + hide on scroll-down, show on scroll-up
 *                        (assets/js/header.js toggles .is-hidden).
 *   • header-transparent transparent over the hero → solid once scrolled
 *                        (composes with either; .is-stuck flips it).
 * Static (no class) is the default. Runtime state classes (.is-stuck / .is-hidden)
 * are toggled by assets/js/header.js.
 *
 * SECTION-DRIVEN COLOUR (separate, opt-in per section): a full-width band marked
 * `.header-alt` puts the header into an ALTERNATE state — header.js adds
 * `body.is-header-alt` while that band sits under the bar (flipping as the band's top
 * crosses the header's vertical middle). This file ships NO colours for it: a theme
 * paints `body.is-header-alt .site-header …` however it likes (e.g. white text over
 * dark bands). Seed the class server-side from the first band to avoid a flash.
 * ---------------------------------------------------------------------- */
:root {
	--header-height: 4.5rem; /* pinned-anchor offset + hide distance; refined at runtime by header.js */
	--z-header: 100;         /* sticky stacking (above content); its own open mega panel still lifts to 1000 (§6a) */
}

/* ONE transition list for the bar — every mode contributes a variable to it.
   Three independent features animate this same element: auto-hide's slide,
   transparent's colour fade, and §6a's mega-menu z-index snap. Each used to
   declare its own `transition` SHORTHAND, and a shorthand replaces the whole
   property — so whichever rule won silently discarded the others. With
   header-transparent AND header-auto-hide both on (documented as composing) the
   bar jumped instead of sliding, because the transparent rule came later at
   equal weight and dropped `transform` from the list.

   Composing through variables removes the collision entirely: this is now the
   only place `transition` is declared on the bar, so nothing can clobber
   anything. A property whose value never changes simply never transitions, so
   listing all four costs nothing when a mode is off. Covers the footer part too
   — §6a gives it the same z-index treatment for footer mega menus. */
.site-header,
.site-footer,
.wp-block-template-part:has(> .site-header__inner),
.wp-block-template-part:has(> .site-footer__inner) {
	transition:
		transform var(--header-slide, 0s) ease,
		background-color var(--header-fade, 0s) ease,
		border-color var(--header-fade, 0s) ease,
		z-index 0s var(--header-z-delay, 0s);
}

/* Pinned positioning, shared by sticky + auto-hide. Matched on BOTH the
   .site-header class and the .site-header__inner-bearing wrapper (WooCommerce
   templates reference the header part without .site-header — see §6). Applies at
   every width; higher specificity than §6a's base z-index:0, while §6a's
   open-panel rule (z-index:1000) still wins so an open mega panel sits above the
   scrim. */
body.header-sticky .site-header,
body.header-sticky .wp-block-template-part:has(> .site-header__inner),
body.header-auto-hide .site-header,
body.header-auto-hide .wp-block-template-part:has(> .site-header__inner) {
	position: sticky;
	top: 0;
	z-index: var(--z-header);
}

/* Pinned headers cover in-page anchor targets — offset scrolled-to elements by
   the bar height. :where() keeps specificity 0 so a component can override. */
body.header-sticky :where([id]),
body.header-auto-hide :where([id]) {
	scroll-margin-top: calc(var(--header-height) + var(--space-sm));
}

/* Auto-hide: slide the pinned bar out on scroll-down, back on scroll-up.
   assets/js/header.js toggles .is-hidden on the header element (either the
   .site-header wrapper, or — on WooCommerce templates — the .site-header__inner-
   bearing template part). */
body.header-auto-hide { --header-slide: 0.3s; }

body.header-auto-hide .site-header,
body.header-auto-hide .wp-block-template-part:has(> .site-header__inner) {
	will-change: transform;
}

body.header-auto-hide .site-header.is-hidden,
body.header-auto-hide .wp-block-template-part.is-hidden:has(> .site-header__inner) {
	transform: translateY(-100%);
}

/* Reduced motion kills the slide. The colour fade stays: it is not motion, and
   suppressing it was never this rule's intent — it only ever targeted auto-hide. */
@media (prefers-reduced-motion: reduce) {
	body { --header-slide: 0s; }
}

/* Transparent-over-hero: the bar is transparent until scrolled past the hero,
   then solid (background + border). header.js toggles .is-stuck (past the first
   content block). Composes with sticky and auto-hide.

   Only the transparency is generic here. The visible effect needs the hero to
   sit UNDER the bar — the site pulls its hero up by var(--header-height) and,
   over a dark hero, gives the transparent bar light text (both are design-
   specific, so they live in the site's CSS, not here). On a page without such a
   hero the transparent bar simply shows the page background — harmless. */
body.header-transparent { --header-fade: 0.3s; }

body.header-transparent .site-header,
body.header-transparent .wp-block-template-part:has(> .site-header__inner) {
	background: transparent;
	border-bottom-color: transparent;
}

body.header-transparent .site-header.is-stuck,
body.header-transparent .wp-block-template-part.is-stuck:has(> .site-header__inner) {
	background: var(--color-background);
	border-bottom-color: var(--color-border);
}

/* -------------------------------------------------------------------------
 * 6a. Navigation mega menus
 * A nav item becomes a mega-menu trigger when it carries a `mega-menu-<slug>`
 * class: inc/mega-menu.php nests a block-editable panel inside its <li> as
 *   <div class="mega-panel"><div class="mega-panel__inner">…</div></div>
 * (rendered from the matching synced pattern). The panel is a structural child
 * of the trigger — no id matching. assets/js/mega-menu.js owns open/close and
 * toggles `.is-open`; the trigger stays a real link, so with JS off it just
 * navigates.
 *
 * Two layers: `.mega-panel` is the full-width positioning layer (transparent);
 * `.mega-panel__inner` is the visible box whose width sets the WIDTH MODE. Modes
 * are chosen by an extra class on the trigger link (editor: Additional CSS
 * class), which lands on the <li> and is matched here because the panel is a
 * descendant:
 *   • (default)    content width — capped at --container-width, centred like the page.
 *   • `mega-full`  full-screen width — edge to edge.
 *   • `mega-custom` custom width — capped at --mega-panel-width (default 60rem),
 *                  overridden per menu in CSS, e.g.
 *                  `.mega-menu-brands { --mega-panel-width: 50rem; }`.
 * ---------------------------------------------------------------------- */

/* Affordance: a caret on trigger items that own a panel. */
.wp-block-navigation-item:has(> .mega-panel) > a::after {
	content: "";
	display: inline-block;
	width: 0.4em;
	height: 0.4em;
	margin-left: 0.4em;
	border-right: 2px solid currentColor;
	border-bottom: 2px solid currentColor;
	transform: translateY(-0.15em) rotate(45deg);
}

/* Hidden until JS opens the item. Core sets the nav item to display:flex, so
   let the panel wrap onto its own row when it is in flow (mobile overlay). */
.wp-block-navigation-item:has(> .mega-panel) {
	flex-wrap: wrap;
}

.mega-panel {
	display: none;
	flex-basis: 100%;
}

.wp-block-navigation-item.is-open > .mega-panel {
	display: block;
}

/* Page-dim overlay (scrim) behind an open panel — created by mega-menu.js.
   Hidden by default; shown only at desktop (see the media query below). */
.mega-overlay {
	display: none;
}

/* The visible box. Card styling lives here (not on the positioning layer) so it
   can be resized/reshaped per width mode. */
.mega-panel__inner {
	padding: var(--space-md);
	background: var(--color-background);
	border: var(--border);
	border-bottom-left-radius: var(--radius);
	border-bottom-right-radius: var(--radius);
	box-shadow: 0 12px 32px rgba(24, 24, 24, 0.08);
}

/* Desktop: the panel becomes a dropdown under the header bar.
   The panel spans the full header WRAPPER width so `mega-full` can reach the
   viewport edges; content/custom modes centre a capped inner box instead. Core
   positions the nav item, the nav, and both responsive wrappers (relative) —
   each would otherwise trap this absolute panel to the tiny trigger <li>. We
   clear that whole chain (and .site-header__inner) so the panel's containing
   block resolves up to the full-width .site-header. The responsive-container
   carries core's own (0,3,0) desktop rule, so we mirror its :not() guards and
   add the header-nav class to win at (0,4,0). All scoped + behind the
   breakpoint, so the mobile overlay below 48rem is untouched. */
@media (min-width: 48rem) { /* md — mega-menu goes desktop dropdown */
	/* Positioning context = the full-width header wrapper. Match it by the
	   .site-header__inner it always contains, not the .site-header class — some
	   templates (e.g. WooCommerce Single Product) reference the header part
	   without that class, and without a context here the panel would anchor to
	   the viewport and drop a full screen-height down (top:100%). */
	.site-header,
	.wp-block-template-part:has(> .site-header__inner) {
		position: relative;
	}

	.site-header__inner,
	.wp-block-navigation.site-header__nav,
	.site-header__nav .wp-block-navigation__responsive-container:not(.hidden-by-default):not(.is-menu-open),
	.site-header__nav .wp-block-navigation__responsive-dialog,
	.wp-block-navigation-item:has(> .mega-panel) {
		position: static;
	}

	/* Footer mega menus — the same positioning setup as the header, for the footer
	   template-part wrapper and its nav. Purely additive; the header rules above
	   are untouched. (The mega <li> itself is already neutralised globally in the
	   list above, so it isn't repeated here.) */
	.site-footer,
	.wp-block-template-part:has(> .site-footer__inner) {
		position: relative;
		margin-block-start: 0;
	}

	.site-footer__inner,
	.wp-block-navigation.site-footer__nav,
	.site-footer__nav .wp-block-navigation__responsive-container:not(.hidden-by-default):not(.is-menu-open),
	.site-footer__nav .wp-block-navigation__responsive-dialog {
		position: static;
	}

	/* Keep the header/footer z-index in sync with the overlay's fade so neither
	   flashes. Base: z-index:0 (below the overlay, so an inactive region dims).
	   While its OWN mega panel is open, the region jumps to 1000 (above the
	   overlay); the OTHER region stays at 0 and is dimmed. Every header/footer
	   part is a .wp-block-template-part, so these cover whichever holds the panel.

	   The lift is IMMEDIATE on open, but the drop back is DELAYED 0.2s to match the
	   overlay's fade-OUT — otherwise the active region drops below the still-fading
	   scrim and flashes dim on close. z-index uses a 0s duration, so it snaps
	   rather than interpolating through the overlay's z-index (900). */
	.wp-block-template-part:has(> .site-header__inner),
	.wp-block-template-part:has(> .site-footer__inner) {
		z-index: 0;
		--header-z-delay: 0.2s;
	}

	.wp-block-template-part:has(.wp-block-navigation-item.is-open > .mega-panel) {
		z-index: 1000;
		--header-z-delay: 0s;
	}

	.mega-panel {
		/* display:none at rest so the closed panel adds NO layout box. It is
		   position:absolute with tall content, so visibility:hidden (which keeps
		   the box) would contribute scrollable overflow and stretch the page past
		   the footer. It still fades in/out: `display` is transitioned discretely
		   (allow-discrete) alongside opacity — the panel stays rendered through the
		   fade-out, then flips to none — and @starting-style (below) gives the
		   fade-in its starting opacity. Matches the overlay's fade. */
		display: none;
		position: absolute;
		top: 100%;
		left: 0;
		right: 0;
		/* Above page content — notably WooCommerce's product-gallery trigger
		   (.woocommerce-product-gallery__trigger, z-index:99), which shares this
		   stacking context. Kept well below full-screen overlays/drawers and the
		   WP admin bar so those still cover the header. */
		z-index: 1000;
		pointer-events: none; /* only the visible inner box is interactive */
		opacity: 0;
		transition: opacity 0.2s ease, display 0.2s allow-discrete;
	}

	.wp-block-navigation-item.is-open > .mega-panel {
		display: block;
		opacity: 1;
	}

	/* Fade-in starting point — the panel animates from display:none, so it needs
	   an explicit starting opacity to transition from. */
	@starting-style {
		.wp-block-navigation-item.is-open > .mega-panel {
			opacity: 0;
		}
	}

	/* Invisible hover bridge across the header's bottom padding. Open/close is
	   bound to the nav <li>, whose text sits centred in the bar with empty
	   padding below it — so dropping the cursor toward the panel crosses a dead
	   strip and triggers the close. This transparent strip fills that gap; being
	   part of the panel (a child of the <li>) it counts as the item's hover
	   region, so the move from trigger to panel never "leaves" the item. Rendered
	   only while the panel is open (the panel is display:none otherwise), and
	   pointer-events is re-enabled since the panel layer disables it. */
	.mega-panel::before {
		content: "";
		position: absolute;
		bottom: 100%;
		left: 0;
		right: 0;
		height: var(--space-md);
		pointer-events: auto;
	}

	/* Upward-opening panels (e.g. a footer mega menu). Add `mega-up` to the trigger
	   link: the panel sits ABOVE the trigger (bottom:100% instead of top:100%), the
	   hover bridge moves to the panel's lower edge, and the rounded corners flip to
	   the top so it still reads as connected to the trigger below it. */
	.wp-block-navigation-item.mega-up > .mega-panel {
		top: auto;
		bottom: 100%;
	}

	.wp-block-navigation-item.mega-up > .mega-panel::before {
		top: 100%;
		bottom: auto;
	}

	.wp-block-navigation-item.mega-up > .mega-panel > .mega-panel__inner {
		border-radius: var(--radius) var(--radius) 0 0;
		box-shadow: none;
	}

	/* Default: content width — a centred card capped at --container-width, aligned
	   with the page content. */
	.mega-panel__inner {
		pointer-events: auto;
		width: min(100% - (var(--gutter) * 2), var(--container-width));
		margin-inline: auto;
	}

	/* Full-screen width — edge to edge, dropped corners/side borders for a band. */
	.wp-block-navigation-item.mega-full > .mega-panel > .mega-panel__inner {
		width: auto;
		margin-inline: 0;
		border-inline: 0;
		border-radius: 0;
	}

	/* Custom width — capped at --mega-panel-width (set per menu in CSS). */
	.wp-block-navigation-item.mega-custom > .mega-panel > .mega-panel__inner {
		width: min(100% - (var(--gutter) * 2), var(--mega-panel-width, 60rem));
	}

	/* Page-dim overlay behind an open panel (Nike-style). Fixed full-viewport,
	   above page content but below the header stacking context (z-index:1000), so
	   the bar and panel stay bright and everything else dims.

	   Fade-OUT (this base rule) runs undelayed, in step with the panel on close.
	   Fade-IN is delayed on the .is-active rule below. */
	.mega-overlay {
		display: block;
		position: fixed;
		inset: 0;
		z-index: 900;
		background: rgba(24, 24, 24, 0.4);
		opacity: 0;
		visibility: hidden;
		transition: opacity 0.2s ease, visibility 0s linear 0.2s;
	}

	/* Delay the fade-IN so the panel is mostly opaque before the overlay darkens —
	   otherwise the area the panel covers is seen darkening THROUGH the still-
	   transparent panel. 0.1s overlaps the tail of the panel's 0.2s fade: the panel
	   is ~half opaque when the overlay starts, which reads clean while opening
	   noticeably sooner than a full 0.2s delay. Visibility flips immediately so the
	   scrim is present for the delayed opacity animation. */
	.mega-overlay.is-active {
		opacity: 1;
		visibility: visible;
		/* Fade-IN is a slower, softer 0.5s (vs the 0.2s fade-OUT). */
		transition: opacity 0.5s ease 0.1s, visibility 0s;
	}
}

/* -------------------------------------------------------------------------
 * 7. Footer
 * ---------------------------------------------------------------------- */
.site-footer {
	/* Opaque (body-colour) background, not transparent — so that when the footer's
	   own mega panel lifts it above the page-dim overlay, the overlay doesn't show
	   THROUGH it. Looks the same as no background on a white body, but blocks the
	   scrim when the footer is the active region (matches .site-header). */
	background: var(--color-background);
	border-top: var(--border);
}

.site-footer__inner {
	align-items: flex-start;
	gap: var(--space-lg);
	padding-block: var(--space-lg);
}

.site-footer__copyright {
	margin: 0.5rem 0 0;
	color: var(--color-muted);
	font-size: 0.9rem;
}

.site-footer__nav a {
	color: var(--color-text);
	text-decoration: none;
}

.site-footer__nav a:hover,
.site-footer__nav a:focus {
	color: var(--color-primary);
}

/* -------------------------------------------------------------------------
 * 8a. Image — "Fill" block style (is-style-fill)
 * A block style on core/image (registered in inc/setup.php) that makes the
 * image fill and crop to whatever box its container provides — like a CSS
 * background-image. The <figure> becomes the positioning context and the <img>
 * is absolutely stretched with object-fit: cover.
 *
 * IMPORTANT — the container must have a height. The <img> is out of flow, so the
 * <figure> only has height if its PARENT provides one (a grid/flex cell, an
 * aspect-ratio wrapper, or an explicit height). With no height source the figure
 * collapses to 0 and nothing shows. Sizing that parent is the author's job (e.g.
 * via the parent block's Additional CSS class / a custom rule) — or set the Image
 * block's own Aspect ratio when you need a self-contained ratio.
 *
 * The doubled class (.wp-block-image.is-style-fill, 0,2,0) already beats core's
 * :where()-based image rules, so no !important is needed on the front end. The block
 * EDITOR is the exception — see the editor-only rule below.
 * ---------------------------------------------------------------------- */
.wp-block-image.is-style-fill {
	position: relative;
	width: 100%;
	height: 100%;
	margin: 0;
	overflow: hidden;
}

.wp-block-image.is-style-fill img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Editor only. While editing, Gutenberg's Image block writes an INLINE
   `style="height: auto"` onto the <img>, and an inline declaration outranks every
   stylesheet rule — so the Fill height above silently does nothing in the canvas and
   the photo falls back to its natural ratio inside the frame (tall and top-cropped,
   or short and leaving bare frame). `!important` is the only weight that can beat an
   inline style, so it is used here and ONLY here.
   Deliberately NOT applied on the front end: there is no inline height there, the
   plain rule above already wins, and forcing it would out-rank §8b's parallax
   oversize (which must stay taller than the frame or the drift exposes an edge). */
.editor-styles-wrapper .wp-block-image.is-style-fill img {
	height: 100% !important;
}

/* Optional crop anchors (default is centre) — add as an Additional CSS class on
   the image so a subject near an edge isn't cropped out. */
.wp-block-image.is-style-fill.object-top img {
	object-position: 50% 0;
}

.wp-block-image.is-style-fill.object-bottom img {
	object-position: 50% 100%;
}

/* -------------------------------------------------------------------------
 * 8b. Image — scroll-parallax effect (.is-scroll-parallax)
 * A COMPOSABLE effect (an Additional CSS class, not a block style — so it layers
 * on anything, incl. "Fill"). The figure clips; the <img> is oversized + centred
 * so the vertical scroll drift (GSAP, assets/js/scroll-parallax.js) never exposes
 * an edge. GSAP owns ONLY the translate; the oversize + centring here is pure
 * layout (never a transform, so it can't fight GSAP). Strength is --parallax-shift,
 * a UNITLESS number — yPercent needs a number, and the JS reads the same variable
 * (one source of truth). The oversize needs a definite figure height: standalone,
 * the JS sets the figure's aspect-ratio from the image; on "Fill", the frame
 * supplies it. No JS / reduced motion → the image renders normally (no drift).
 * ---------------------------------------------------------------------- */
.wp-block-image.is-scroll-parallax {
	--parallax-shift: 6;
	overflow: hidden;
}

.wp-block-image.is-scroll-parallax img {
	display: block;
	width: 100%;
	/* Oversize ~2.5x the shift for gap-free headroom at the scroll extremes,
	   and centre the extra via `top` (layout, not transform). */
	height: calc(100% + (var(--parallax-shift) * 2.5%));
	object-fit: cover;
	position: relative;
	top: calc(var(--parallax-shift) * -1.25%);
	will-change: transform;
}

/* Layered on "Fill": Fill makes the <img> position:absolute; inset:0. Re-specify
   it fully here (higher specificity than Fill's rule) so the oversize + centring
   win and the image isn't over-constrained. */
.wp-block-image.is-style-fill.is-scroll-parallax img {
	position: absolute;
	left: 0;
	right: auto;
	bottom: auto;
	top: calc(var(--parallax-shift) * -1.25%);
	width: 100%;
	height: calc(100% + (var(--parallax-shift) * 2.5%));
	object-fit: cover;
}

/* Editor only, and for the same reason as §8a's editor rule: Gutenberg's inline
   `height: auto` on the <img> outranks the height above. §8a's editor rule alone is
   NOT enough here — it pins the image to exactly 100% while the `top` offset above
   still lifts it by half the oversize, so the bottom of the frame is left bare. The
   oversize has to be restated, not just the fill. */
.editor-styles-wrapper .wp-block-image.is-style-fill.is-scroll-parallax img {
	height: calc(100% + (var(--parallax-shift) * 2.5%)) !important;
}

/* -------------------------------------------------------------------------
 * 8c. Icon block (core/icon) — sizing
 *
 * Core ships the wrapper as a plain block-level <div> with NO width, plus
 * `:where(.wp-block-icon) svg { width:100%; height:100% }`. So an icon with no
 * explicit width stretches to the full column, and one WITH a width sits inside a
 * full-width empty box — which reads as a huge whitespace box around the glyph
 * (unmistakable in the editor, where the selection outline draws it).
 *
 * Make the wrapper hug its icon and give the SVG a sane default size. An author's
 * own Width (the block's dimensions support) is emitted inline on the <svg>, so it
 * still wins; a component class (e.g. an icon chip) also out-specifies this.
 * ---------------------------------------------------------------------- */
.wp-block-icon { width: fit-content; }
.wp-block-icon svg { width: 1.5em; height: auto; }

/* Alignment needs the full width back to position within it — core centres with
   flex, so leave that one alone and push the right-aligned case over. */
.wp-block-icon.aligncenter { width: auto; }
.wp-block-icon.alignright { margin-inline-start: auto; }

/* -------------------------------------------------------------------------
 * 9. Post & archive lists
 * ---------------------------------------------------------------------- */
.post-list__item {
	padding-block: var(--space-md);
	border-bottom: var(--border);
}

.entry-meta {
	gap: var(--space-sm);
	margin-bottom: var(--space-md);
	color: var(--color-muted);
	font-size: 0.9rem;
}

/* -------------------------------------------------------------------------
 * 10. Sidebar layout
 * A CSS grid primitive used by the page-sidebar-* templates. Content stays
 * first in the DOM (accessibility); the --left modifier repositions the
 * sidebar visually without changing source order.
 * ---------------------------------------------------------------------- */
.with-sidebar {
	display: grid;
	grid-template-columns: minmax(0, 1fr) var(--sidebar-width);
	gap: var(--space-lg);
	align-items: start;
	padding-block: var(--space-lg);
}

.with-sidebar--left {
	grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
}

.with-sidebar--left > .content-area {
	grid-column: 2;
	grid-row: 1;
}

.with-sidebar--left > .sidebar {
	grid-column: 1;
	grid-row: 1;
}

@media (max-width: 64rem) { /* lg — collapse the sidebar to one column below desktop */
	.with-sidebar,
	.with-sidebar--left {
		grid-template-columns: 1fr;
	}

	/* Single column: reset row/column so items stack in source order
	   (content first, sidebar below). */
	.with-sidebar--left > .content-area,
	.with-sidebar--left > .sidebar {
		grid-column: 1;
		grid-row: auto;
	}
}

/* Sidebar widgets */
.sidebar__widget + .sidebar__widget {
	margin-top: var(--space-lg);
}

.sidebar__title {
	margin-bottom: var(--space-sm);
	font-size: 1.15rem;
}

/* -------------------------------------------------------------------------
 * 11. Block editor overrides
 * ---------------------------------------------------------------------- */
.wp-block-image img {
	width:100%;
}

.wp-block-separator {
    border: none;
    border-top: 1px solid;
}
