/* ==============================================================
   PHASE B — Design tokens
   Brand orange is read directly out of jbl-logo.svg (#FF4713).
   Cream and periwinkle are sampled from the reference.
   ============================================================== */

:root {

  /* --- Brand ------------------------------------------------- */
  --color-brand:              #FF4713;
  --color-brand-ink:          #FFFFFF;

  /* Primary action fill. NOT --color-brand: white 15px bold on
     #FF4713 is 3.41:1 and fails AA (4.5 needed — button labels are
     small text, unlike the CTA heading which passes as large text).
     85% of brand reaches 4.56:1 and is the lightest step that passes.
     Shared by every solid button so the fix cannot drift apart. */
  --color-btn-primary:        #D93C10;

  /* --- Surfaces ---------------------------------------------- */
  --color-surface:            #FFFFFF;
  --color-surface-utility:    #F6F3EA;  /* utility bar cream      */
  --color-surface-promo:      #97A3F5;  /* promo bar periwinkle   */
  --color-surface-sunken:     #FAFAFA;  /* style guide page only  */

  /* --- Ink --------------------------------------------------- */
  --color-ink:                #0A0A0A;
  /* Display headings sit slightly off pure black in the reference. */
  --color-heading-display:    #2B2A28;
  --color-ink-muted:          #5A5A5A;

  /* --- Lines & focus ----------------------------------------- */
  /* Decorative hairlines only - dividers, table rules, card edges.
     Composited on white it is 1.38:1, which is fine because none of
     those carry information. Do NOT use it on a form control. */
  --color-border:             rgba(0, 0, 0, .14);

  /* Form control boundaries. SC 1.4.11 wants 3:1 for anything needed
     to identify a UI component, and an input's border is exactly
     that - it is how you see where the field is. #949494 is the
     lightest grey that clears 3:1 on white; this sits a shade under
     it at 3.36:1 for margin. */
  --color-input-border:       #8C8C8C;
  --color-focus:              #0B5FFF;

  /* Mixed into fills for hover and active. A token because "darken
     toward black" is a system decision, not a local one. */
  --color-shade:              #000000;

  /* The counterpart. A dark fill cannot darken on hover, so the
     inverse button lightens instead - same idea, opposite direction. */
  --color-tint:               #FFFFFF;

  /* White used as ink on a dark ground (skip link, CTA focus ring).
     Distinct from --color-brand-ink, which means "ink on brand". */
  --color-ink-inverse:        #FFFFFF;

  /* Muted text/rules on a dark ground. Was kept component-scoped
     (--color-sitefooter-link: #8C8C8C) until there was a second
     consumer to justify a shared token — the pillars trust strip
     (§29) is that second consumer, so it is promoted here rather
     than hardcoded a second time. 5.89:1 on --color-ink/-inverse
     surfaces, clears 4.5 for body-size text. */
  --color-ink-muted-inverse:  #8C8C8C;

  /* Documentation only: the "Prefer" marker in guidance blocks. */
  --color-positive:           #2E7D32;
  --color-search-rule:        #BF4526;  /* sampled from reference */

  /* --- Type -------------------------------------------------- */
  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
               Roboto, Helvetica, Arial, sans-serif;

  /* Display face for the CTA heading. Archivo carries a genuine width
     axis, so "condensed heavy" is two settings rather than a second
     family. Inter is the fallback so a failed load degrades in width,
     not in typeface. */
  /* Monospace, for token names in the specimen. */
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas,
               'Liberation Mono', monospace;

  --font-display: 'Archivo', 'Inter', -apple-system, BlinkMacSystemFont,
                  'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  /* 62 is the narrow end of Archivo's width axis. Measured against the
     reference: the heading is ~630px wide at ~40px type, and wdth 62
     reproduces 631px. wdth 78 would run ~766px — visibly too wide. */
  --display-wdth: 62;
  --display-wght: 700;

  --text-2xs:     .6875rem;   /* 11px — badge                    */
  --text-xs:      .75rem;     /* 12px — utility bar              */
  --text-sm:      .8125rem;   /* 13px — promo bar                */
  --text-md:      .875rem;    /* 14px — main nav                 */
  --text-body:    .9375rem;   /* 15px — body copy                */
  --text-lg:      1rem;
  --text-xl:      1.25rem;    /* 20px                            */
  --text-2xl:     1.375rem;   /* 22px — drawer categories        */

  --weight-regular:   400;
  --weight-medium:    500;
  --weight-semibold:  600;
  --weight-bold:      700;

 /* Line heights. Every literal previously scattered through the
     components now has a name; body copy was unified on 1.55.  */
  --leading-none:     1;      /* display, guaranteed single line */
  --leading-display:  1.05;   /* large display headings          */
  --leading-heading:  1.1;    /* smaller display headings        */
  --leading-tight:    1.15;   /* UI labels, compact headings     */
  --leading-control:  1.4;    /* buttons and inputs              */
  --leading-normal:   1.45;   /* document default                */
  --leading-body:     1.55;   /* paragraphs                      */

  /* --- Space (4px base) -------------------------------------- */
  --space-1:   .25rem;   /*  4 */
  --space-2:   .5rem;    /*  8 */
  --space-3:   .75rem;   /* 12 */
  --space-4:   1rem;     /* 16 */
  --space-5:   1.5rem;   /* 24 */
  --space-6:   2rem;     /* 32 */
  --space-7:   2.5rem;   /* 40 */
  --space-8:   3rem;     /* 48 */
  --space-9:   4rem;     /* 64 */
  --space-10:  5rem;     /* 80 */
  /* Section-scale steps. Nothing needed these until a full-bleed band
     had to breathe against its own side gutters; the scale stopped at
     80px and a hero was reaching past it. */
  --space-11:  6rem;     /* 96 */
  --space-12:  8rem;     /* 128 */
  --space-13:  10rem;    /* 160 */

  /* --- Layout ------------------------------------------------ */
  /* 1480, not 1680. Both are whole multiples of the 4px unit, so the
     grid does not choose between them — what does is what happens on a
     wide screen. At 1920 a 1680 container gave the two-tier header a
     1600px bar with 528px between the logo and the nav and 494px
     between the nav and the CTA: three elements adrift in a lot of
     nothing. 1480 pulls that back without touching any layout that
     depends on a minimum, since the widest is the header nav at ~1140.
     Below a 1560px viewport nothing changes at all — the container
     stops binding and --container-pad takes over. */
  --container-max:    1480px;
  --container-pad:    clamp(1rem, 2.5vw, 2.5rem);

  --bar-h-utility:    2.75rem;   /* 44 */
  --bar-h-promo:      2.25rem;   /* 36 */
  --bar-h-main:       4.5rem;    /* 72 */

  /* Full stacked header height — used to size the mobile drawer. */
  --header-h: calc(var(--bar-h-utility) + var(--bar-h-promo) + var(--bar-h-main));

  /* --- Icons ------------------------------------------------- */
  --icon-sm:          1rem;      /* chevrons */
  --icon-md:          1.375rem;  /* nav actions */
  --icon-stroke:      1.75;

  /* --- Brand logo optical heights ----------------------------
     Intrinsic ratios are wildly different — HK is 9.6:1, the JBL
     mark 1.45:1 — so one shared height reads badly unbalanced.
     Each mark is sized to a common optical weight instead.       */
  --logo-h-jbl:              1.375rem;
  --logo-h-harman-kardon:     .8125rem;
  --logo-h-akg:              1rem;
  --logo-h-mark-levinson:    1.6875rem;
  --logo-h-infinity:         1.1875rem;
  --logo-h-masthead:         2.25rem;

  /* --- Newsletter CTA ----------------------------------------
     The band reuses the brand orange read from jbl-logo.svg. Ink is
     black rather than white: white on this orange is ~3.3:1, which
     carries the large heading but would fail for anything at body
     size, so only the heading is white. */
  --color-cta-surface:  var(--color-brand);
  --color-cta-heading:  #FFFFFF;
  --color-cta-ink:      var(--color-ink);
  --color-cta-rule:     var(--color-ink);
  --cta-rule-weight:    2px;

  /* Display step. Fluid because the heading is the only thing in the
     band that must not wrap awkwardly between 360px and 1680px.
     Ceiling is 40px to match the measured reference, not a guess. */
  --text-display:       clamp(1.875rem, 2.4vw, 2.5rem);

  --cta-pad-block:      clamp(var(--space-7), 6vw, var(--space-9));
  --cta-gap:            clamp(var(--space-6), 5vw, var(--space-10));

  /* --- Sign-in form ------------------------------------------
     Colours below marked "sampled" are read by eye from the
     reference JPEG; each is a single token to correct. */
  --color-signin-heading:      var(--color-heading-display);
  --color-signin-notice:       var(--color-ink);
  --color-signin-instruction:  #3F5A72;  /* sampled: slate blue, clearly not the notice colour */
  --color-signin-submit:       var(--color-btn-primary);
  --color-signin-submit-ink:   var(--color-brand-ink);
  --color-provider-border:     var(--color-brand);

  /* Shared underlined-field treatment, same language as the header
     search and the newsletter, but on white rather than a colour. */
  --color-field-rule:   var(--color-ink);
  --field-rule-weight:  2px;

  /* Measured: "RETURNING CUSTOMERS" has a width:size ratio of 8.65 in
     Archivo wdth 62, and the reference spans ~442px — so 51px. That
     ratio is string-specific and does not transfer from the CTA. */
  --text-signin-heading:  clamp(1.75rem, 4.5vw, 3.1875rem);

  --checkbox-size:        1.125rem;   /* 18px */
  --signin-max-w:         42rem;      /* 672px — matches the reference column */
  --btn-pad-block:        .625rem;
  --btn-pad-inline:       var(--space-6);

  /* --- Geo-redirect modal -------------------------------------
     Icon ratio is 62:87 — taller than wide — so it is sized by
     height and the width follows; never constrain both. */
  --geo-max-w:          40rem;      /* 640px — matches the reference card */
  --geo-pad:            clamp(var(--space-5), 4vw, var(--space-6));
  --geo-gap:            var(--space-5);
  /* The artwork is 61.8x69.5 inside an 87-tall viewBox — 17.5px of
     empty space sits below it (20% of the height). Sizing the element
     to 60px therefore renders the visible signpost at only ~48px.
     75px compensates, giving ~60px of actual artwork, ~53px wide. */
  --geo-icon-h:         4.6875rem;  /* 75px box -> ~60px visible art */

  /* Measured: "WANT THE LOCAL EXPERIENCE?" has a width:size ratio of
     10.95 in Archivo wdth 62; the reference spans ~317px, so 29px. */
  /* Shared by every heading that sits inside a contained panel
     (geo modal, contact prompt). Measured for the contact string:
     matching the reference block WIDTH wants 32px and its cap
     height ~28px; this step's 29px ceiling sits between them, and
     one scale across both panels beats two points on either. */
  --text-panel-heading: clamp(1.375rem, 2.4vw, 1.8125rem);

  --color-geo-proceed:      var(--color-btn-primary);
  --color-geo-stay-border:  var(--color-brand);
  --geo-btn-min-w:      10rem;
  --geo-btn-pad-block:  var(--space-3);
  /* These two are about "a modal", not about the geo prompt, and §19
     is the second dialog to want them — so they moved up a level.
     Values are unchanged, so §7 renders identically. */
  --modal-shadow:       0 10px 40px rgba(0, 0, 0, .18);
  --modal-backdrop:     rgba(0, 0, 0, .45);
  --geo-shadow:         var(--modal-shadow);
  --geo-backdrop:       var(--modal-backdrop);

  /* --- Specs accordion ----------------------------------------
     Sizes derived by measuring each string against the reference,
     then cross-checked against cap heights:
       "SPECS & DOWNLOADS" ratio 7.58, 533px wide  -> 70px
       "DESCRIPTION"       ratio 4.76, 152px wide  -> 32px
       "ITEM#: JBLBAR..."  ratio 10.08, 240px wide -> 24px
     All three are larger than they appear; wght 700 is sufficient,
     no heavier axis value is needed.

     The mobile crop's scale factor is unknown, so the clamp minima
     are conventional mobile sizes rather than measured ones. */
  --text-page-heading: clamp(2.125rem, 5vw, 4.375rem);

  /* Three display steps, all arrived at by measurement rather than
     imposed up front:
       --text-page-heading     34-70px   hero, specs page title
       --text-section-heading  32-56px   band and card headings
       --text-panel-heading    22-29px   headings inside panels */   /* 34 -> 70 */
  --text-specs-row:    clamp(1.375rem, 2.2vw, 2rem);     /* 22 -> 32 */
  --text-specs-sku:    clamp(1rem, 1.6vw, 1.5rem);       /* 16 -> 24 */

  --color-specs-rule:  var(--color-ink);
  --specs-rule-weight: 1px;
  --specs-row-pad:     var(--space-5);
  --specs-icon:        1.25rem;

 /* --- Buttons and links -------------------------------------
     Disabled colours are their own pair rather than an opacity
     fade: fading a solid button drops white-on-orange to about
     2:1. WCAG exempts disabled controls from contrast, but
     illegible is still bad, so these stay readable. */
  --color-disabled-surface: #E6E2DE;
  --color-disabled-ink:     #6E6862;
  --link-underline-offset:  3px;

  /* --- Use-case cards -----------------------------------------
     Rendered in OUR language, not the reference's: uppercase
     Archivo and square corners, rather than the rounded face and
     16px radii of the original. The layout is matched; the visual
     language is deliberately ours.

     Measured: "DOING ANYTHING NICE TOMORROW?" has a width:size
     ratio of 12.70 in Archivo wdth 62, and its longest line 7.86.
     At 56px the two-line block runs ~440px, against the
     reference's ~450px — so the heading keeps its shape. */
  --text-section-heading:     clamp(2rem, 4vw, 3.5rem);   /* 32 -> 56 */

  /* A measure, not a container width: it is what makes the heading
     break after "NICE" into two lines as the reference does. */
  --usecase-heading-measure:  30rem;
  --usecase-intro-measure:    38ch;

  /* 19rem, not a round guess: every asset is requested at
     h=170&dpr=2, so 170px is the height the illustrations were
     authored for. At their measured 1.782 ratio that implies a
     303px card (18.94rem). 19rem renders the media box at 170.6px
     — the size the images were made for. At the previous 12rem the
     box was 108px, using barely two thirds of each asset. */
  /* Capped at 19rem on desktop; below ~420px the viewport takes
     over. 72vw deliberately leaves part of the next card showing —
     that sliver is the only thing telling a phone user the row
     scrolls, since there are no arrows. A fixed 19rem would fill a
     360px screen edge to edge and read as a single static card. */
  --usecase-card-w:           clamp(13rem, 72vw, 19rem);
  --usecase-card-pad:         var(--space-4);
  --usecase-gap:              var(--space-5);
  /* Measured from the assets: all nine are 606x340. Matching the
     box to the image means no letterboxing and no cropping. */
  --usecase-media-ratio:      303 / 170;

  /* --- Car category cards -------------------------------------
     Measured: all five assets are 926x521 (1.777), so the media box
     matches exactly - nothing letterboxed, nothing cropped.

     The heading reuses the shared section step rather than taking a
     size of its own. Matching the reference's 540px block WIDTH
     would need 63px in our condensed face and overshoot its cap
     height by ~18%; 56px lands within 5% of the cap height, which
     is what reads as "the same size". Unlike the use-case heading
     this one is a single left-aligned line, so block width carries
     no compositional meaning worth preserving. */
  --cartypes-card-w:          clamp(15rem, 78vw, 23rem);  /* 368px */
  --cartypes-media-ratio:     926 / 521;
  --cartypes-gap:             var(--space-4);   /* reference ~18px */

  /* The same cream as the utility bar. Here it is the only surface
     - the cars sit on it and the caption sits outside - so it does
     not create the nested-box problem that removed the fill from
     the use-case cards. */
  --color-cartypes-media:     var(--color-surface-utility);

  /* --- Statement band -----------------------------------------
     The system's first dark ground. Measured on #0A0A0A:

       white          19.80   safe everywhere
       brand orange    5.81   safe even at small text
       btn-primary     4.34   would FAIL a small button label here
       focus ring      3.86   passes 3:1, the weakest in the system
       ink-muted       2.87   FAILS - never put it on this ground

     The last two are the ones to remember. Muted body text is the
     obvious reach for a supporting paragraph and it is unusable
     here, which is why the body is white rather than muted. The
     band has nothing focusable today; if a control is ever added,
     both the focus ring and the primary button fill need checking
     against this surface rather than assumed from the light one. */

  /* An alias, not a second #0A0A0A: an ink and a surface are
     different layers even when the value matches, and duplicating
     the hex is what the palette section warns against. */
  /* Shared by every dark surface (bento's dark card, pillars,
     metrics, the tierheader utility strip, the site footer) — kept
     here because this comment block is where its contrast budget
     against #0A0A0A was worked out, even though .statement itself
     (the section that first used it) has since been removed. */
  --color-surface-inverse:    var(--color-ink);

  /* --- Contact prompt -----------------------------------------
     Avatar sizes measured from the reference: 52px standard, 62px
     for the featured one, overlapping by 12px with a 3px ring.

     The circles are a deliberate exception to the square-corner
     decision. Roundness on an avatar is the convention for "a
     person", not a corner-radius style choice - squaring them
     would read as a filmstrip, not as our house style. */
  --contact-avatar:          3.25rem;    /* 52px */
  --contact-avatar-lg:       3.875rem;   /* 62px */
  --contact-avatar-overlap:   .75rem;    /* 12px */
  --contact-avatar-ring:     3px;

  --contact-panel-pad:       clamp(var(--space-7), 6vw, var(--space-9));
  --color-contact-panel:     var(--color-surface-utility);
  /* The ring separates one circle from the next, so it has to be
     the page surface rather than the panel it sits on. */
  --color-contact-ring:      var(--color-surface);

  /* --- Advertising CTA band -----------------------------------
     Measured on the brand orange:

       ink (black)   5.81   passes even at small text
       white         3.41   large text only
       ink-muted     2.02   FAILS badly - not usable here
       focus ring    1.50   FAILS

     So this band is black-inked throughout, which is what makes it
     read differently from the white-inked newsletter band on the
     same orange.

     The focus override is required, not decorative: the band holds
     two focusable elements. Black scores 5.81 against the white the
     newsletter had to settle for at 3.41 - its ink is white, so it
     had no darker option. */
  --color-adcta-surface:      var(--color-brand);
  --color-adcta-ink:          var(--color-ink);
  --adcta-pad-block:          clamp(var(--space-8), 8vw, var(--space-10));
  /* The reference keeps this sentence on one line: 83 characters,
     which is past the ~75 our own typography rule 8 calls
     comfortable. Matched anyway, because the single line is the
     composition - but noted, because it is a deliberate exception
     to a rule this project wrote down rather than an oversight. */
  --adcta-measure:            84ch;
  --text-adcta-footnote:      .625em;   /* relative to the body line */

  /* --- Starter guide accordion ---------------------------------
     Heading reuses the shared section step. Measured: matching the
     reference's cap height wants 55.6px and its block width 51.2px
     - 9% apart, so the 56px ceiling satisfies both. (The car
     category heading had the same two targets 18% apart, and one
     had to be sacrificed.)

     The left rule is the state indicator, in two colours. State is
     NOT carried by colour alone: the panel appearing is the primary
     signal, the rule reinforces it, and aria-expanded carries it to
     assistive tech. Verified in the S5 pass rather than assumed. */
  /* Fluid, not a breakpoint. The heading sits at its 32px floor
     until ~800px, so a fixed 20px label compressed the hierarchy to
     1.6x right in the middle of the range - worse than on a phone.
     Tracking the same curve keeps the ratio at 2x or better at
     every width. */
  --text-guide-label:        clamp(1rem, 1.6vw, 1.25rem);

  --guide-rule-w:            2px;
  --color-guide-rule:        var(--color-border);
  --color-guide-rule-open:   var(--color-ink);
  --guide-rule-gap:          var(--space-3);   /* ~14px in reference */
  --guide-item-gap:          var(--space-7);   /* ~42px in reference */
  --guide-pad-block:         clamp(var(--space-8), 7vw, var(--space-10));

  /* ~96 characters, matching the reference. That is well past the
     ~75 our typography rule 8 calls comfortable - the same
     deliberate exception the advertising band makes. Two references
     in a row running long measures is worth noticing: it may be
     the rule that needs revisiting, not the components. */
  --guide-measure:           96ch;

  /* --- Hero with lead form ------------------------------------
     The heading reuses the page step. Measured, the two targets
     diverge more than anywhere else in the project: matching the
     reference's line WIDTHS wants 96-104px, its cap height 76px -
     about 30% apart, because the lines are short and our condensed
     face is much narrower per character. Cap height wins; a 100px
     hero would tower over every other heading in the system, and
     the two lines would need different sizes to match both widths.
     70px is 8% under the cap-height target. */
  --hero-gap:                clamp(var(--space-7), 5vw, var(--space-9));
  --text-hero-tagline:       clamp(1rem, 1.4vw, 1.375rem);

  --color-hero-accent:       var(--color-brand);
  --color-hero-field-border: var(--color-input-border);
  --hero-field-border-w:     1px;

  /* Without a cap the field stretched to the whole column - 543px at
     1280 and wider still on a large screen. A zip code is five
     characters; the input should not look like a search bar. */
  --hero-field-max:          32rem;   /* 512px */

  /* Measured from the asset: 2048x1153. Reserving the right shape
     means the column does not jump when it loads. */
  --hero-media-ratio:        2048 / 1153;

  /* Decorative (aria-hidden), so no contrast requirement - the
     sentence beside them states the rating. */
  --color-hero-star:         var(--color-brand);
  --hero-star-size:          1.25rem;

  /* The asterisk is aria-hidden too, but it still has to be legible.
     btn-primary reaches 4.56:1 on white where brand orange manages
     only 3.41. */
  --color-hero-required:     var(--color-btn-primary);
  --hero-rating-logo:        2.5rem;

  /* Narrower than the site container on purpose. The nav is chrome and
     should span; the hero is content and should not. Measured at the
     70px headline: the widest heading line is 392px, the tagline 437px,
     and the field is capped at 512px - so the content column never
     needs more than ~512px. At --container-max (1680px) that column is
     768px, leaving 256px of dead space beside every element in it.
     1280px puts it at 568px: the field keeps a little air, and the car
     is still large enough to read as the hero image. */
  --hero-max:                1280px;

  /* Vertical air has to answer to the side gutters, and narrowing the
     inner to 1280px doubled those. At a 1600px viewport the content
     now sits 200px from the side edges but only 64px from the top -
     a 3:1 split that reads as a band squeezed between its neighbours.
     128px brings it to 1.56:1, which is the right way round for a
     centred layout without being level.
     Fluid rather than fixed: below the cap the side gutters collapse
     to --container-pad, so a flat 128px would overshoot in the other
     direction on a 1000px screen. 8vw reaches the cap exactly at
     1600px. */
  --hero-pad-block:          clamp(var(--space-7), 8vw, var(--space-12));

  /* --- How it works -------------------------------------------------
     Derived from the wireframe (canvas 1100, content 152-952 = 800).
     Three readings, three different verdicts, and the difference is
     worth recording because it is not arbitrary.

     SUBHEADING — the derivations AGREE. Its 200px bar against our
     12.03 ratio asks for 16.6px; its 12px bar as a cap height asks for
     16.7px. They agree because the strings are the same length, which
     is the condition width-matching needs and rarely gets.

     HEADING — width is useless. Their two lines run 280 and 420, a
     ratio of 1.50; ours are 117 and 114, a ratio of 0.97. Their second
     line is half again as long as their first and ours are equal, so
     the strings are not comparable at all. Cap height gives ~45px and
     --text-section-heading gives 48 at a 1200px viewport. That is the
     SEVENTH section on that step — which reads as the scale doing real
     work rather than as laziness, since each one was measured
     independently and landed there.

     NUMERAL — width and cap height disagree on the '1' and agree on
     the '2' and '3', which is itself the answer. Measured, our '1' is
     0.357em wide; the wireframe draws it 48px, implying 134px. But its
     122px height as a cap implies 169px, and at 169px a '2' or '3'
     lands near the 72px those are drawn at. Archivo's '1' is simply
     narrower than the wireframe's block. Cap height wins: ~168px.
     Fluid, because 168px of numeral on a phone is absurd.

     COLUMNS. The wireframe's content is 73% of its canvas, so the list
     is capped at 73% of ours rather than filling the container: three
     steps spread over 1400px would lose the grouping the reference
     has. col:gap in the wireframe is 204:94, or 2.17:1, which at a
     1080px list gives a 128px gap — exactly --space-12. */
  --steps-list-max:       67.5rem;   /* 1080 = 73% of --container-max */
  /* Fluid. As a fixed 128 it never shrank: at a 294px list the two
     gaps consumed 256px of it, leaving 13px columns and nine lines of
     wrapped text. 9vw reaches the cap at ~1420px, so the wireframe
     ratio still holds where the wireframe applies. */
  --steps-gap:            clamp(var(--space-6), 9vw, var(--space-12));

  --steps-header-gap:     var(--space-4);    /* heading -> sub,   16 */
  --steps-list-gap:       var(--space-8);    /* sub -> steps,     46 */
  --steps-number-gap:     var(--space-4);    /* numeral -> title, 14 */
  --steps-cta-gap:        var(--space-8);    /* steps -> CTA,     47 */

  --text-steps-number:    clamp(4rem, 12vw, 10.5rem);   /* 64 -> 168 */
  --text-steps-sub:       var(--text-lg);
  /* 16px, from the FULL line's 12px cap. The second bar is 10px, but
     that is a partial line rather than a smaller size. */
  --text-steps-text:      var(--text-lg);

  --steps-chevron:        var(--icon-sm);
  --color-steps-chevron:  var(--color-ink-muted);

  /* --- Bento grid ---------------------------------------------------
     Derived from the wireframe (container 1017, grid 38->1055) and,
     for two values the wireframe could not settle, from the reference
     photograph.

     The columns are exact and worth stating, because a first read of
     the wireframe had the two small cards spanning two columns each:

       6 x 156 + 5 x 16 = 1016   against a measured 1017
       col 1  38-194   col 2  210-366   col 3  382-538
       col 4  554-710  col 5  726-882   col 6  898-1054

     Every cell falls on those lines. The two small cards are ONE
     column each, not two.

     HEADING. Width says 26px (320px bar / our 12.28 ratio), cap height
     says 47px. The strings differ — theirs is 16 characters, ours 28 —
     so width is meaningless here and cap height wins, as everywhere
     else. The reference photo agrees independently: its heading scales
     to ~48px at our container width. --text-section-heading gives 48px
     at a 1200px viewport, so it is reused. Sixth section on that step.

     FIGURE. The hardest one. Three readings: 92px by width, 50px by
     cap height, and the reference photo's own figure scales to ~50px
     of cap. Width is high because our display face is condensed and
     theirs is not — the same string occupies far less width in ours,
     so matching their WIDTH would mean overshooting their size badly.
     Cap height is the honest match at ~48px.

     ROWS are deliberately not tokens. The wireframe draws 140/65/164,
     but those are content heights in the reference, not a fixed track
     list — pinning them would clip a card whose copy grew. The spans
     make the mosaic; the content makes the heights. --bento-card-min
     only stops a short card collapsing. */
  --bento-cols:            6;
  --bento-gap:             var(--space-4);   /* wireframe 16, exact */
  --bento-card-pad:        var(--space-5);   /* wireframe 24, exact */
  --bento-card-min:        8.75rem;          /* 140, the shortest cell */

  --bento-header-gap:      var(--space-2);   /* heading -> sub,  ~9  */
  --bento-cta-gap:         var(--space-4);   /* sub -> CTA,      ~17 */
  --bento-grid-gap:        var(--space-7);   /* CTA -> grid,     ~39 */

  --text-bento-figure:     3rem;             /* 48px */
  --text-bento-title:      var(--text-xl);
  --text-bento-sub:        var(--text-lg);

  --color-bento-dark:      var(--color-surface-inverse);
  --color-bento-tint:      var(--color-surface-sunken);
  /* Added at K3, not K2: styling revealed that the accent card and the
     default card had collapsed onto the same value, which made the
     `tone` field meaningless for one of six cards. The reference uses a
     brand-derived wash there; ours is the same idea in orange. */
  --color-bento-accent:    color-mix(in srgb, var(--color-brand) 14%, var(--color-tint));
  --color-bento-media:     var(--color-surface-sunken);

  /* --- Two-tier header ---------------------------------------------
     Derived from the wireframe (box 1197 wide, tiers 35 and 65 tall).
     Two of these derive cleanly and two do not, and it is worth being
     clear about which is which.

     CLEAN — the utility link. Our 'Customer support' renders 119.7px
     at 14px against the wireframe's 120px bar. That is not a rounding
     coincidence at 16 characters: their string is the same length as
     ours, so 14px is confirmed rather than assumed.

     NOT CLEAN — the nav links. Their bars are 115-120px; our widest
     label is 84px at 14px, which would ask for 20px type. But their
     three bars are near-equal (115:120:120) where ours are not
     (83:84:64), so their labels are simply longer — about 17
     characters against our 12. Width says nothing about size when the
     strings differ, so the nav takes §1's own --text-md instead. Their
     bars also read slightly heavier than the utility bar's, hence
     semibold against regular at the same size.

     NOT CLEAN — the wordmark. The wireframe draws a solid 140x20
     block, which is a logo lockup rather than text, so its height is
     not a cap height and the usual cap-height reading does not apply.
     Matched on footprint instead: 140 / our 4.29 ratio = 32.6px.

     The CTA was first sized by HEIGHT to the wireframe's 35px, which
     needed 6px of block padding. It was later raised to 10px by
     decision — exactly .btn--sm's own value, so this token no longer
     overrides anything. Kept as the documented hook for when the
     header's control height next diverges from the button scale.

     --container-max, not --hero-max: a header is chrome and should
     span, which is the same argument that took §18 the other way. */
  --tierheader-max:               var(--container-max);
  --tierheader-utility-pad:       var(--space-2);   /* -> a 35px tier */
  --tierheader-main-pad:          var(--space-4);   /* -> a 75px tier */
  --tierheader-nav-gap:           var(--space-4);   /* wireframe 15  */
  --tierheader-cta-pad-y:         .65625rem;        /* = btn--sm, 44px */

  --text-tierheader-wordmark:     2rem;             /* 32px -> 137px */
  --text-tierheader-nav:          var(--text-md);
  --text-tierheader-utility:      var(--text-md);

  --color-tierheader-utility:     var(--color-surface-inverse);
  --color-tierheader-utility-ink: var(--color-ink-inverse);

  /* --- Booking hero ------------------------------------------------
     Derived from the wireframe (canvas 1120, bar 628 wide) and from
     our own measured strings.

     The heading is the one real disagreement, and a wide one:

       line 1 bar  519 / our 8.50 ratio  ->  61px
       line 2 bar  439 / our 6.77 ratio  ->  65px
       line 1 bar height 32 as cap        ->  44px

     Cap height wins, as it does everywhere else here, because a bar's
     WIDTH depends on the wireframe's own copy and we do not have it.
     The two-line ratio does agree (theirs 1.182, ours 1.255), but that
     only says the lines are proportionally similar — not that they are
     the same length, so it cannot rescue the width reading.

     --text-section-heading resolves to 44.8px at the wireframe's own
     1120px width, which is the cap-height answer exactly. That makes
     this the FIFTH section on that step. It is deliberately smaller
     than §18's --text-page-heading: that hero carries two short words
     per line, this one carries a sentence.

     The rating row derives cleanly and confirms itself — our
     '150,000+ 5 star reviews' renders 162px at 14px against the
     wireframe's 160px bar, so the strings are genuinely comparable
     and the rest of the row can be trusted:

       logo 28 + gap 9 + stars 69 + gap 12 + text 160 = 278  (exact)

     Stars are 13px here against §18's 20px. Small, but they are
     aria-hidden decoration beside a sentence that states the rating,
     so nothing rests on them being large. */
  --booking-max:            var(--hero-max);
  /* Generous by decision, not derived: the wireframe is a crop and
     says nothing about the band's own height. 10rem is the next step
     on the scale after 128 and still a whole number of 4px units — the
     unit every other step is built from — so it stays on grid.
     10vw reaches the cap at a 1600px viewport, matching how §18's
     hero paces its own padding. */
  --booking-pad-block:      clamp(var(--space-8), 10vw, var(--space-13));

  /* Still read by the car's own positioning below (B5) even though the
     bar it was measured against now lives in bookingform.css — this is
     "how wide the widest thing in the column is," not a bar-specific
     value, so it stays. --booking-bar-pad/--booking-when-min/
     --booking-icon were bar-specific and were removed with it. */
  --booking-bar-max:        40rem;      /* 640px, wireframe 628 */

  --booking-heading-gap:    var(--space-6);   /* heading -> bar  */
  --booking-rating-gap:     var(--space-7);   /* bar -> rating   */

  --text-booking-rating:    var(--text-md);

  /* The car is POSITIONED, not sized. Its left edge is pinned to the
     right edge of the booking bar and it runs from there off the page,
     so its width is whatever is left over. That replaces a percentage
     read off a cropped composition — a guess I could not check — with
     a rule that holds at every width: no text ever sits over it. See
     the B5 note in bookinghero.css for why that matters. */
  --booking-media-bleed:    var(--space-8);

  /* --- Booking form (§30) -------------------------------------------
     Ported alongside the booking hero to replace its free-text
     location field with a fixed branch select — see bookingform.css
     for why. One class set draws both layouts: a stacked card below
     the form's own 43rem container width, a horizontal bar above it.

     Card measurements, wireframe scale 480/373 = 1.287:
       inset, top and sides      18 ->  23   --space-5   24
       inset, bottom             23 ->  30   --space-6   32
       field row, rule to rule   57 ->  73   24 pad + 22.4 line + rule = 71
                                            (12 on the row, 12 on the input)
       last row -> button        13 ->  17   --space-4   16

     The source drew its button at 60px — a deliberate one-off,
     bigger than .btn--md, because 47/57 of the wireframe's own row
     reads taller than .btn--md's ratio. That predates this project's
     own 44/48 standardisation (buttons.css): every button here is
     now one of exactly two heights, so a third, bespoke geometry for
     one card would undo the thing just established elsewhere on this
     page. --bookingform-submit-min was dropped for that reason — the
     button is a plain .btn--md, 48px, like every other --md button
     on the site. See bookingform.css for the matching 48px on the
     two controls beside it. */
  --bookingform-max:            30rem;
  --bookingform-border-w:       1px;
  --bookingform-pad:            var(--space-5);
  --bookingform-pad-bottom:     var(--space-6);
  --bookingform-row-pad:        var(--space-5);
  --bookingform-submit-gap:     var(--space-4);

  /* TYPE, inherited — the same size the hero's own search field
     already uses for its input text. */
  --text-bookingform-input:     var(--text-lg);

  /* COLOUR. Border heavier than the between-field rule on purpose:
     the border says "this is a control," the rule only separates two
     rows of the same one. --color-input-border is the lightest grey
     that still clears the 3:1 a field boundary needs — the same
     token the hero's own field border already uses. */
  --color-bookingform-surface:     var(--color-surface);
  --color-bookingform-border:      var(--color-ink);
  --color-bookingform-rule:        var(--color-input-border);
  --color-bookingform-ink:         var(--color-ink);
  --color-bookingform-placeholder: var(--color-ink-muted);

  /* THE BAR (desktop layout). Bar height is no longer scaled off the
     source's own 60px button (see the comment above --bookingform-max
     for why) — it's 48px control + 2x4px, the button's own margin,
     both landing on the button's own 48+8=56px outer height. Field
     inset/icon/gap/chevron/button-width/bar-width below are still the
     source's own wireframe-derived numbers; --bookingform-bar-
     divider-inset was dropped along with the inset divider it drew
     (see bookingform.css) — full height fits a 56px bar the 8px inset
     never had to.
       field inset, left       20 ->  23   --space-5   24
       icon                    16 ->  18   1.125rem
       icon -> text            10 ->  12   --space-3   12
       chevron                 12 ->  14   --icon-sm   16
       button width           108 -> 125   7.75rem     124
       bar width              812 -> 937   58rem       928 */
  --bookingform-bar-max:           58rem;
  --bookingform-bar-pad:           var(--space-1);
  --bookingform-bar-field-pad:     var(--space-5);
  --bookingform-bar-submit-min-w:  7.75rem;
  /* The source splits Where/When 1fr-1fr — even in a card, where two
     rows both run edge to edge, but not in a bar, where the date
     field ends up as wide as the branch field for text a quarter the
     length. The hero's own earlier bar already made this call
     (grid-template-columns: 1fr auto auto) and this restores it: the
     date field sizes to its content plus a little air, everything
     else goes to the branch field. 8rem — a date input like this
     one renders ~116-128px depending on locale/engine. */
  --bookingform-when-min:          8rem;
  --bookingform-icon:              1.125rem;
  --bookingform-icon-gap:          var(--space-3);
  --bookingform-chevron:           var(--icon-sm);

  --color-bookingform-icon:        var(--color-ink-muted);
  --color-bookingform-divider:     var(--color-input-border);

  /* --- Testimonials -----------------------------------------------
     Derived from the wireframe (section box 1198, cards 266 wide on a
     22px gap) and from our own strings.

     The heading is the one place both derivations agreed for once:
     its 420px bar against our 8.97 width:size ratio asks for 46.8px,
     and its 35px bar read as a cap height asks for 48.6px. Reuse of
     --text-section-heading lands on 48px at a 1200px viewport, which
     is inside both. That makes this the FOURTH section on that step;
     the note there about one shared heading beating two points of
     accuracy has only got truer.

       card       266  ->  282 (four columns of a 1200px content row)
       gap         22  ->  --space-5 / 24
       card pad    ~20 ->  --space-5 / 24
       stars     87 wide -> 5 x 16px + 4 x .1em gaps = 86.4  (exact)
       avatar      35  ->  2.25rem / 36
       avatar gap  17  ->  --space-4 / 16
       heading gap 50  ->  --space-8 / 48
       CTA gap     53  ->  --space-8 / 48

     No card border, unlike the wireframe. There the border separates
     the card from a white page; here the band does that job, and a
     hairline darker than the card would read as an edge artefact
     against black rather than as a boundary. */
  --testimonials-max:         var(--hero-max);
  --testimonials-pad-block:   clamp(var(--space-8), 6vw, var(--space-9));
  --testimonials-gap:         var(--space-5);
  --testimonials-header-gap:  var(--space-8);
  --testimonials-cta-gap:     var(--space-8);
  --testimonials-card-pad:    var(--space-5);
  --testimonials-star:        1rem;      /* 16px */
  --testimonials-avatar:      2.25rem;   /* 36px */
  --testimonials-author-gap:  var(--space-4);

  --color-testimonials-card:  var(--color-surface);
  --color-testimonials-star:  var(--color-brand);

  --text-testimonial-quote:   var(--text-body);
  --text-testimonial-name:    var(--text-body);

  /* --- FAQ accordion ---------------------------------------------
     Derived from the wireframe (canvas 880, rules spanning 40->856,
     so an 816px content column) and from our own measured strings.

     The two derivations disagreed, and which one to trust differs per
     element — not arbitrarily. A bar's WIDTH depends on the
     wireframe's own copy, which we do not have; its HEIGHT does not.

     For the questions the width is still usable, because the spread
     across the seven bars (342:254, or 1.35) matches the spread across
     our seven labels (20.03:14.70, or 1.36) almost exactly — so the
     strings are comparable lengths and the scale carries over. The
     heading is a single unknown string with no such cross-check, so
     only its height says anything.

       content column   816  ->  SUPERSEDED: the column now follows
                                 --hero-max (1280px) by decision, not
                                 the wireframe's 816. The row is the
                                 button, so a wider row is a bigger
                                 target, not a longer reach.
       heading      cap  29  ->  ~40px, inside --text-section-heading
       question   width 342  ->  342 / 20.03 = 17.1px -> --text-lg
       subheading   cap  14  ->  the question's own bar is 15, so the
                                 two are the same size; the wireframe
                                 separates them by COLOUR, not scale
       row pitch         56  ->  SUPERSEDED: matched to §8's rows
                                 instead (73px), so the two accordions
                                 in the guide sit at the same rhythm.

     Row height carries BOTH a padding and a min, and each does a job.
     The padding is §8's own --space-5 rhythm, and it is what governs
     the row once a question wraps to two lines. The min holds the
     single-line pitch even at 16px type, where §8 reaches its height
     with 22px type instead — matching the height without inheriting
     the type size. */
  --faq-max:              var(--hero-max);   /* 1280px */
  --faq-row-min:          4.5rem;   /* 72px, to §8's 73 */
  --faq-row-pad-block:    var(--space-5);
  /* Shared by the trigger AND the panel — that is the whole point of
     it being one token. The question and its answer have to start on
     the same line, and two independent values would drift. */
  --faq-row-pad-inline:   var(--space-5);
  --faq-row-gap:          var(--space-5);  /* question to chevron  */

  --text-faq-question:    var(--text-lg);
  --text-faq-answer:      var(--text-body);

  /* Decorative: the rules separate rows that the questions already
     separate on their own, so the hairline is the right token here —
     unlike a form control boundary. */
  --color-faq-rule:       var(--color-border);
  /* The same 20px box §8 uses — the two accordions now share their
     indicator, so they should share its size. Duplicated rather than
     pointed at --specs-icon because a cross-component token reference
     is the wrong fix; the right one is F6. */
  --faq-icon:             1.25rem;

  /* NOT derivable from the wireframe — every row is drawn collapsed,
     so it shows nothing about the open state. These are the project's
     ordinary paragraph steps until a reference says otherwise. */
  /* Two values: block-start then block-end. It is applied with
     padding-block, never padding — as `padding` it silently became an
     inline value too and indented every answer past its question. */
  --faq-panel-pad-block:  var(--space-2) var(--space-5);
  --faq-answer-measure:   70ch;

  /* --- Cookie consent -------------------------------------------
     Every value here is FIXED rather than fluid, and that is the
     whole point: the card has a hard max-width, so anything sized
     off the viewport carries on growing after the card has stopped.
     --text-panel-heading would reach 29px at a 1280px viewport
     inside a 512px card. A fixed box gets fixed type.

     Measured from the wireframe (card 504px) and from the strings:

       card     504  ->  32rem / 512px   (+1.6%)
       padding   25  ->  --space-5 / 24
       inner    450  ->  464
       column   146  ->  146.67          (exact)
       gap       10  ->  --space-3 / 12

     The card width is set by the ACTION ROW, not by the text: three
     equal columns, each holding its label on one line. Measured,
     "Preferences" is 93px of text. Against a 146.67px column the
     standard 24px button padding leaves 5.67px of slack — not enough
     to survive a font-metric difference — so these buttons take 16px
     instead, which lifts it to 21.67px. §7 tightens the same way for
     the same reason at its narrow breakpoint. */
  --cookie-max-w:             32rem;   /* 512px */
  --cookie-pad:               var(--space-5);
  --cookie-gap:               var(--space-5);  /* between blocks    */
  --cookie-gap-tight:         var(--space-3);  /* heading -> text   */
  --cookie-action-gap:        var(--space-3);
  --cookie-action-pad-inline: var(--space-4);

  /* Measured: "COOKIES ON THIS SITE" has a width:size ratio of 7.76
     in Archivo wdth 62. The wireframe's heading bar is 150px against
     a 504px card, which scales to 155px here and asks for 20px; its
     bar HEIGHT read as a cap height asks for ~23.6px. Same conflict
     the hero documents, resolved the same way — cap height wins.
     22px is also the floor of --text-panel-heading, so the panel
     family stays coherent without inheriting that token's growth. */
  --text-cookie-heading:      1.375rem;      /* 22px */
  --text-cookie-footnote:     var(--text-sm);

  /* --- Site footer (§26) -------------------------------------
     Fluid values are in cqi, not vw: every one of them is consumed
     inside .sitefooter, which is the query container, so they track
     the box the component actually got rather than the window. On a
     real page the two are the same number; in the style guide they
     differ by ~90px.
     Wireframe: 1199px canvas, content 65 -> 1133, so 1068 wide.
     Our content box at a 1440 viewport is 1440 - 2 x 36 = 1368,
     which makes the scale 1368 / 1068 = 1.281. Every figure below
     is a wireframe measurement multiplied by that and landed on
     the nearest step of the scale.

     COLUMNS. Pitch is 247 for the first three (x = 65, 312, 557)
     and the fourth runs 806 -> 1133, so it is 327 wide. The gutter
     itself is the one number the drawing does NOT fix: any gutter
     between 32 and 64 reproduces those x-positions to within a few
     px. What IS fixed is the split between the nav block and the
     signup column, 741 - g : 327, which moves only from 2.17 to
     2.12 across that whole range. So the ratio is measured and the
     gutter is chosen.

     The signup column sits at 30.6% of the content width either
     way, which is what the field has to hold an email address in. */
  --sitefooter-max:            var(--container-max);
  --sitefooter-cols:           minmax(0, 2.12fr) minmax(0, 1fr);
  --sitefooter-gap:            clamp(var(--space-6), 3.4cqi, var(--space-8));

  /* 102 and 65 in the wireframe. The bottom inset equals the left
     inset exactly, which is the tell that it is padding rather than
     an artboard edge; the top is 1.57x it. 128:80 is 1.6. */
  --sitefooter-pad-top:        clamp(var(--space-10), 9cqi, var(--space-12));
  --sitefooter-pad-bottom:     clamp(var(--space-8), 5.8cqi, var(--space-10));

  /* 200 in the wireframe -> 256 here, and I do not fully believe it.
     It is a genuine measurement: the legal line is drawn 200px below
     the longest column, twice the top padding. But 256px of empty
     black between the links and a copyright line is also exactly
     what you would get if the artboard were simply taller than its
     content and the line were pinned to the bottom. Implemented as
     measured. Halving it to var(--space-12) is a one-token change if
     it reads as a hole. */
  --sitefooter-gap-legal:      clamp(var(--space-9), 17.8cqi, 16rem);

  /* Heading bar bottom 113 -> first link top 143 = 30 -> 38. */
  --sitefooter-heading-gap:    var(--space-7);
  /* Link pitch is 26 -> 33.3, a line box plus a gap. Settled at T3:
     14px at --leading-body is 21.7, plus 4px of padding added to
     clear the 24px target minimum, plus 8 = 33.7. */
  --sitefooter-link-gap:       var(--space-2);

  /* Box is 137 -> 190, so 53 tall -> 68. The placeholder starts 17px
     inside the border and the arrow ends ~20px from it: one inset,
     24px, not two. The button carries 8px of its own padding, so the
     right inset is that much smaller to keep the arrow optically on
     the same line as the text.

     The BLOCK padding is 16, not 24, and it is not the input that
     decides that. The row is as tall as its tallest item, and the
     icon button is 38px (22px icon + its own 8px each side) against
     the input's 22.4px line box - so the height is 2 x pad + 40
     however large the text is. 14px of padding would hit 68 exactly
     and is not on the scale; 16 gives 72. */
  --sitefooter-field-pad-block:   var(--space-4);
  --sitefooter-field-pad-inline:  var(--space-5);
  --sitefooter-field-border-w:    1px;

  /* TYPE. Measured, not assumed: Archivo's cap height is 0.690 of the
     em and Inter's is 0.730, both read off canvas metrics rather than
     taken from a table.

       heading  bar 11px  -> 14.09 cap -> 20.4px  -> --text-xl
       link     bar  8px  -> 10.25 cap -> 14.0px  -> --text-md

     The link bar is 7-8px in the drawing and that 1px is 12% of it,
     so cap height alone only narrows the link to 12-14px. The row
     PITCH breaks the tie: 26px in the wireframe is 33.3 here, and
     14px at --leading-body is a 21.7px line box, +4px of target-size
     padding, +8px gap = 33.7. 13px cannot reach that pitch on any
     step of the scale, and 16px overshoots the cap by 27%.

     The heading's WIDTH says 23.8px, not 20.4 - "COMPANY" is 3.449
     ratio units and the bar is 82px. Cap wins, as it does at §18 and
     §19, and here for a second reason: the copy in this footer is
     mine, so a width derivation would be fitting a size to a string
     I picked, which is circular. Cap height does not care what the
     words are. */
  --text-sitefooter-heading:  var(--text-xl);   /* 20 */
  --text-sitefooter-link:     var(--text-md);   /* 14 */
  --text-sitefooter-legal:    var(--text-md);
  /* 16px, not the 14 the rest of the footer uses: under 16px iOS
     Safari zooms the page on focus, and a page that jumps when you
     tap the field is a worse fault than a 2px type mismatch. It costs
     nothing in height - the icon button is taller than the text
     either way. */
  --text-sitefooter-input:    var(--text-lg);   /* 16 */

  /* COLOUR. Measured against this ground, not assumed:

       #FFFFFF   19.80   headings, link hover
       #8C8C8C    5.89   links, legal line, field border
       #5A5A5A    2.87   --color-ink-muted, the documented failure
       #0B5FFF    3.86   the global focus ring
       #FF4713    5.81   brand orange

     The wireframe draws its links in a slate blue-grey. Read by eye
     that sits near 5.7:1, which #8C8C8C matches at 5.89 - so the
     luminance is the reference's and only the hue is ours, because
     the palette has no blue-grey and this is not the place to add
     the first one.

     #8C8C8C is also --color-input-border's value, where it is the
     lightest grey clearing 3:1 on WHITE at 3.36. The same grey
     scores 5.89 here. That is a coincidence of the two grounds, not
     a shared meaning, so the link keeps its own token.

     Kept component-scoped rather than promoted to a global
     --color-ink-muted-inverse: this is the first dark surface that
     needs muted text. The second one is when it becomes a palette
     decision. */
  --color-sitefooter-surface:      var(--color-surface-inverse);
  --color-sitefooter-heading:      var(--color-ink-inverse);
  --color-sitefooter-link:         #8C8C8C;
  --color-sitefooter-link-hover:   var(--color-ink-inverse);
  --color-sitefooter-legal:        var(--color-sitefooter-link);
  --color-sitefooter-field-border: var(--color-input-border);

  /* --- Detail band (§27) ---------------------------------------
     Wireframe: 1200 canvas, rules spanning 66 -> 1134, so the same
     1068 content column and the same 1.281 scale as §26. That is not
     a coincidence worth ignoring - in the page wireframe the rules
     start at x=66 and the footer's first column starts at x=65, so
     the two components are drawn in ONE content column. This band
     therefore takes --container-max, not the --hero-max §20 uses.
     (§20 has --hero-max because it was asked for, not because it was
     measured, so there is nothing to be consistent with.)

     ROW HEIGHT. Rules at 308 and 362 give 54 -> 69.2. Shipping 72.
     The 1px I can read off a low-fi drawing is 1.3px here, so the
     measurement covers 67.9-70.5 and 72 sits just outside it - but
     §8 is 73 and §20 is 72, and a fourth accordion 3px shorter than
     the other three would be an accident nobody could see and
     everybody would inherit. --detail-row-min is one token if the
     literal 69 is preferred.

     THE PLUS SITS FLUSH with the left end of the rule, and the label
     starts 24 in (-> 30.7). That 24 is not padding on the trigger:
     it is the icon (12 -> 15.4) plus the gap to the label (12 ->
     15.4). Expressed as those two things, 16 + 16 = 32, so the
     trigger's inline padding is zero and the rules run edge to edge
     exactly as drawn. */
  --detail-max:              var(--container-max);

  /* 46 above the first rule and 49 below the last -> 59 and 63. Both
     land on --space-9, so the band is symmetric. In cqi because it
     is consumed inside the container (T4), which also means it has
     to be applied to __inner rather than to the band itself - an
     element never resolves cqi against itself. §26 documents this. */
  --detail-pad-block:        clamp(var(--space-7), 4.4cqi, var(--space-9));

  --detail-row-min:          4.5rem;   /* 72, to §20's 72 and §8's 73 */
  --detail-row-pad-block:    var(--space-5);
  --detail-sign-gap:         var(--space-4);
  --detail-icon:             var(--icon-sm);   /* 16, from the 12px glyph */

  /* TWO tokens, not one shorthand. §20 held "var(--space-2)
     var(--space-5)" in a single token and it was applied with
     `padding:` instead of `padding-block:` - the second value became
     inline padding on the panel alone and every answer sat 24px past
     its question. A shorthand in a custom property cannot be
     type-checked by anything; two named values can only go where
     they are put. */
  --detail-panel-pad-top:    var(--space-2);
  --detail-panel-pad-bottom: var(--space-5);
  --detail-answer-measure:   70ch;

  /* TYPE. The label bar is 201 x ~8. The WIDTH is useless here for
     the same reason it was at §26 - the string is one I chose, and a
     201 -> 257.5px bar at 14px needs about 40 characters, which is a
     question ("What documents do I need to rent a car?" is 39), not
     one of the About labels in the default data. So cap height:
     8 x 1.281 = 10.25, Inter's cap is 0.730, giving 14.0px.

     §20 sets its question at 16. This one does not follow it, and the
     row height DID follow §20 (72 over a measured 69). The rule that
     resolves both: a difference nobody can see goes to the system, a
     difference they can goes to the reference. 3px of row height is
     invisible and would have been inherited by every accordion after
     it; 14 against 16 is a seventh of the type size and plainly
     visible in the drawing.

     WEIGHT is not in the drawing at all - a filled bar carries no
     weight information, only height. So the system decides, and the
     system already has an answer for this exact control: §20's
     question is semibold.

     The answer size is §20's, unchanged. Every panel in the wireframe
     is closed, so there is nothing to measure and nothing to argue
     with. */
  --text-detail-label:       var(--text-md);     /* 14 */
  --text-detail-answer:      var(--text-body);   /* 15, from §20 */

  /* COLOUR, measured on white (black-on-white returned 21.00 first):

       #0A0A0A          19.80   label, and the plus/minus glyph
       rgba(0,0,0,.14)   1.38   the rules, composited to #DBDBDB
       #0B5FFF           5.13   focus ring

     The rules do not clear the 3:1 of SC 1.4.11 and do not need to.
     What identifies the control is its label at 19.80 and its plus
     at 19.80 - delete every rule and the buttons are still
     unmistakably buttons. The rules separate rows; they are not the
     control's boundary. Same call §20 made with the same value.
     It is a judgement, not a measurement: raising the alpha to .5
     would score 3.95 if the rules should read as structure. */
  --color-detail-rule:       var(--color-border);
  --color-detail-label:      var(--color-ink);

  /* --- Metrics (§28) ---------------------------------------------
     Ported from the style guide (src/css/metrics.css). Values below
     are copied, not referenced, from the components they were
     measured against there. */
  --metrics-max:            var(--container-max);
  --metrics-cols:           repeat(3, minmax(0, 1fr));
  --metrics-gap:            var(--space-7);      /* 40 */
  --metrics-pad-block:      clamp(var(--space-10), 9cqi, var(--space-12));
  --metrics-heading-gap:    clamp(var(--space-7), 4.4cqi, var(--space-9));
  --metrics-rule-gap:       var(--space-6);      /* rule -> value    */
  --metrics-caption-gap:    var(--space-5);      /* value -> caption */
  --metrics-rule-w:         1px;

  --text-metrics-value:     clamp(3.5rem, 6.32cqi, 5.6875rem);   /* 91 at 1440 */
  --text-metrics-heading:   clamp(2rem, 3.33cqi, 3rem);          /* 48 at 1440 */
  --text-metrics-caption:   var(--text-xs);

  --color-metrics-surface:  var(--color-surface-inverse);
  --color-metrics-heading:  var(--color-ink-inverse);
  --color-metrics-value:    var(--color-ink-inverse);
  --color-metrics-caption:  var(--color-ink-muted-inverse);
  --color-metrics-rule:     var(--color-ink-muted-inverse);

  /* --- Media band ----------------------------------------------
     New component, no style-guide section to derive from (see
     media-band.pug). Ratio matches the reference photo supplied
     (1599 x 449.7 = 3.556) closely enough that 32/9 (3.556 exactly)
     was picked over inventing a rounder number with no source. A
     max-height caps it separately from the ratio: bled to 100vw with
     no cap, an ultra-wide monitor would stretch this to 700px+ of
     pure photo — object-fit:cover crops to the cap rather than the
     band growing to whatever the ratio implies at that width. */
  --media-band-ratio:     32 / 9;
  --media-band-max-h:     34rem;   /* 544 */
  --media-band-gap:       var(--space-8);   /* 48, bottom only — see media-band.css */

  /* --- Motion ------------------------------------------------ */
  --ease:        cubic-bezier(.2, .6, .2, 1);
  --dur-fast:    120ms;
  --dur:         200ms;

  /* --- Radii ------------------------------------------------- */
  --radius-sm:   2px;
  --radius-md:   6px;
  --radius-pill: 999px;

  /* --- Stacking ---------------------------------------------- */
  --z-header:  100;
  --z-panel:   110;
  --z-skip:    200;
}

/* Breakpoints cannot live in custom properties — media queries do
   not resolve var(). Documented here as the single source of truth:

     sm    480px
     auth  560px   sign-in provider buttons stack
     cta   768px   newsletter CTA stacks to one column
     lg   1024px
     nav  1100px   main nav collapses to the burger + drawer
     xl   1280px
*/
