/* ==========================================================
   ht-law / 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 image, icons. Built
                                from src/ masters. Regenerate
                                whenever --accent moves.
     4. media query conditions  in layout.css. CSS does not allow
                                a custom property inside a media
                                feature, and there is no build
                                step to substitute one.

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

/* ─ face ────────────────────────────────────────────────────
   Figtree, wght 300 to 900, 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's Figtree
   subsets do not carry U+2192, so the button arrows are inline SVG
   rather than a font glyph.
   ────────────────────────────────────────────────────────── */

:root {

  /* ─ colour ─────────────────────────────────────────────── */

  --ink:            #0A0A0A;
  --paper:          #FFFFFF;
  --surface:        #F7F7F7;
  --muted:          #4A4A4A;
  --accent:         #324157;
  --accent-ink:     #FFFFFF;
  --rule:           #E5E5E5;

  /* extension: three hairline greys and a hover accent.

     --accent-hover is derived, so changing --accent alone is
     enough. The source's hand-picked hover was a lighter, more
     saturated version of the same hue, which is exactly what
     lifting OKLCH lightness and chroma at a fixed hue produces.
     On the original navy this lands on #1253BA against the
     hand-picked #0854BB, a difference of ten in the red channel
     and nothing anywhere else.

     The declaration below is the floor, and the @supports block
     after this rule is the real one. Both are derived, so neither
     can go stale. The order matters and cannot be reversed: a
     custom property accepts any token sequence at parse time, so
     a browser that does not understand relative colour syntax
     still stores it happily and only fails when it is substituted
     into background, at which point the button 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 this for a mix toward white on its own. A tint
     desaturates, so the hover reads washed out rather than
     brighter. It is used here only as the floor. */
  --accent-hover:   color-mix(in oklch, var(--accent) 72%, white);
  --rule-strong:    #D4D4D4;
  --rule-bold:      #BFBFBF;
  --rule-field:     #949494;

  /* The secondary button's outline, so a button looks like a button
     before anyone hovers it. 3.03:1 on paper, where --rule-bold was
     1.84:1 and left the control almost invisible. Kept separate from
     the hairline greys because a control and a divider should not be
     forced to change together. */
  --btn-line:       #949494;

  /* extension: text on the accent field. Nine alpha steps in the
     source, consolidated to five. --accent-ink-dim replaces the
     0.50 and 0.45 steps, both of which failed AA at 12px.

     These are alphas, so the contrast they achieve depends on how
     dark --accent is. They are safe down to a relative luminance
     of about 0.06, which covers a deep navy, green, red or plum.
     A mid-tone accent needs them raised. md/README.md carries the
     table and the ceiling. */
  --accent-ink-strong: rgba(255, 255, 255, 0.80);
  --accent-ink-body:   rgba(255, 255, 255, 0.75);
  --accent-ink-muted:  rgba(255, 255, 255, 0.65);
  --accent-ink-soft:   rgba(255, 255, 255, 0.62);
  --accent-ink-dim:    rgba(255, 255, 255, 0.58);

  /* extension: surfaces and lines on the accent field */
  --accent-fill-hover: rgba(255, 255, 255, 0.88);
  --accent-fill-press: rgba(255, 255, 255, 0.75);
  --accent-line:       rgba(255, 255, 255, 0.70);
  --accent-line-press: rgba(255, 255, 255, 0.60);
  --accent-rule:       rgba(255, 255, 255, 0.15);
  --accent-rule-soft:  rgba(255, 255, 255, 0.12);
  --accent-btn-fill:   rgba(255, 255, 255, 0.10);

  /* The nav and drawer shadows are the accent at 35%, so they
     read as the header's own colour deepening rather than a grey
     smudge. Derived, so changing --accent alone is enough. On the
     original navy this resolves to exactly the rgba(2, 47, 119,
     0.35) it replaces. */
  --accent-shadow:     color-mix(in srgb, var(--accent) 35%, transparent);
  --ink-line-press:    rgba(0, 0, 0, 0.50);

  /* placeholder text sits at 0.76 of --muted. Below that it
     fails AA against the field. */
  --placeholder-alpha: 0.76;

  /* ─ type ───────────────────────────────────────────────── */

  --font-display: 'Figtree', system-ui, -apple-system, sans-serif;
  --font-body:    'Figtree', system-ui, -apple-system, sans-serif;
  --font-utility: 'Figtree', system-ui, -apple-system, sans-serif;

  /* The mark FX/THRESHOLD shows while it holds. Live text set in
     --font-display, not an image, so it follows the client's
     monogram and needs nothing regenerated when that changes.
     The quotes are part of the value: content: var(--mark). */
  --mark:      "CSG";
  --step-mark: clamp(40px, 6vw, 72px);

  /* The progress bar runs the full width of the bottom edge, which
     is where a browser puts one and so needs no explaining. On the
     accent field its track and fill are the same white steps the
     rest of the site uses on that field, so nothing new is invented
     and it follows --accent for free. */
  --bar-height: 2px;

  /* List marker. The square the offence lists carry in front of each
     item, in --accent. Small enough to read as punctuation rather than
     as a bullet, which is what keeps the lists looking like a document
     and not like a slide. */
  --marker-size: 5px;

  --weight-light:   300;
  --weight-regular: 400;
  --weight-medium:  500;

  /* contract steps */
  --step--2: 12px;                          /* extension: eyebrow, label */
  --step--1: 14px;
  --step-0:  17px;
  --step-1:  18px;
  --step-2:  22px;
  --step-3:  clamp(36px, 4.4vw, 64px);
  --step-4:  clamp(56px, 8.4vw, 120px);

  /* extension: intermediate and role sizes this composition uses */
  --step-utility: 15px;
  --step-icon:    16px;
  --step-nav:     24px;
  --step-question: clamp(20px, 2vw, 26px);
  --step-row:      clamp(28px, 3vw, 40px);
  --step-contact:  clamp(28px, 3.2vw, 48px);

  /* extension: phone-band display sizes */
  --step-display-phone: clamp(40px, 12vw, 72px);
  --step-contact-phone: clamp(40px, 10vw, 72px);

  --lh-tight:   0.95;
  --lh-body:    1.5;

  /* extension: this composition uses seven more */
  --lh-heading: 1.05;
  --lh-title:   1.15;
  --lh-block:   1.2;
  --lh-list:    1.4;
  --lh-prose:   1.55;
  --lh-address: 1.65;
  --lh-stack:   1.7;

  --track-display: -0.01em;

  /* extension */
  --track-heading: -0.005em;
  --track-body:     0;
  --track-tight:    0.01em;
  --track-meta:     0.04em;
  --track-label:    0.08em;
  --track-eyebrow:  0.16em;

  /* ─ 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. Every rung is used by this composition.
     md/README.md lists the ones used exactly once. */
  --sp-4:    calc(4px   * var(--density));
  --sp-6:    calc(6px   * var(--density));
  --sp-8:    calc(8px   * var(--density));
  --sp-9:    calc(9px   * var(--density));
  --sp-10:   calc(10px  * var(--density));
  --sp-12:   calc(12px  * var(--density));
  --sp-14:   calc(14px  * var(--density));
  --sp-16:   calc(16px  * var(--density));
  --sp-18:   calc(18px  * var(--density));
  --sp-20:   calc(20px  * var(--density));
  --sp-22:   calc(22px  * var(--density));
  --sp-24:   calc(24px  * var(--density));
  --sp-28:   calc(28px  * var(--density));
  --sp-32:   calc(32px  * var(--density));
  --sp-36:   calc(36px  * var(--density));
  --sp-40:   calc(40px  * var(--density));
  --sp-48:   calc(48px  * var(--density));
  --sp-56:   calc(56px  * var(--density));
  --sp-60:   calc(60px  * var(--density));
  --sp-64:   calc(64px  * var(--density));
  --sp-80:   calc(80px  * var(--density));
  --sp-96:   calc(96px  * var(--density));
  --sp-100:  calc(100px * var(--density));
  --sp-120:  calc(120px * var(--density));
  --sp-140:  calc(140px * var(--density));
  --sp-144:  calc(144px * var(--density));
  --sp-160:  calc(160px * var(--density));
  --sp-180:  calc(180px * var(--density));
  --sp-200:  calc(200px * var(--density));
  --sp-240:  calc(240px * var(--density));
  --sp-280:  calc(280px * 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-48);
  --space-8: var(--sp-64);

  /* Two values per responsive pair, both defined once here.
     The bands in layout.css choose which one to consume, so no
     :root is ever redeclared inside a media query. */
  --gutter:          var(--sp-96);
  --gutter-wide:     var(--sp-64);
  --gutter-phone:    var(--sp-24);
  --section-y:       var(--sp-200);
  --section-y-phone: var(--sp-120);

  --max-width:        1440px;
  --max-width-narrow: 1240px;

  --measure:         480px;
  --measure-sm:      380px;
  --measure-lead:    600px;
  --measure-wide:    760px;
  --measure-brand:   280px;

  /* ─ form ───────────────────────────────────────────────── */

  --radius:        0;
  --radius-sm:     4px;
  --border:        1px;

  /* Clears the fixed header, with air. Used by the sticky
     headings on the services and contact pages. */
  --sticky-top:  120px;

  --tap-min:     44px;
  --offscreen:   -9999px;   /* honeypot parking */

  /* Composition insets. Viewport-relative, so they belong here
     rather than on the ramp. */
  --cap-offset:        6vw;

  /* Transform distances, not spacing. Never density-scaled:
     a nudge is a nudge at any density. */
  --nudge:           5px;
  --hamburger-shift: 7px;

  --shadow-nav:    0 2px 24px var(--accent-shadow);
  --shadow-drawer: 0 8px 24px var(--accent-shadow);

  /* Select chevron. A mask, so the colour comes from --accent
     rather than being baked into the data URI as the source
     did with %23022F77. */
  --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='7' viewBox='0 0 12 7'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23000' stroke-width='1.5' fill='none' stroke-linecap='square'/%3E%3C/svg%3E");
  --chevron-w: 12px;
  --chevron-h: 7px;

  /* ─ motion ─────────────────────────────────────────────── */

  /* Was cubic-bezier(.2, .6, .2, 1), which put 60% of the movement
     into the first 20% of the time. That is a sharp start, and on a
     large area it lands as a snap followed by a long drift rather than
     as one movement. This curve eases in as well as out, so a change
     begins gently, and it is what makes the difference between motion
     that is merely slow and motion that reads as smooth. */
  --ease:      cubic-bezier(.4, 0, .2, 1);

  /* Slowed across the board so the site reads as unhurried rather
     than snappy. --dur-fast governs hover feedback and is the one to
     keep short: past about 350ms a button stops feeling connected to
     the cursor. */
  --dur-fast:  240ms;
  --dur-base:  400ms;
  --dur-slow:  560ms;
  --dur-link:  280ms;   /* extension: nav and footer link colour */

  /* Extension. FX/WIPE on the home page practice rows: the accent
     sweeps in from the left rather than fading up everywhere at once.

     The sweep uses its own curve. --ease is an ease-in-out, which is
     right for something changing in place but wrong for something
     travelling: it makes the leading edge crawl away from the left
     margin. This one leaves at speed and settles at the right, so the
     row reads as being filled rather than as growing. */
  --dur-sweep:  400ms;
  --ease-sweep: cubic-bezier(.2, .5, .2, 1);

  /* The text inverts on its own, much faster clock. Each column waits
     for the sweep's leading edge to reach it, so nothing is ever white
     on white, and then flips quickly rather than cross-fading through
     grey.

     The delays are measured, not guessed. The three columns begin at
     1.8%, 14.1% and 57.9% of the row's width, and at --dur-sweep the
     leading edge reaches those points at 0ms, 17ms and 84ms. Retime
     the sweep and these have to be measured again. */
  --dur-invert:     50ms;
  --delay-invert-1:  0ms;   /* number, the edge covers it immediately */
  --delay-invert-2: 25ms;   /* title, edge crosses it 17ms to 67ms */
  --delay-invert-3: 110ms;  /* blurb, edge crosses it 84ms to 167ms */

  /* FX/RISE. Every stagger in this composition is a whole
     multiple of --stagger, so effects.css carries multipliers
     rather than durations. */
  --rise-y:  20px;
  --stagger: 26ms;

  /* FX/THRESHOLD, the first-load preloader. --dur-preload-in is
     the mark's entrance and runs inside the hold, so the whole
     effect is hold plus out, 940ms.

     --dur-preload-hold and --dur-preload-out are the same two
     numbers as HOLD and OUT at the top of js/modules/preloader.js.
     They are the one place in this template where a CSS value and a
     JS value have to agree. Change them together. */
  --ease-preload:      cubic-bezier(.16, 1, .3, 1);

  /* The bar's own curve, and it has to be its own. The site's --ease
     is front-loaded, which suits a panel arriving but is wrong for
     progress: it put the bar at 44% after 70ms and 91% by 250ms, so
     it read as already finished and then stuck. This one tracks close
     to linear early, 14% at 70ms and 50% at 250ms, then eases into
     completion so it does not stop dead. */
  --ease-bar:          cubic-bezier(.3, .2, .5, 1);
  --dur-preload-in:    300ms;   /* mark rises and fades in */
  --dur-preload-hold:  500ms;   /* panel is up for this long in total */
  --dur-preload-out:   440ms;   /* panel lifts away */

  /* FX/FACET, the home hero's field. Symmetric, and it has to
     be. --ease above is front-loaded, which is right for
     something arriving and wrong for something breathing: it
     would snap to each end of the drift and hang there, which
     reads as a stutter rather than a float. This one eases
     equally out of and into both turns, so a pane is never
     quite still and never quite fast.

     The eight drift periods live in effects.css rather than
     here. They are seconds-scale, this contract stops at
     --dur-slow, and they are per pane rather than a shared
     value, so a token each would be eight names carrying one
     idea. Same reasoning as the 3s rise safety reveal. */
  --ease-drift: cubic-bezier(.45, 0, .55, 1);

  /* No page transition on this site. --ease-page and the three
     --dur-page-* tokens are deliberately absent, and now cannot
     be added: FX/FACET holds the Signature slot and kit rule 8
     forbids a transition alongside another Signature effect.
     FX/THRESHOLD is unaffected, being Considered rather than
     Signature, and runs once per session rather than on every
     navigation. */
}

/* ─ accent hover, the real one ───────────────────────────────
   Lifts lightness and chroma at a fixed hue, which is what makes
   the hover read as the same colour turned up rather than a tint.
   Guarded, because an unsupported relative colour does not fall
   back, it computes to transparent. See the note on --accent-hover.
   ────────────────────────────────────────────────────────── */

@supports (color: oklch(from #000 l c h)) {
  :root {
    --accent-hover: oklch(from var(--accent) calc(l + 0.14) calc(c * 1.33) h);
  }
}
