/* =============================================================================
   Pinnacle — design tokens. THE canonical source (CLAUDE.md §8.9).

   Every other stylesheet consumes these. No raw colour, font-size, spacing,
   radius or z-index value appears anywhere else (§3.8). The spec linter
   enforces it and exempts only this file.
   ========================================================================== */

:root {
  /* ---- Colour ---------------------------------------------------------- */
  /* A NEUTRAL WHITE SCALE — no cream, no beige. THE PAGE IS PURE WHITE and the
     products carry all the chroma (§8.2); the chrome steps DOWN off it, never
     the other way round. The off-whites below are true neutrals with the
     faintest cool cast — far enough from blue to avoid the SaaS-grey §8.2
     warns about, and never warm, which would read as cream and tint every
     product photo laid on it.

     FOUR NAMES, THREE VALUES. --bg and --surface are both #FFF: on a pure
     white page a card cannot lift by fill, so its 1px --line hairline is what
     separates it — which is what §8.6 says does most of that work anyway.
     They stay separate tokens because they answer different questions ("the
     page" vs "a raised thing") and only one of them can ever move.
         bg / surface  #FFFFFF   the page, and anything raised onto it
         bg-tint       recedes   1.07:1 below --bg — CHROME: the header, field
                                 and chip fills, spec-table zebra, hover fills
         bg-alt        recedes   1.16:1 below --bg — recessed bands, footer
     Ratios are COMPUTED, not estimated.                                     */
  --bg:            #FFFFFF;   /* page — PURE white (§8.2)                    */
  --bg-tint:       #F6F7F8;   /* chrome step — 1.07:1 below --bg. Field/chip
                                 fills, zebra, hover fills. The header runs
                                 --bg (pure white) with a --shadow-sm hairline
                                 shadow instead, so it reads as a raised bar
                                 rather than a recessed band                 */
  --bg-alt:        #ECEEF0;   /* recessed band + footer — 1.16:1 below --bg  */
  --surface:       #FFFFFF;   /* cards, drawers, modals — flush with --bg,
                                 separated by their --line hairline (§8.6)   */
  --line:          #DEE1E4;   /* hairlines, card borders, table rules        */
  --mega-media-border: #171717; /* mega-menu picture frame                  */
  --bg-warm-grey:  #E7E6E3;   /* process dial's recessed surface. Warmer than
                                 --bg-alt (which is blue-grey) so the dial's
                                 ring reads as a machined face rather than
                                 another UI panel. Was a raw hex inside
                                 process-dial.php's <style> block, where §3.8
                                 could not see it                             */
  --bg-cream:      #FBFAF5;   /* DELIBERATE EXCEPTION to the neutral-only rule
                                 above — warm, requested by name for the
                                 guarantee strip (§6.1) specifically. 1.04:1
                                 below --bg, so ink-900/ink-700 text keeps the
                                 same contrast floor as on --bg. Not part of
                                 the neutral scale: don't reach for this on a
                                 section that carries product photography, the
                                 tint-every-photo risk above is exactly why the
                                 scale stays cool everywhere else.            */
  --ink-900:       #191A1B;   /* headings            — 17.25:1 on --bg       */
  --ink-700:       #393B3D;   /* body copy           — 11.22:1 on --bg       */
  --ink-500:       #616467;   /* secondary/meta ONLY — 5.95:1 on --bg, and
                                 5.12:1 on --bg-alt: the floor has to hold in
                                 the BAND too, not just on the page          */
  --accent:        #B3252A;   /* maple red — primary CTA — 6.53:1 w/ white,
                                 and the same 6.53:1 as text on the page.
                                 True red (hue ~358), sat matched to the old
                                 sienna so it sits as brand colour, not alarm */
  --accent-hover:  #8F1D21;   /* 8.88:1 w/ white                             */
  --accent-tint:   #F7E9E9;   /* pressed/highlight fills (compare, counts).
                                 Product-card Free install / Sale badges use
                                 --accent + --surface instead (§5.7, §8.2)   */
  --brass:         #A85C60;   /* hairline + icon detail ONLY, never text.
                                 Muted maple metal: same lightness/sat as the
                                 old gold (#A9885A), hue rotated to the accent
                                 so separator ticks and heading rules follow
                                 the red rather than leftover orange          */
  --canada-red:    #B3252A;   /* home-hero eyebrow + radial wash — same as
                                 --accent so the page is one red, not two    */
  /* WHATSAPP'S OWN BRAND COLOURS, and the only reason they are in the palette
     is that they are not ours to choose: the send control in the floating
     contact box has to read as WhatsApp or it stops telling the visitor what
     happens next (§9.8). Used ONLY on `.floating-chat__send`. They lived as
     raw hex inside a <style> block in floating-cta.php, where the §3.8 "no raw
     colour outside tokens.css" rule could not see them.                     */
  --whatsapp:        #25D366; /* brand green — send button rest              */
  --whatsapp-hover:  #1EBE5D; /* hover, one step down in lightness           */
  --whatsapp-edge:   #128C7E; /* brand teal — leading edge of the send key   */
  --whatsapp-ink:    #102A19; /* 8.9:1 on --whatsapp                         */
  --whatsapp-ink-hover: #081B10; /* 11.4:1 on --whatsapp-hover               */

  --success:       #2C6349;   /* 7.03:1 w/ white                             */
  --error:         #941458;   /* wine/magenta-hued, 8.40:1 w/ white — shifted
                                 further from red now that --accent IS red,
                                 so it still can't read as --accent          */

  /* ---- On-dark (the estimator's own dark card ONLY — §5.10) --------------
     The rest of the site has no dark surface, so nothing above this line was
     built for one: --ink-900 as TEXT is 17.25:1 on white, but --ink-900 as a
     BACKGROUND needs its own light-on-dark pairings, computed the same way
     (WCAG relative luminance) rather than guessed. --accent and --error both
     fall under the 3:1 non-text floor against --ink-900 as a background
     (2.67:1 and 2.07:1) — lightened variants exist for exactly the uses that
     sit directly against that fill; anything with its own opaque light fill
     (a --surface input box, a solid --accent button) keeps reading the
     original token unchanged, since its contrast is against ITS OWN
     background, not the card's. */
  --ink-on-dark:       #FFFFFF; /* primary text on --ink-900 — 17.43:1        */
  --ink-on-dark-muted: #8C8D8D; /* secondary/meta on --ink-900 — 5.24:1       */
  --line-on-dark:      #353536; /* hairline on --ink-900 — 1.42:1, as
                                    deliberately subtle as --line's 1.24:1
                                    on --bg (§8.6)                           */
  --accent-on-dark:    #BE464A; /* focus ring / accent border on --ink-900 —
                                    3.46:1, clears the 3:1 non-text floor    */
  --error-on-dark:     #BF729B; /* error text on --ink-900 — 5.06:1          */

  /* ---- Type ------------------------------------------------------------ */
  /* Bricolage = Display/H1/H2 ONLY; H3 and below use --font-body.           */
  --font-display: "Bricolage Grotesque", system-ui, sans-serif; /* woff2      */
  --font-body:    "Outfit", system-ui, sans-serif;              /* woff2      */

  /* Scale — 12px is the BASELINE UI SIZE and the most common size on the page:
     meta, labels, facet names, captions, card titles, nav. Everything else
     varies UP from it as the role needs (§8.3).

     --fs-body was dropped from 14 to 12 on explicit product instruction,
     overriding the readability rationale that used to live here (paragraphs
     not running at the 12px baseline). It now reads identically to
     --fs-small on mobile — still a separate token because the two answer
     different questions (a paragraph vs. a piece of metadata), and only one
     of them is meant to move if this is revisited. --fs-body is NO LONGER
     FLAT: it steps back up to --fs-body-lg (14) from --bp-lg, on further
     instruction that desktop paragraphs keep the old size — the swap lives
     with every other --bp-lg pair below ("THE BREAKPOINT SWAPS"). Everything
     else below is unchanged:
       · FORM INPUTS ARE PINNED AT 16 in main.css, decoupled from --fs-body.
         Under 16px mobile Safari zooms on focus and never zooms back.
       · Colour discipline matters more at this size, not less: --ink-500 is
         still meta-only, and grey-on-grey body copy is still the anti-pattern
         §8.12 names.                                                        */
  --fs-display: clamp(1.375rem, 1.1rem + 1.2vw, 2rem);      /* 22 → 32       */
  --fs-hero:    clamp(2.5rem, 1.1rem + 3.6vw, 4rem);        /* 40 → 64 — deliberately above the §8.3
                                 Display ceiling for this one hero headline; raised
                                 again once padding-block-end was freed up on
                                 .home-hero__upper to make room for it            */
  --fs-h2:      clamp(1.125rem, 1rem + 0.55vw, 1.4375rem);   /* 18 → 23       */

  /* The section-heading step (§8.3). A top-level section heading is an h2
     semantically, but --fs-h2 is tuned for a heading sitting in a column of
     text, and these sit beside photography, where it does not hold its own.
     --fs-display is the other direction: at 32 it matched the h1 outright on
     interior pages and read as too loud for a heading that repeats six or
     seven times down a page.

     This is an ADDITION to the scale, not a local override, which is the only
     reason it is allowed to exist: a size used by seventeen selectors across
     nine stylesheets is a role, and a role belongs in this file. It keeps the
     §8.3 rule that every adjacent pair differs by at least 3px - 23 · 27 · 32
     at the top end. */
  --fs-section: clamp(1.25rem, 1.05rem + 0.8vw, 1.75rem);    /* 20 → 28       */
  --fs-dial:    clamp(3.4rem, 9vw, 5.2rem);  /* 54 → 83 — the process dial's
                                 step numeral, and the ONE thing on the site
                                 above --fs-hero. It is a single glyph read as
                                 a graphic mark rather than text, sized to fill
                                 the ring; nothing else should reach for it. It
                                 was a raw clamp inside process-dial.php's
                                 <style> block until that CSS moved into a
                                 stylesheet where §8.9 could see it           */
  --fs-h3:      1rem;       /* 16                                            */
  --fs-price:   1.125rem;   /* 18 — detail price + estimator total ONLY, where
                               the price has a column to itself. Cards run it at
                               --fs-h4 in weight 700: in a ~160px tile a step up
                               in size outweighs the product name (§5.7)       */
  --fs-h4:      0.875rem;   /* 14 — the spec-box label size, flat at every
                               width. No longer the same size as --fs-body
                               on mobile (12); matches again from --bp-lg,
                               where --fs-body steps back up to 14           */
  --fs-body:    0.75rem;    /* 12 — paragraphs, base/mobile value. Steps up
                               to --fs-body-lg from --bp-lg (see note above
                               and the breakpoint-swap block below)          */
  --fs-body-lg: 0.875rem;   /* 14 — paragraphs from --bp-lg (desktop). The
                               original flat value, restored above mobile   */
  --fs-small:   0.75rem;    /* 12 — THE BASELINE: meta, labels, facets, nav   */
  --fs-micro:   0.6875rem;  /* 11 — uppercase labels only, never sentences   */

  /* THE LOCKUP HAS ITS OWN TWO STEPS, and it is the one role that sits outside
     the scale (§9.9). It is not text on a page: it is a fixed two-line mark
     that has to hold its proportion inside a 56px header, so it must not move
     when a content role is retuned — borrowing --fs-h2 coupled it to the
     heading scale, and borrowing --fs-price would couple it to the catalogue.
     18/10 is a 1.8x step, inside the 1.6-2.1x range §9.9 requires for the
     wordmark to read as the name and the line under it as a descriptor.
     THE CAPTION IS BELOW --fs-micro ON PURPOSE. §9.9's own warning applies —
     at 10px this is at the edge of legible for the 35-65 buyer (§8.3) — so it
     is a mark to be recognised, never a line to be read. Product-card badges
     sit at the same size on --fs-badge, a separate token, so retuning the
     lockup cannot move the catalogue.                                        */
  --fs-logo:         1.125rem;  /* 18 — wordmark                              */
  --fs-logo-caption: 0.625rem;  /* 10 — descender line                        */
  --fs-badge:        0.625rem;  /* 10 — product-card Free install / Sale only.
                                   Same size as the caption (uppercase mark,
                                   not a sentence) but not the same token.    */

  /* Small type needs MORE leading, not less — it is the compensation that
     keeps a 14px paragraph readable at a 60-70ch measure.                   */
  --lh-display: 1.15; --lh-heading: 1.25; --lh-body: 1.6;  --lh-small: 1.45;

  /* Form inputs never take --fs-body. Pinned here so the number has one home
     and main.css does not carry a literal (§3.8).                           */
  --fs-input:   1rem;       /* 16 — iOS focus-zoom floor, NOT a style choice  */
  --fw-body: 400; --fw-ui: 500; --fw-subhead: 600; --fw-display: 700;
  /* Nothing below 400. Ever. */
  --tracking-micro: 0.04em;

  /* ---- Space (4px grid) ------------------------------------------------- */
  --s-1: 2px;   --s-2: 4px;   --s-3: 8px;   --s-4: 12px;  --s-5: 16px;
  --s-6: 20px;  --s-7: 24px;  --s-8: 32px;  --s-9: 40px;  --s-10: 48px;
  --s-11: 64px; --s-12: 80px; --s-13: 96px; --s-14: 128px; --s-15: 160px;
  /* --s-15 EXISTS FOR ONE CONSUMER: --section-y-tall-xl. The ladder above
     deliberately skips rungs as it climbs (16s to 96, then 32s), and 160 is
     the next 32-step — it is not an invitation to reach for a bigger number
     generally. Nothing below the tall rhythm at --bp-xl should read it. */

  /* ONE GUTTER, BOTH AXES. This pair was previously split — a tighter
     horizontal value and a row value one step above it, on the argument that
     row 1's card CTA bar would otherwise crowd row 2's image. In practice the
     split read as a misalignment rather than as density: the eye takes an
     unequal gap on a square grid as a mistake long before it reads it as
     rhythm. The row value won, so nothing lost vertical air — the columns
     gained it. Still the 4px scale, still one step up at --bp-lg.

     If a specific grid genuinely needs asymmetry, it overrides locally and
     says why (.product-grid in catalogue.css does exactly this) — the shared
     token stays square. */
  --hairline: 1px;       /* canonical structural rule width */

  --grid-gutter:      var(--s-3);   /* 8px mobile                            */
  --grid-gutter-lg:   var(--s-4);   /* 12px desktop                          */

  /* THREE SECTION RHYTHMS, not one (§8.18 #4). Every section at the same
     padding is the generated-site tell — a page needs cadence, not a
     metronome. All three are on the 4px scale.
       compact  — guarantee strip, breadcrumb band, anything that must sit
                  CLOSE to what it qualifies
       standard — the default; grids, rails, most content
       tall     — the one or two moments a page should breathe             */
  /* STEPPED UP ONE NOTCH AT EVERY WIDTH, MOBILE INCLUDED, on explicit product
     instruction that the site should read as more spacious. This is the
     SECOND such step (the first moved 20/32 · 32/64 · 40/80 to the values one
     below the current ones), and it overrides the rationale that used to live
     here: that the mobile values should stay a step tighter than the desktop
     ones scaled down, because vertical space costs more on a phone — it is
     the axis the visitor pays for in scrolling. That argument is not wrong,
     it was weighed and declined; the cost is real and is paid in scroll
     length on a 640px-tall viewport, where a section boundary now takes
     roughly a sixth of the screen on the standard rhythm.

     WHAT DID NOT CHANGE IS THE ONLY PART THAT MUST NOT. Every value moved
     exactly one step on the 4px scale, so the cadence of §8.18 #4 — the RATIO
     between compact, standard and tall — is preserved at every tier, and no
     value left the scale. A future step follows the same rule: move them all
     together, or the three rhythms collapse into a metronome.

     THREE TIERS, NOT TWO. These were the only spacing tokens in the system
     that jumped straight from their base value to their `lg` one, which left
     768-1023px — a tablet, at very nearly desktop width — rendering the phone
     value, and made the step at --bp-lg twice the size of any other in the
     system. A `-md` value now sits between them.

     THERE IS NO `-sm` VALUE, and that is a real limit rather than an
     oversight. The ladder has no distinct step left to give it: compact would
     need a value between 32 and 40, and inventing one puts a section rhythm
     on a rung the scale deliberately skips, to serve a 288px-wide band of
     viewports otherwise identical to the one below it. 480-767 is large
     phones — the same reading context as small ones — so it shares.

     `-xl` DID EARN ITS OWN STEP, on the same test `-sm` fails: 1280px+ is a
     genuinely different screen from a 1024px laptop, not the same one
     slightly wider. It cost one new rung (--s-15) for the tall rhythm; the
     other two landed on rungs that already existed. So base covers through
     767, `-md` the tablet range, `-lg` 1024-1279, `-xl` 1280 up.            */
  --section-y-compact:    var(--s-8);   /* 32px  base                        */
  --section-y-compact-md: var(--s-9);   /* 40px  from --bp-md                */
  --section-y-compact-lg: var(--s-10);  /* 48px  from --bp-lg                */
  --section-y:            var(--s-10);  /* 48px  base — THE DEFAULT          */
  --section-y-md:         var(--s-11);  /* 64px  from --bp-md                */
  --section-y-lg:         var(--s-13);  /* 96px  from --bp-lg                */
  /* --section-y-tall's base equals --section-y-md: a tall band on a phone is
     a standard band on a tablet. Same number, different questions — neither
     token may be collapsed into the other.                                  */
  --section-y-tall:       var(--s-11);  /* 64px  base                        */
  --section-y-tall-md:    var(--s-12);  /* 80px  from --bp-md                */
  --section-y-tall-lg:    var(--s-14);  /* 128px from --bp-lg                */
  /* THE --bp-xl TIER. A 1280px+ monitor is a genuinely different screen from
     a 1024px laptop — it is the one range where the page still read tight
     after the md tier landed — so the rhythms get a fourth step there. This
     is the ONLY token pair in the system that swaps at --bp-xl. */
  --section-y-compact-xl: var(--s-11);  /* 64px  from --bp-xl                */
  --section-y-xl:         var(--s-14);  /* 128px from --bp-xl                */
  --section-y-tall-xl:    var(--s-15);  /* 160px from --bp-xl                */

  /* Card internal padding. A token rather than a literal because the card,
     the category card and the spec box all have to move together — three
     components drifting apart on padding is what makes a grid look assembled
     rather than designed. */
  --card-pad:    var(--s-4);  /* 12px                                        */
  --card-pad-lg: var(--s-5);  /* 16px from --bp-lg                           */

  /* THE ONE OFF-GRID VALUE IN THE SYSTEM, and it is deliberate. The §8.4 scale
     is 4px and 7 is not on it; this is the copyright row's vertical padding,
     set by eye between --s-2 (4) and --s-3 (8). A token rather than a literal
     so it stays greppable as the exception it is — --s-3 is the on-scale
     neighbour if it is ever reconsidered.                                    */
  --footer-bar-y: 7px;

  /* ---- Layout ----------------------------------------------------------- */
  --w-max:   1440px;  /* page container                                      */
  --w-prose: 720px;   /* blog, city pages, FAQ — ~60-70ch                    */
  /* The header is CHROME, not content, and carries more in one row than any
     content block does — logo, eleven nav items with carets, search and a CTA.
     It gets its own wider cap rather than being squeezed into --w-max. Still
     capped rather than full-bleed: edge-to-edge on an ultrawide monitor pushes
     the logo and the CTA a screen apart.                                    */
  --w-header: 1760px;
  --tap-min: 44px;    /* the AAA floor — calendar cells, time wheel rows,
                         mobile nav rows and mega-menu carets ONLY. Every other
                         control reads --field-h below: 36 on touch.         */

  /* THE CUTOUT HERO (destination hero, category hero, generic page hero):
     a full-bleed photo at a FIXED height, never one that grows with the
     copy — the overlapping white card is position:absolute AT EVERY WIDTH
     precisely so its content can never inflate this number.

     BASE IS TALLER THAN --bp-md, deliberately: a phone screen is naturally
     portrait, so the hero photo runs taller than it is wide there, with the
     card anchored along its bottom edge; a desktop viewport is landscape,
     so the photo runs shorter and wider instead, with the card moved to a
     side column. Same mechanism (position: absolute, fixed height), two
     shapes for two screen shapes — see the component rules in main.css and
     catalogue.css for how the card itself adapts. */
  --hero-media-h:    480px;  /* base width — taller, portrait-leaning        */
  --hero-media-h-md: 420px;  /* from --bp-md — shorter, landscape-leaning    */

  /* Homepage FAQ media is fixed-height at every width so source-image aspect
     ratio never changes the composition. Desktop uses a taller landscape
     crop because the FAQ panel is centered against the image beside it. */
  --home-faq-media-h:    640px;  /* base/mobile */
  --home-faq-media-h-lg: 720px;  /* from --bp-lg */

  /* Field height resolves by POINTER CAPABILITY, never width (§8.4): 36px for
     a finger, 32px for a mouse. Fields, selects, the header and mobile search,
     the contact-channel chips, the unit toggle, the compare control, the rail
     arrows and the calendar chevrons all read this pair, so a row of controls
     can never end up half one height and half the other.

     36 IS SET BY THE ONE THING IN THE BOX THAT CANNOT SHRINK. An input holds
     16px of text on touch (--fs-input, below and §8.3) and that is a hard iOS
     floor, not a type choice: under 16 mobile Safari zooms the page on focus
     and never zooms back. 16px of text plus its border needs roughly 28px, so
     36 leaves about 4px of breathing room each side and 32 would leave 2 —
     legible but visibly cramped, and cramped is what a booking form cannot
     afford (§7.2). THE BOX CAME DOWN AS FAR AS THE TEXT INSIDE IT ALLOWS.

     Which is also why this is 4px above --btn-h and not equal to it: a BUTTON
     has no such floor, so it paints at 32 and buys its target back with a
     transparent ::after. An input cannot use that trick — its box has to hold
     the text, not merely be tappable.

     36, NOT the 44 of --tap-min. That floor is WCAG 2.2 AAA (SC 2.5.5); the AA
     requirement this site targets is 24 (SC 2.5.8), so 36 still clears it by
     half again. THE FOUR CONTROLS THAT COST A LEAD ON A MIS-TAP KEEP THE FULL
     44 AND DO NOT READ THIS TOKEN — the appointment calendar cells, the time
     wheel rows, the mobile nav rows and the mega-menu carets read --tap-min
     directly, at every pointer type (§7.2, §9.2, §8.12).                     */
  --field-h:       var(--field-h-touch);
  --field-h-touch: 36px;
  --field-h-fine:  32px;

  /* THE PAINTED HEIGHT OF A BUTTON, and deliberately NOT --field-h. A .btn
     draws at 32 at every pointer type, so it looks identical on a phone and on
     a laptop rather than reading as chunkier under a thumb. On touch the extra
     height needed to clear the 40px target is a TRANSPARENT pseudo-element,
     not padding, so the target grows without the button gaining a pixel of
     visual weight.

     THE BOX IS SMALLER THAN THE TARGET, NEVER THE OTHER WAY ROUND. This is the
     idiom the site already uses twice: the 32px chip inside its 44px calendar
     cell (§7.2) and the bare rail arrows that drop their box but keep
     --field-h as the hit area (§9.11).

     A BUTTON PAIRED WITH A FIELD ON ONE ROW IS THE EXCEPTION and takes
     --field-h instead, or the newsletter Subscribe sits 8px short of the email
     input beside it (§7.4).                                                 */
  --btn-h: 32px;

  /* Card padding steps up once at --bp-lg, where there is room for it. The
     media query that does it lives in main.css; the two values live here so a
     component never invents its own.                                        */
  --gutter-x:    var(--s-5);  /* 16px page side gutter, base                 */
  --gutter-x-sm: var(--s-7);  /* 24px from --bp-sm                           */
  --gutter-x-lg: var(--s-8);  /* 32px from --bp-lg                           */
  --header-h:    56px;        /* 64px from --bp-lg — see §8.13 chrome budget */

  /* ---- Breakpoints (§8.13) ----------------------------------------------
     DECLARED HERE AS THE SOURCE OF THE NUMBERS AND FOR JS TO READ.
     Custom properties are INVALID inside a media query condition:
       @media (min-width: var(--bp-md))  ← never matches, fails silently.
     Write the literal in the @media prefix. These four literals are the ONLY
     raw values allowed in a stylesheet, and only there.                     */
  --bp-sm: 480px;   --bp-md: 768px;   --bp-lg: 1024px;   --bp-xl: 1280px;

  /* ---- Radius (these five) ---------------------------------------------- */
  --r-xs:   0;       /* product-card overlay badges — square, by request     */
  --r-sm:   0;       /* buttons, inputs, chips, facets, product card         */
  --r-md:   0;       /* cards, images, spec boxes, modals, drawer            */
  --r-lg:   0;       /* reserved for a future full-bleed panel               */
  --r-full: 9999px;  /* floating CTA + avatars — the deliberately-round
                        shapes, left alone by request                        */
  /* Nesting: inner radius = outer radius − padding. Never match them.       */

  /* ---- Elevation --------------------------------------------------------- */
  --shadow-sm: 0 1px 2px rgba(28,25,23,.06);        /* resting cards         */
  --shadow-md: 0 8px 24px -8px rgba(28,25,23,.16);  /* hover, drawer, modal  */
  --focus-ring: 1px solid var(--accent);
  --focus-offset: 1px;

  /* ---- Motion ------------------------------------------------------------ */
  --dur-fast: 120ms;  --dur-base: 200ms;  --dur-slow: 320ms;
  --ease: cubic-bezier(.2,.6,.3,1);
  --lift: -2px;  /* button hover translateY — §8.7                           */

  /* Skeleton shimmer loop (§8.15) — a separate, much longer duration than the
     three UI-transition tokens above on purpose. Those time a state change
     that happens once; this times a sweep that repeats for as long as an
     image is loading, and at --dur-slow (320ms) it reads as a strobe rather
     than a shimmer. */
  --dur-shimmer: 1600ms;

  /* The choreography band, used by motion.css (§8.14). These are deliberately
     slower than the three UI tokens above and answer a different question:
     --dur-* times a state change the user just caused, where anything past
     ~320ms feels laggy, while these time an entrance the user did not ask for
     (reveal, modal, nav transition), where the same 320ms reads as abrupt.
     Two bands, one owner. They lived in motion.css's own :root until
     2026-09-21, which meant a second token scale defined outside this file:
     the site had two different "fast" (120ms here, 180ms there) and three
     easing curves with no way to see that from the canonical source. Values
     are unchanged by the move. */
  --motion-fast: 180ms;   --motion-base: 420ms;
  --motion-slow: 720ms;   --motion-long: 900ms;
  --motion-ease:      cubic-bezier(0.22, 1, 0.36, 1);
  --motion-soft-ease: cubic-bezier(0.16, 1, 0.3, 1);

  /* ---- Stacking ---------------------------------------------------------- */
  --z-base: 0;      --z-raised: 10;  --z-sticky: 100; --z-notice: 200;
  --z-float: 300;   --z-drawer: 400; --z-modal: 500;  --z-consent: 600;
  --z-toast: 700;

  /* ---- Scrollbars -------------------------------------------------------- */
  /* Thin overlay-style pills. The Windows UA bar is ~17px and fights the
     dense catalogue (§8.4); an 8px track with a 2px transparent inset paints
     a 4px thumb that thickens on hover. Firefox only honours scrollbar-width
     and scrollbar-color — the webkit rules in main.css are progressive.
     Thumb is --line at rest (the same hairline as a card) and steps toward
     --ink-700 on hover; never --accent (§8.2). */
  --scrollbar-size: var(--s-3);
  --scrollbar-track: transparent;
  --scrollbar-thumb: var(--line);
  --scrollbar-thumb-hover: color-mix(in srgb, var(--ink-700) 45%, var(--line));
}

/* Controls come down to 32 ONLY where a fine pointer proves there is no finger
   — pointer capability, never width. A 1280px touch laptop keeps the touch
   height, and so does an iPad at every width including landscape: BOTH REPORT
   pointer: coarse, so neither ever enters this block (§8.13, §8.4). One place,
   so buttons, fields, selects and the header search stay on one height. */
@media (hover: hover) and (pointer: fine) {
  :root { --field-h: var(--field-h-fine); }
}

/* =============================================================================
   THE BREAKPOINT SWAPS — every token with a per-width pair, in ONE place.

   Declaring a `-lg` value and never swapping to it is a token that does
   nothing: the section rhythms shipped their mobile values at every width, so
   a 1440px page ran 40px of section padding and read as a phone layout stretched
   wide. A pair declared here is only real once it is swapped here.

   THE RULE FOR ANYTHING ADDED LATER: if a token has a `-sm`/`-lg` sibling, its
   swap belongs in this block and nowhere else. Scattering them into component
   stylesheets is what let these three go missing — nothing looks wrong in a
   file that simply never mentions them.
   ========================================================================== */

@media (min-width: 480px) {
  :root { --gutter-x: var(--gutter-x-sm); }
}

/* The cutout hero's card moves from the photo's bottom edge to a side
   column at --bp-md, not --bp-lg — and the three section rhythms take their
   middle tier here, so the tablet range stops rendering the phone's spacing
   (see the rhythm block above). */
@media (min-width: 768px) {
  :root {
    --hero-media-h: var(--hero-media-h-md);
    --home-faq-media-h: var(--home-faq-media-h-lg);

    /* All three together, exactly as at --bp-lg below — a tier that moves
       only some of them flattens the cadence §8.18 #4 depends on. */
    --section-y:         var(--section-y-md);
    --section-y-compact: var(--section-y-compact-md);
    --section-y-tall:    var(--section-y-tall-md);
  }
}

@media (min-width: 1024px) {
  :root {
    /* Space — the three rhythms of §8.4/§8.18 #4. Compact/standard/tall must
       all step up together, or the cadence between them collapses. */
    --section-y:         var(--section-y-lg);
    --section-y-compact: var(--section-y-compact-lg);
    --section-y-tall:    var(--section-y-tall-lg);

    /* Density: 8px gutters on a phone, 12px where there is room (§8.4). */
    --grid-gutter: var(--grid-gutter-lg);

    /* Layout chrome. */
    --gutter-x:  var(--gutter-x-lg);
    --card-pad:  var(--card-pad-lg);
    --header-h:  64px;   /* §8.13's chrome budget — 56px below --bp-lg */

    /* Type — paragraph copy only, back up to its original 14px on desktop
       (§8.3). Every other role in the scale is flat at every width. */
    --fs-body: var(--fs-body-lg);
  }
}

/* THE ONLY --bp-xl SWAP IN THE SYSTEM. Every other pair tops out at --bp-lg,
   which is why this block did not exist until the rhythms needed it (§8.13). */
@media (min-width: 1280px) {
  :root {
    /* All three together, as at every other tier — a tier that moves only
       some of them flattens the cadence §8.18 #4 depends on. */
    --section-y:         var(--section-y-xl);
    --section-y-compact: var(--section-y-compact-xl);
    --section-y-tall:    var(--section-y-tall-xl);
  }
}

/* Required — AODA/WCAG, §8.7. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Prices, spec boxes, comparison rows, estimator output, countdown.
   Proportional figures make aligned columns look broken. */
.price, .spec-value, .compare-cell, .estimate-out, .countdown {
  font-variant-numeric: tabular-nums;
}
