/* ==========================================================
   ht-acc / tokens.css
   Layer 3. The only file in this project that may contain a
   literal colour, font name or pixel value.

   Four documented exceptions live outside this file, because
   none of them can read a custom property. They are written out
   here in words rather than in placeholder syntax, so that a
   find and replace fill cannot rewrite this comment.

     1. meta theme-color        carries the brand colour as a
                                placeholder, in every page head
     2. site.webmanifest        the same, twice
     3. generated brand assets  favicon, OG card, icons. Built
                                from src/make-assets.py, which
                                reads the accent out of this file
                                and derives the rest the same way
                                the CSS below does
     4. media query conditions  in layout.css, and in the two
                                (scripting: none) blocks that
                                mirror it, effects.css and
                                noscript.css. CSS does not allow a
                                custom property inside a media
                                feature, and there is no build step
                                to substitute one. Four numbers,
                                760, 761, 1100 and 1101

     One more, and it is a fallback rather than a value:
     env(safe-area-inset-bottom, 0px) takes a 0px default in the
     sticky call bar and in the body padding that matches it.

   md/README.md carries the full reasoning.
   ========================================================== */

/* ─ face ────────────────────────────────────────────────────
   Plus Jakarta Sans, wght 200 to 800, served by Google Fonts.
   The @font-face comes from their stylesheet, which every page
   head requests with media="print" so it never blocks the first
   paint, then promotes with js/modules/fonts.js.

   Nothing about the face is declared here, so this file no
   longer decides where the font comes from, only what it is
   called. The name below has to match the family Google serves.

   Two consequences, both recorded in md/README.md. The CSP in
   _headers has to allow two Google origins. And Google serves
   this family in four subsets, none of which carries U+2190,
   U+2192, U+2605 or U+2606, so the arrows and the rating stars
   are inline SVG rather than font glyphs.
   ────────────────────────────────────────────────────────── */

:root {

  /* ═ colour ═══════════════════════════════════════════════ */

  /* contract */
  --ink:            #1C1C1E;
  --paper:          #FFFFFF;
  --surface:        #F4F4F5;
  --muted:          #6E6E73;
  --accent:         #1F3A5F;
  --accent-ink:     #FFFFFF;
  --rule:           #E4E4E7;

  /* ─ the derived accent chain ──────────────────────────────
     Four colours, all derived, so moving --accent alone moves
     every one of them and nothing is left stale. Each lifts or
     drops OKLCH lightness at a fixed hue, which is what makes
     the result read as the same colour turned up or down
     rather than as a tint mixed with grey.

     The declarations here are the floor. The @supports block
     at the foot of this file is the real one. Both are derived,
     so neither can go stale. The order cannot be reversed: a
     custom property accepts any token sequence at parse time,
     so a browser without relative colour syntax stores the
     declaration happily and only fails when it is substituted
     into background, at which point the control turns
     transparent and its white label vanishes. @supports is the
     only guard that catches that, because declaration order and
     the var() fallback both miss it.

     Do not swap the floors for a plain mix on their own. A mix
     toward white desaturates, so it reads washed out rather
     than lighter. They are the floor and nothing else.

     Three of the four are pinned to an absolute lightness rather
     than to a delta, because their job is to be legible on one
     particular field rather than to look like the brand. The
     @supports block at the foot of this file says which is which
     and why. Every ratio quoted below was measured on #1F3A5F:
     they are a record of this build, not a promise about yours,
     and md/README.md carries the table across nine accents.
     ──────────────────────────────────────────────────────── */

  /* Solid fills that carry white text. On this build the base
     accent already scores 11.48:1 on paper, well clear of the
     4.5 AA floor, so this rung is not rescuing legibility the
     way it does for a mid-tone accent; it scores 13.05:1 and
     stays as the solid brand fill. --accent itself stays for
     non-text fills, bars, chart columns and dots, where text
     contrast does not apply. */
  --accent-strong:  color-mix(in srgb, var(--accent) 88%, black);

  /* Text on the pale accent field. For a mid-tone accent this
     is the one place the strong accent is not enough; on this
     navy --accent-strong on --accent-soft already reaches
     8.81:1, so the rung holds the ladder's shape rather than
     rescuing it. It lands at 4.53:1, the tightest rung in this
     build. Used by the carousel badge digits. */
  --accent-deep:    color-mix(in srgb, var(--accent) 68%, black);

  /* Accent text on the ink field, and this build needs it more
     than the periwinkle did: the base accent is 1.48:1 there
     and fails badly, because a dark navy on near black is
     barely a colour change at all. Pinning lightness to 0.625
     takes it to 4.75:1. Used by the footer credit. */
  --accent-lift:    color-mix(in srgb, var(--accent) 92%, white);

  /* The pale accent field itself: pill fills, badges, soft
     buttons, the inverse focus ring. */
  --accent-soft:    color-mix(in srgb, var(--accent) 30%, white);

  /* ─ palette ───────────────────────────────────────────────
     Support values the contract does not cover. All neutral,
     none derived from --accent, because none of them should
     move when the brand does. */

  /* Idle fill dark enough to clear 3:1 against both --paper
     and --surface, for meaningful non-text graphics sitting
     inside surface cards. The chart's twelve columns. */
  --grey-mid:       #8A8A8F;

  /* Google's own star colour. Deliberately not derived: it
     stands for a third party's mark, not for this firm. */
  --gold:           #F5B921;

  --placeholder-fill: #E7E7EA;
  --placeholder-line: #C9C9CF;

  /* ─ alpha steps on the ink field ──────────────────────────
     White at an alpha, so what they blend to depends on how
     dark --ink is. Safe as they stand for any ink at a
     relative luminance below about 0.06. md/README.md carries
     the table. */
  --ink-text-soft:  rgb(255 255 255 / 62%);
  --ink-rule:       rgb(255 255 255 / 14%);


  /* ─ the hero field ────────────────────────────────────────
     The ruled lines behind the first screen and the figures
     picked out along them. Derived, so they follow --accent for
     free. The source wrote its version of these as
     rgb(92 111 238 / 6%), which is the accent spelled out by
     hand and the single most likely thing to be left behind by
     a brand change. */
  --hero-rule:      color-mix(in srgb, var(--accent) 11%, transparent);
  --hero-figure:    color-mix(in srgb, var(--accent) 18%, transparent);

  /* The mask that fades the ruled lines out toward the headline.
     Declared here rather than in the component because a mask needs
     an opaque colour to mean "keep this", and #000 in a mask is the
     alpha channel rather than a colour: nothing black is ever
     painted. Keeping it here is what stops a literal appearing in
     components.css. The stop reads --ledger-hold at use time, so the
     tablet band's narrower hold is picked up for free. */
  --ledger-mask:    linear-gradient(to left, #000 var(--ledger-hold), transparent 100%);

  /* ─ semantic aliases ──────────────────────────────────────
     Components read these, never the base values above, so a
     component says what a colour is for rather than what it
     is. */
  --bg-page:            var(--paper);
  --bg-card:            var(--surface);
  --bg-panel:           var(--ink);
  --text-primary:       var(--ink);
  --text-secondary:     var(--muted);
  --text-inverse:       var(--paper);
  --text-inverse-soft:  var(--ink-text-soft);
  --text-on-accent:     var(--accent-ink);
  --text-on-soft:       var(--accent-deep);
  --text-accent:        var(--accent-strong);
  --text-credit:        var(--accent-lift);
  --border-strong:      var(--ink);
  --border-hairline:    var(--ink-rule);
  --fill-accent:        var(--accent);
  --fill-accent-text:   var(--accent-strong);
  --fill-accent-soft:   var(--accent-soft);
  --fill-idle:          var(--rule);
  --fill-idle-strong:   var(--grey-mid);
  --focus-ring:         var(--accent);
  --focus-ring-inverse: var(--accent-soft);
  --rating-star:        var(--gold);
  --rating-star-idle:   var(--placeholder-line);

  /* ═ type ═════════════════════════════════════════════════ */

  --font-display: 'Plus Jakarta Sans', -apple-system, 'Segoe UI', system-ui, sans-serif;
  --font-body:    'Plus Jakarta Sans', -apple-system, 'Segoe UI', system-ui, sans-serif;
  --font-utility: 'Plus Jakarta Sans', -apple-system, 'Segoe UI', system-ui, sans-serif;


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

  /* Heading weights, as roles rather than rungs. Every heading
     on the site reads one of these three, so the whole
     hierarchy moves together from here without hunting through
     components.css. Each sits one rung below where the export
     set it: hero was black, heading was bold, subhead was
     semibold. The footer title is the fourth and lightest
     heading and carries its own name beside --footer-title-size.

     The face is variable and Google serves 400 to 800, so every
     rung on the ramp above is a real weight, not a synthesis. */
  --weight-hero:     var(--weight-bold);
  --weight-heading:  var(--weight-semibold);
  --weight-subhead:  var(--weight-medium);

  /* ─ contract steps ────────────────────────────────────────
     The six the kit names, mapped onto the rungs this design
     actually uses, so a layer 2 component written against the
     contract drops in unchanged. The role names below are the
     working names, which is the same arrangement ht-law uses
     for its space ramp. */
  --step--2: 12px;                      /* extension: eyebrow, label */
  --step--1: 14px;
  --step-0:  16px;
  --step-1:  19px;
  --step-2:  32px;
  --step-3:  40px;
  /* The floor is 64px rather than 40px. The 40 never bound: this band
     opens at 1101 where 4.4vw is 48.4px, so the desktop headline
     started smaller than the phone one at 760 and smaller than the
     tablet one all the way to about 1455. Lifting the floor fixes the
     low end and leaves wide screens exactly as they were, because
     4.4vw overtakes 64px at 1455 and the 76px cap still lands at
     1727. */
  --step-4:  clamp(64px, 4.4vw, 76px);

  /* ─ role sizes ────────────────────────────────────────────
     Extensions. Every one is used by this composition. */
  --fs-hero:      var(--step-4);
  --fs-h2:        var(--step-3);
  --fs-h2-lg:     var(--step-3);
  --fs-h2-dark:   var(--step-2);
  --fs-h3:        var(--step-1);
  --fs-h4:        var(--step-0);
  --fs-body:      var(--step-0);
  --fs-body-sm:   15px;
  --fs-nav:       15px;
  --fs-btn:       15px;
  --fs-pill:      var(--step--1);
  --fs-micro:     13px;
  --fs-eyebrow:   var(--step--2);
  --fs-figure:    34px;
  --fs-star:      18px;
  --fs-glyph:     16px;
  --fs-glyph-sm:  14px;

  /* ─ line height ───────────────────────────────────────────
     Two in the contract, six here. */
  --lh-tight:  1.2;
  --lh-body:   1.5;
  --lh-solid:  1;                       /* extension: centred glyph in a circle */
  --lh-hero:   0.95;                    /* extension */
  --lh-h2:     1.05;                    /* extension */
  --lh-prose:  1.6;                     /* extension: the regulatory pages */

  /* ─ tracking ──────────────────────────────────────────────
     One in the contract, five here. */
  --track-display: -0.01em;
  --track-tight:   -0.015em;            /* extension */
  --track-wide:     0.02em;             /* extension */                /* extension */
  --track-eyebrow:  0.3em;              /* extension */

  --measure:        42ch;
  --measure-sub:    46ch;               /* extension */
  --measure-narrow: 34ch;               /* extension */
  --measure-prose:  68ch;               /* extension: the regulatory pages */

  /* Casing is a token because the v1 page set the hero
     uppercase and this one does not. Kept so the decision is
     visible rather than buried in a component rule. */
  --type-hero-transform:    none;
  --type-eyebrow-transform: uppercase;

  /* ═ space ════════════════════════════════════════════════ */

  /* 0.85 tight · 1 default · 1.25 generous. Scales the ramp. */
  --density: 1;

  /* Value-named ramp. The number is the base pixel value at
     --density: 1, so at 1 every value is identical to the
     design's own. Renamed from the source's --space-1 to
     --space-16, which collided with the contract's eight-step
     --space-1 to --space-8 at completely different values.
     Same arrangement ht-law uses, and the same reason. */
  --sp-4:    calc(4px   * var(--density));
  --sp-8:    calc(8px   * var(--density));
  --sp-12:   calc(12px  * var(--density));
  --sp-16:   calc(16px  * var(--density));
  --sp-20:   calc(20px  * var(--density));
  --sp-24:   calc(24px  * var(--density));
  --sp-26:   calc(26px  * var(--density));
  --sp-32:   calc(32px  * var(--density));
  --sp-38:   calc(38px  * var(--density));
  --sp-44:   calc(44px  * var(--density));
  --sp-52:   calc(52px  * var(--density));
  --sp-64:   calc(64px  * var(--density));
  --sp-76:   calc(76px  * var(--density));
  --sp-94:   calc(94px  * var(--density));
  --sp-126:  calc(126px * var(--density));
  --sp-156:  calc(156px * var(--density));

  /* Contract aliases. A component written against the ht-kit
     eight-step ramp works here unchanged. */
  --space-1: var(--sp-4);
  --space-2: var(--sp-8);
  --space-3: var(--sp-12);
  --space-4: var(--sp-16);
  --space-5: var(--sp-24);
  --space-6: var(--sp-32);
  --space-7: var(--sp-44);
  --space-8: var(--sp-64);

  --gutter:     48px;
  --max-width:  1440px;


  /* ═ form ═════════════════════════════════════════════════ */

  --radius:        20px;                /* contract: the card radius */
  --radius-md:     14px;
  --radius-sm:     10px;
  --radius-xs:     8px;
  --radius-pill:   999px;
  --radius-circle: 50%;

  --border:          1px;               /* contract: the hairline */
  --border-w:        1.5px;
  --border-w-strong: 2px;

  --tap-min:     48px;
  --control-sm:  40px;
  --control-lg:  50px;
  --avatar-md:   42px;
  --badge-md:    34px;

  --ring-w:      3px;
  --ring-offset: 2px;

  --offscreen:  -9999px;                /* honeypot parking */

  /* Clears the sticky header, with air, so an in-page anchor
     does not land underneath it. */
  /* Clears the sticky header, with air, so an in-page anchor never
     lands underneath it. The header measures 98px stuck at every
     band, so 96 put the target two pixels behind the bar. This is
     that height plus a clear line of air.

     It matters more than it used to. The services page is gone and
     the nav's Services item is an anchor into the home page now, so
     this is the offset the primary navigation lands on rather than a
     detail on one inner page. Re-measure it if the header's padding
     or type size moves. */
  --sticky-top: 124px;

  --z-base:  0;
  --z-raise: 1;
  --z-over:  2;
  --z-top:   10;
  --z-panel: 90;                        /* FX/THRESHOLD and the nav drawer */
  --z-panel-top: 95;                    /* the header while the drawer is
                                           open, so the control that opens
                                           the panel stays above it */

  /* ═ motion ═══════════════════════════════════════════════ */

  --ease:      cubic-bezier(.2, .7, .3, 1);
  --dur-fast:  120ms;
  --dur-base:  220ms;
  --dur-slow:  420ms;

  --hover-opacity:      0.88;
  --hover-opacity-soft: 0.82;

  /* FX/WIPE, on every button. ACTIONS family, Quiet tier, HOVER
     input. A solid block rises from the bottom edge and the label
     inverts over it.

     Two fills, because the button it rises behind decides what can
     be read on top of it. The solid button is already the accent, so
     it wipes to --ink and keeps its white label at 17.01:1. The soft
     and outline buttons wipe to the accent itself, and their label
     inverts to --accent-ink at 13.05:1. Both measured on #1F3A5F.

     --dur-base rather than --dur-fast: at 120ms the block is over
     before the eye has followed it, which reads as a flicker rather
     than as a rise. Past about 350ms a button stops feeling
     connected to the cursor, so this is the top of the useful range
     and not the middle of it. */
  --wipe-fill:        var(--ink);
  --wipe-fill-soft:   var(--accent-strong);

  /* A button standing on the ink field cannot wipe to ink: the block
     would be the same colour as the panel behind it and the button
     would appear to vanish, leaving its label floating. On that field
     the wipe inverts and the label goes dark. Used by the closing
     call to action, which sits on the ink panel, and by the header
     once it has stuck and turned ink itself. */
  --wipe-fill-inverse: var(--paper);
  --dur-wipe:         var(--dur-base);

  /* The label does not cross-fade, it flips in one frame, and the
     flip is timed to the block's leading edge. Both halves of that
     are measured rather than chosen.

     A cross-fade passes through a mid grey that has no contrast
     against either field: sampled through the transition it bottomed
     out at 1.24:1 on the way in and 1.30:1 on the way out, which
     reads as the label vanishing and coming back. An instant flip
     has no mid state, so the only exposure left is the frames where
     the block's edge is crossing the label and part of it is on each
     field. That floor is 3.49:1, the label's own colour against the
     block, and it lasts a frame or two.

     The two delays differ because the block is front-loaded by
     --ease and so crosses the label sooner on the way out than it
     clears it on the way in. Symmetric delays measured 1.49:1 in one
     direction or the other; these hold 3.49:1 both ways.

     Retime --dur-wipe or --ease and both have to be measured again.
     Neither resting state is affected: the label is 11.46:1 before
     the hover and 4.87:1 after it. */
  --dur-invert:        0s;
  --delay-invert-in:   60ms;
  --delay-invert-out:  20ms;

  /* FX/THRESHOLD, the first-load panel. --dur-preload-hold and
     --dur-preload-out are the same two numbers as HOLD and OUT
     at the top of js/modules/loader.js. They are the one place
     in this template where a CSS value and a JS value have to
     agree. Change them together.

     620 and 420 are the source's own timings: it waited a
     minimum of 620ms from navigation start, then faded over
     the 420ms its transition fell back to. */
  --dur-preload-hold:  620ms;
  --dur-preload-out:   420ms;
  --dur-preload-pop:   420ms;           /* mark and word rise in */
  --delay-preload-pop:  80ms;           /* the word waits for the mark */
  --dur-preload-sweep: 1100ms;          /* the bar, one pass */

  /* No page transition on this site, so --ease-page and the
     three --dur-page-* tokens are deliberately absent.
     FX/THRESHOLD is Considered rather than Signature and runs
     once per session rather than on every navigation, so it
     spends nothing and forbids nothing. */

  /* ═ composition ══════════════════════════════════════════
     Layer 3, this page. Every value the design decides for
     itself, so no component rule holds a literal. */

  /* ─ frame ─────────────────────────────────────────────── */
  --section-gap:               var(--sp-156);
  --section-gap-sm:            var(--sp-126);
  /* ─ header ────────────────────────────────────────────── */
  --brand-mark:                34px;
  --nav-link-pad:              18px;

  /* auto 1fr auto, not 1fr auto 1fr. The middle column is the
     flexible one and the nav is pushed to its end, so the links
     sit against the call to action on the right rather than
     centred in the bar. */
  --header-cols:               auto 1fr auto;

  /* The header's own gutter, so the bar can hug the viewport
     more closely than the reading column does without dragging
     every section out with it. It matches --gutter on the widest
     band and comes in one rung of the ramp on each band below,
     where the screen is narrow enough that the side margin is
     the expensive part. This is the same idea as --header-max:
     the header is chrome and belongs to the viewport, the
     content column is for reading and stays where it is. */
  --header-gutter:             var(--gutter);
  --header-max:                1680px;
  /* ─ hero ──────────────────────────────────────────────── */
  /* The hero field: the ruled lines of an accounts book, fading in
     from the right edge with four figures picked out along them.

     It sizes by line pitch rather than by counting elements, which is
     what the grid of panes it replaced could not do. That grid had to
     hide five panes at 1100 and another four at 760 to stay out of
     the headline's way; this one just narrows.

     --ledger-hold is how much of the field stays solid before the
     mask begins to fade it out toward the headline. The fade is what
     keeps the lines off the type without a hard edge. */
  --ledger-w:                  72%;
  --ledger-hold:               38%;
  --ledger-pitch:              34px;
  --ledger-inset:              var(--sp-64);
  --ledger-bar-h:              10px;
  --ledger-fig-1:              96px;
  --ledger-fig-2:              148px;
  --ledger-fig-3:              72px;
  --ledger-fig-4:              120px;
  --hero-copy-w:               16ch;
  --header-h:                  72px;
  --hero-share:                70svh;
  --hero-min-h:                calc(var(--hero-share) - var(--header-h));
  --hero-pad-block-start:      var(--sp-64);
  --hero-pad-block-end:        var(--sp-94);
  /* ─ dark sloped panel ─────────────────────────────────── */

  /* Square. The panel bleeds to both viewport edges and its top edge
     is already a drawn shape, so a rounded corner on the other three
     read as a card floating on the page rather than as a band cut
     into it. Its own token rather than the contract's --radius, so
     squaring the panel does not square the tiles and the cards with
     it. */
  --stats-radius:              0;

  --step-h:                    70px;
  --step-w:                    36%;
  --slope-run:                 220px;
  /* The centred block is the whole of the dark panel now that the
     intro line is gone, so it carries its own air rather than
     relying on the panel's padding to give it any. */
  --stats-row2-pad-block:      var(--sp-64);

  --stats-pad-block-start:     var(--sp-94);
  --stats-pad-block-end:       var(--sp-126);
  --accred-gap:                var(--sp-64);
  --accred-rule-w:             var(--border);
  --accred-rule-h:             24px;
  /* ─ heading row ───────────────────────────────────────── */
  --headrow-cols:              1.4fr 1fr;
  --headrow-pad-start:         var(--sp-126);
  --headrow-align:             start;
  --headrow-title-align:       start;
  --headrow-aside-align:       end;
  --headrow-aside-justify:     end;
  /* ─ data bento ────────────────────────────────────────── */
  --bento-cols:                1.55fr 1fr;
  --bento-right-drop:          0px;
  --datacard-pad:              var(--sp-32);
  --datacard-gap:              var(--sp-20);
  --chart-h:                   176px;
  --chart-gap:                 var(--sp-12);
  --chart-label-size:          var(--fs-micro);
  --bar-radius:                var(--radius-sm);

  /* The UK quarterly VAT calendar, not this firm's data. Most
     VAT registered businesses file quarterly, and the return and
     any payment are due one calendar month and seven days after
     the quarter ends. For the commonest stagger group, whose
     quarters end in March, June, September and December, that
     falls on 7 February, 7 May, 7 August and 7 November.

     These are dates rather than magnitudes, so the twelve values
     are binary: a month either carries a deadline or it does
     not. Drawing a height difference between the four would be
     inventing a quantity nothing measures. The other two stagger
     groups shift the same four dates by one and by two months,
     which the chart key says in words. */
  --bar-jan:                    24%;
  --bar-feb:                   100%;
  --bar-mar:                    24%;
  --bar-apr:                    24%;
  --bar-may:                   100%;
  --bar-jun:                    24%;
  --bar-jul:                    24%;
  --bar-aug:                   100%;
  --bar-sep:                    24%;
  --bar-oct:                    24%;
  --bar-nov:                   100%;
  --bar-dec:                    24%;

  --facts-gap:                 var(--sp-20);
  --fact-value-size:           36px;
  --split-cols:                40fr 60fr;
  --split-gap:                 var(--gutter);
  --split-pad-start:           var(--sp-94);
  --split-pad-end:             calc(var(--section-gap) + var(--sp-64));
  --steps-gap:                 var(--sp-26);
  --step-cols:                 auto 1fr;
  --step-num-w:                4ch;
  /* ─ services tiles ────────────────────────────────────── */
  --tiles-cols:                repeat(6,1fr);
  --tiles-rows:                var(--tile-half-h) var(--tile-half-h) var(--tile-row-h);
  --tiles-gap:                 var(--sp-24);
  --tile-pad:                  var(--sp-44);
  --tile-half-h:               244px;
  --tile-row-h:                270px;
  --tile-1-col:                1 / 4;
  --tile-1-row:                1 / 3;
  --tile-2-col:                4 / 7;
  --tile-2-row:                1 / 2;
  --tile-3-col:                4 / 7;
  --tile-3-row:                2 / 3;
  --tile-4-col:                1 / 3;
  --tile-4-row:                3 / 4;
  --tile-5-col:                3 / 5;
  --tile-5-row:                3 / 4;
  --tile-6-col:                5 / 7;
  --tile-6-row:                3 / 4;
  --tile-arrow-size:           var(--fs-h3);
  --tile-name-size:            26px;
  --tile-feature-name-size:    40px;
  --tile-feature-desc-size:    var(--fs-h4);
  /* ─ audience carousel ─────────────────────────────────── */
  --serve-head-cols:           1.35fr 1fr;
  --serve-aside-align:         stretch;
  --serve-nav-w:               224px;
  --serve-track-h:             3px;
  --serve-btn-disabled:        0.35;
  --serve-visible:             4;
  --serve-card-w:              calc((100% - (var(--serve-visible) - 1) * var(--tiles-gap)) / var(--serve-visible));
  --serve-card-h:              312px;
  --serve-card-pad:            var(--sp-32);
  --serve-badge:               44px;
  /* ─ testimonials ──────────────────────────────────────── */
  --band-pad-block:            var(--sp-156);
  --band-head-cols:            1.4fr 1fr;
  --band-title-w:              22ch;
  --quote-card-h:              312px;
  --band-cols:                 repeat(2,1fr);
  --band-gap:                  var(--sp-24);
  --quote-card-pad:            var(--sp-44);
  /* ─ closing cta ───────────────────────────────────────── */
  --cta-pad-block:             var(--sp-156);
  --cta-pad-inline:            var(--sp-76);
  --cta-stack-gap:             var(--sp-38);
  --cta-line-w:                20ch;
  /* ─ footer ────────────────────────────────────────────── */
  --footer-pad-block:          var(--sp-126);
  --footer-row-gap:            var(--sp-76);
  --footer-lead-cols:          1fr 1fr;
  --footer-meta-cols:          1fr 1fr;
  --footer-title-size:         48px;
  --footer-title-weight:       var(--weight-regular);
  --footer-title-w:            16ch;
  --footer-cols:               repeat(4,1fr);
  /* The baseline row: copyright, legal links, credit. Set at the
     scale's smallest step rather than the body-small one the rest of
     the footer uses, which is the ordinary treatment for a legal
     line and keeps it subordinate to the four link columns above. */
  --footer-bottom-size:        var(--fs-micro);
  --footer-bottom-cols:        1fr auto 1fr;
  --footer-bottom-mid:         center;
  --footer-bottom-end:         end;

  /* ═ band variants ════════════════════════════════════════
     Three bands, at this design's own collapse points rather
     than the kit's default numbers. The reasoning is in
     md/README.md; the short version is that this composition
     changes structure at 760 and at 1100 and nowhere else.

     Every value either band reassigns is named here, so
     layout.css contains no literal at all: its band blocks do
     nothing but choose which of these to consume. That is the
     one structural difference from ht-law, which reassigns at
     the component rule instead. This design was composed as
     token reassignment with the component rules held constant
     across every band, and keeping that is what makes the
     three bands verifiably the same layout at three sizes.

     A token appears here only where its band differs from the
     band above, so the absence of a name is itself the record
     that nothing changes there.
     ────────────────────────────────────────────────────── */

  /* ─ band 2 · 761 to 1100 ──────────────────────────────── */
  --band-head-cols-tablet:         1fr auto;
  --bento-cols-tablet:             1fr;
  --brand-mark-tablet:             30px;
  --footer-cols-tablet:            1fr 1fr;
  --footer-lead-cols-tablet:       1fr;
  --footer-title-w-tablet:         100%;
  --fs-figure-tablet:              32px;
  --fs-h2-tablet:                  32px;
  --fs-h2-dark-tablet:             26px;
  --fs-h2-lg-tablet:               34px;
  --fs-h3-tablet:                  18px;
  /* The headline used to shrink as the screen grew. Measured across
     the boundary, the phone band ran it at 52px at 760 and this band
     picked it up at 36.5px at 761, a 15.5px drop going up a size.
     4.8vw was simply too shallow to carry a band that starts at 761.

     The ramp now reads phone 34 to 52, tablet 56 to 64, desktop 64
     up to 76, so the headline only ever grows with the screen. It
     took lifting --step-4's floor as well: capping tablet low enough
     to meet a 48.4px desktop was what kept it small. */
  --fs-hero-tablet:                clamp(56px,6.6vw,64px);
  --gutter-tablet:                 var(--sp-38);
  --header-gutter-tablet:          var(--sp-24);
  --header-h-tablet:               64px;
  --headrow-aside-align-tablet:    start;
  --headrow-aside-justify-tablet:  start;
  --headrow-cols-tablet:           1fr;
  --headrow-pad-start-tablet:      var(--sp-94);
  --hero-copy-w-tablet:            20ch;
  /* Pulled back from 78%, so the headline has the column and the
     field has what is left rather than the other way round. */
  --ledger-w-tablet:               58%;
  --ledger-hold-tablet:            34%;
  --ledger-pitch-tablet:           30px;
  --ledger-inset-tablet:           var(--sp-32);
  --nav-link-pad-tablet:           14px;
  --quote-card-h-tablet:           340px;
  --section-gap-tablet:            var(--section-gap-sm);
  --serve-head-cols-tablet:        1fr;
  --serve-visible-tablet:          2;
  --slope-run-tablet:              200px;
  --split-cols-tablet:             1fr;
  --split-gap-tablet:              var(--sp-52);
  --split-pad-end-tablet:          var(--section-gap);
  --split-pad-start-tablet:        var(--sp-64);
  --stats-pad-block-end-tablet:    var(--sp-156);
  --step-w-tablet:                 44%;
  --tile-1-col-tablet:             1 / 3;
  --tile-1-row-tablet:             auto;
  --tile-2-col-tablet:             1 / 2;
  --tile-2-row-tablet:             auto;
  --tile-3-col-tablet:             2 / 3;
  --tile-3-row-tablet:             auto;
  --tile-4-col-tablet:             1 / 2;
  --tile-4-row-tablet:             auto;
  --tile-5-col-tablet:             2 / 3;
  --tile-5-row-tablet:             auto;
  --tile-6-col-tablet:             1 / 3;
  --tile-6-row-tablet:             auto;
  --tile-half-h-tablet:            auto;
  --tile-name-size-tablet:         30px;
  --tile-row-h-tablet:             auto;
  --tiles-cols-tablet:             repeat(2,1fr);
  --tiles-rows-tablet:             auto;

  /* ─ band 1 · 760 and below ────────────────────────────── */
  --accred-gap-phone:              var(--sp-26);
  --band-cols-phone:               1fr;
  --band-head-cols-phone:          1fr;
  --band-pad-block-phone:          var(--sp-126);
  --band-title-w-phone:            100%;
  --bento-cols-phone:              1fr;
  --brand-mark-phone:              30px;
  --chart-gap-phone:               var(--sp-4);
  --chart-h-phone:                 156px;
  --chart-label-size-phone:        var(--fs-body-sm);
  --cta-line-w-phone:              100%;
  --cta-pad-block-phone:           var(--sp-94);
  --cta-pad-inline-phone:          var(--sp-32);
  --cta-stack-gap-phone:           var(--sp-26);
  --datacard-pad-phone:            var(--sp-32);
  --footer-bottom-cols-phone:      1fr;
  --footer-bottom-end-phone:       start;
  --footer-bottom-mid-phone:       start;
  --footer-cols-phone:             1fr;
  --footer-lead-cols-phone:        1fr;
  --footer-meta-cols-phone:        1fr;
  --footer-pad-block-phone:        var(--sp-94);
  --footer-row-gap-phone:          var(--sp-64);
  --footer-title-size-phone:       34px;
  --footer-title-w-phone:          100%;
  --fs-figure-phone:               36px;
  --fs-h2-phone:                   36px;
  --fs-h2-dark-phone:              28px;
  --fs-h2-lg-phone:                36px;
  --fs-hero-phone:                 clamp(34px,9vw,52px);
  --gutter-phone:                  var(--sp-24);
  --header-gutter-phone:           var(--sp-16);
  --header-cols-phone:             1fr auto;
  --header-h-phone:                88px;
  --headrow-aside-align-phone:     start;
  --headrow-aside-justify-phone:   start;
  --headrow-cols-phone:            1fr;
  --headrow-pad-start-phone:       var(--sp-126);
  --hero-copy-w-phone:             20ch;
  --hero-min-h-phone:              0px;
  --hero-pad-block-end-phone:      var(--sp-94);
  --hero-pad-block-start-phone:    var(--sp-64);
  --hero-share-phone:              auto;
  --nav-link-pad-phone:            14px;
  --quote-card-h-phone:            0px;
  --quote-card-pad-phone:          var(--sp-32);
  --section-gap-phone:             var(--sp-126);
  --serve-card-h-phone:            300px;
  --serve-card-pad-phone:          var(--sp-32);
  --serve-head-cols-phone:         1fr;
  --serve-visible-phone:           1;
  --slope-run-phone:               120px;
  --split-cols-phone:              1fr;
  --split-gap-phone:               var(--sp-52);
  --split-pad-end-phone:           var(--section-gap);
  --split-pad-start-phone:         var(--sp-94);
  --stats-pad-block-end-phone:     var(--sp-94);
  --stats-pad-block-start-phone:   var(--sp-64);
  --stats-row2-pad-block-phone:    var(--sp-52);
  --step-h-phone:                  56px;
  --step-w-phone:                  52%;
  --tile-1-col-phone:              auto;
  --tile-1-row-phone:              auto;
  --tile-2-col-phone:              auto;
  --tile-2-row-phone:              auto;
  --tile-3-col-phone:              auto;
  --tile-3-row-phone:              auto;
  --tile-4-col-phone:              auto;
  --tile-4-row-phone:              auto;
  --tile-5-col-phone:              auto;
  --tile-5-row-phone:              auto;
  --tile-6-col-phone:              auto;
  --tile-6-row-phone:              auto;
  --tile-feature-name-size-phone:  32px;
  --tile-half-h-phone:             auto;
  --tile-min-h-phone:              220px;
  --tile-pad-phone:                var(--sp-32);
  --tile-row-h-phone:              auto;
  --tiles-cols-phone:              1fr;
  --tiles-gap-phone:               var(--sp-20);
  --tiles-rows-phone:              auto;

  /* Everything below 1100 gains a hamburger and a drawer, phone
     and tablet both, which the design had no equivalent of
     because it was one page with in-page anchors. Sized to
     --control-lg so the header keeps exactly the height it has in
     the source. */
  --drawer-pad:              var(--sp-24);
  --drawer-gap:              var(--sp-8);
  --drawer-link-size:        var(--step-1);
  /* Sticky call bar, phone band only. Its height is derived from
     the control it holds rather than typed, so the body padding
     that keeps it off the footer cannot fall out of step with the
     bar itself. */
  --sticky-pad:              var(--sp-12);
  --sticky-h:                calc(var(--control-lg) + var(--sticky-pad) * 2 + var(--border));

  --hamburger-w:             18px;
  --hamburger-bar:           2px;
  --hamburger-gap:           5px;

  /* The distance one outer bar travels to reach the middle, so
     it is the gap plus its own thickness and it cannot drift
     out of step with the two above. --hamburger-turn is the
     quarter the whole set makes once the bars have crossed. */
  --hamburger-shift:         calc(var(--hamburger-gap) + var(--hamburger-bar));
  --hamburger-turn:          90deg;

  /* The icon carries its own duration and ease rather than the
     site defaults. --ease is a strong ease out, which is right
     for a control snapping into place and reads as a jolt on a
     two phase move. This is an ease in out, so each phase eases
     out of the last rather than starting from a standstill.

     --delay-burger is deliberately shorter than --dur-burger, so
     the turn begins while the bars are still crossing and the two
     phases overlap instead of running as separate steps. That
     overlap is what makes it read as one motion. */
  --dur-burger:              300ms;
  --delay-burger:            80ms;
  --ease-burger:             cubic-bezier(.65, 0, .35, 1);
  --scrim:                   rgb(28 28 30 / 34%);

  /* FX/THRESHOLD, the panel's own furniture. */
  --loader-bar-w:            140px;
  --loader-bar-h:            2px;
  --loader-bar-run:          45%;
  --loader-pop-y:            8px;
}

/* ─ the derived accent chain, the real one ───────────────────
   Relative colour syntax at a fixed hue. Guarded, because an
   unsupported relative colour does not fall back, it computes
   to transparent. See the note beside the floors above.

   The four deltas were calibrated against the template's
   origin accent #5C6FEE, where they landed within one step of
   the values the design shipped by hand: --accent-strong on
   #5263E8 against #5163E8, and --accent-soft on #C7D3FB
   against #C9D2FB. This build runs them on #1F3A5F, deriving
   #153157, #3F5D87, #6B89B4 and #CBD5E3.
   ────────────────────────────────────────────────────────── */

@supports (color: oklch(from #000 l c h)) {
  :root {

    /* Relative, because this one is the brand fill and has to stay
       recognisably the accent. It clears 4.5:1 on paper for any
       accent already at about 3.9:1 or better; a paler accent than
       that needs the delta widened. md/README.md carries the table. */
    --accent-strong: oklch(from var(--accent) calc(l - 0.033) calc(c * 1.05) h);

    /* Absolute, because these three are legibility roles rather than
       brand fills: each one exists to be readable on one particular
       field, and a field does not move when the accent does. Pinning
       lightness and letting only hue and chroma follow is what makes
       them hold for a navy and for a gold alike. A relative delta
       does not: measured across nine accents it left the credit at
       1.53:1 on ink for a navy and pushed the soft field to near
       white for a gold.

       The three numbers are the periwinkle's own derived lightness,
       so this build is pixel for pixel what it was. */
    --accent-deep:   oklch(from var(--accent) 0.475 calc(c * 1.05) h);
    --accent-lift:   oklch(from var(--accent) 0.625 c h);
    --accent-soft:   oklch(from var(--accent) 0.870 calc(c * 0.30) h);
  }
}
