/* ============================================================
   BAC · tokens.css — THE runtime token mirror
   ------------------------------------------------------------
   THE single :root in this repo. Every page links this file;
   no page re-declares a token. If you need a value that is not
   here, add it here — never inline it (tools/check.py enforces).

   Source of truth for VALUES: ../../bac-shared/tokens.json
   (strategy writes · this repo mirrors · Claude Design reads).
   On any change: update tokens.json + this file + log one line
   to ../../bac-shared/CHANGELOG.md. Hand-kept in lockstep, NOT
   a build pipeline — a build step would break hand-over.

   Rules + rationale: ../docs/design-rules.md
   Status: PROPOSED until sign-off.
   ============================================================ */

:root {

  /* -- INK & PAPER — the re-weighted palette (brain/06, 31 Jul) -- */
  /* Chrome is colourless: ink, paper and alpha steps of ink. Sky is
   QUARANTINED to the story layer — never chrome. The logo is the one
   exemption (design-rules § 1, § 10). */
  --ink  : #042839;
  /* GROUND B — adopted 5 Aug 2026. Ink is a SURFACE colour, not a base: at
     OKLCH L 0.26 with real chroma, a whole plane of it reads as a blue
     website rather than as authority. This sits below it.
     Not a hand-picked near-black — a derived one, and it lands exactly:
         Navy at  l - 0.084 · c * 0.57 · h + 1  →  #04131C
     The chroma REDUCTION is the point; it is what makes this read cooler
     and more neutral than a straight darkening, and the neutrality is the
     authority. Note this is a different move from surface.css's ramp,
     which HOLDS chroma on the way down (--dark is c*1.05) to keep a colour
     recognisably itself. A ground has to recede; a surface has to stay.
     site/_dark.html calls this --d-ground and derives it in full. When the
     dark-default work lands, that page should converge on this token
     rather than keep its own name. */
  --ink-deep: #04131C;
  --paper: #F9F8F5;
  --white: #FFFFFF;
  --mist : #E0ECE8;
  /* The sixth ground, named 16 Aug 2026. Was a bare page-local --surface
     on index-v5 with no name and no row in the source — open-items #11.
     Chrome and plate ground: bars, controls, the surface under lifted content. */
  --warm : #EDE9E5;
  --slate: #364349;
  /* DARKENED 7 Aug 2026, from #7C868C. Not a taste change — a measurement.
     The old value is 3.50:1 on paper, below WCAG AA for body text, and it is
     set as a TEXT colour in 34 places across 7 stylesheets: eyebrows, labels,
     captions, meta, chips. A full-page sweep of the landing page found 28
     elements failing at once; after this change, 0. The footer caught the same
     thing independently weeks ago and swapped itself to --slate locally;
     nobody generalised it.

     The fix is the VALUE, not the usage. Moving those 34 call sites to
     --slate (9.62:1) would have fixed the contrast by deleting the hierarchy
     --muted exists to create — everything recessive would stop receding.

     Derived by darkening along the colour's own hue, so it is the same grey
     with more of it:
        #7C868C · 3.50:1     the old value
        #6B7479 · 4.50:1     exactly on the AA threshold — rejected
        #646D71 · 4.98:1     ADOPTED
     4.50 was rejected for the reason --rev-fg-3 is a floor: sitting on the
     line leaves nothing to tune with. 4.98 keeps real margin and still sits
     about a half-step lighter than --slate, so the step survives. */
  --muted: #646D71;
  --sky  : #86E1FA;

  /* -- THE LINE LANGUAGE — lines are first-class (§ 3) --
   A LADDER, not a single ink. Until 5 Aug 2026 there was exactly one
   structural line value and it was --ink at full strength, so every
   frame, every cell divider and every box on the page was drawn at
   maximum. Two consequences, both real: the chrome competed with the
   type, and hover had nowhere to go — .usecase-box:hover set the
   border to the colour it already had, a no-op that read as a broken
   interaction. Four rungs now, and the hover step is spent climbing:

     --rule-hair   divisions INSIDE one object (cell edges, row rules)
     --rule        the object's own frame — the resting state
     --rule-hover  hover · focus · active
     --rule-ink    #042839 — emphasis, the mark, the one-ink fallback

   Every rung is the SAME ink at an alpha, never a grey tint: a rule
   over paper, over the grain and over white then stays one family,
   where a flat grey would go cold on paper and wrong on white.

   HOVER GOES ALL THE WAY TO INK. That is the house gesture, not a
   darkening — the line RESOLVES under the cursor, the same move the
   page already makes with rented→owned. It also means the hover and
   focus states clear WCAG 2.2 non-text contrast on their own, which
   the resting rule (~1.3:1) deliberately does not: at rest a frame is
   a decorative boundary and the heading identifies the box.

   The floor for --rule-hair is 10%. Below that a column divider stops
   separating (tested at 7% on the four-unlock row — it vanished).
   → proof: site/_proof-lines.html · options A–E, 5 Aug 2026 */
  --rule-hair    : rgba(4,40,57,.10);
  /* A CONTROL'S RESTING EDGE — component tier, not a fifth rung on the ladder.
     An object's frame states a boundary; a control has to read as enterable. */
  --rule-25      : rgba(4,40,57,.25);
  --rule-d25     : rgba(249,248,245,.25);
  --rule         : rgba(4,40,57,.16);
  --rule-hover   : #042839;
  --rule-ink     : #042839;

  /* the reversed ladder — re-cut in the same pass, because the two
     sides have to MATCH IN PERCEIVED WEIGHT, not in number. Paper on
     ink reads heavier than ink on paper at equal alpha, so the
     reversed rungs sit lower than a naive mirror would put them. */
  --rule-hair-rev: rgba(249,248,245,.14);
  --rule-rev     : rgba(249,248,245,.22);
  --rule-hover-rev: var(--paper);

  --glyph    : #042839;
  --glyph-rev: rgba(249,248,245,.55);
  --dot      : rgba(4,40,57,.22);

  /* -- THE HALFTONE SYSTEM — the mark says which thing it is, the
     ink says how near. Grounds and the hatches inside a drawing are
     one vocabulary at two scales. Aliased onto the line inks on
     purpose: a ground is the same ink as a rule, laid down thinner.
     Only the PAGE pitch is tokenised. The fine pitch and the 27deg
     angle live in the SVG pattern defs as literals — pattern
     geometry takes attributes, not custom properties, and a token
     nothing can read is a lie. → components/grounds.css */
  --hatch-page  : 24px;
  --hatch-2     : rgba(4,40,57,.15);   /* structure · the grid */
  --hatch-3     : var(--dot);          /* near      · the dots */
  --hatch-2-rev : rgba(249,248,245,.15);
  --hatch-3-rev : var(--rule-rev);

  /* -- TYPE — one family, one weight, a tracking curve (§ 2) --
   Hierarchy comes from size, tracking and case only — never weight.

   RETUNED 5 Aug 2026 to the measured Zapier conventions, proven page-wide
   on the landing page before adoption (References/zapier-reference-audit.md
   §6). Two things changed, and they are the whole point:

   1 · LINE-HEIGHT CARRIES THE SCALE, and the spread is far wider than it
       was. Display goes SUB-ONE — 0.90 at the top, 1.00 at display — while
       reading copy opens to 1.71. It used to run 1.14 at the top and 1.50
       at the bottom: the same idea, at a fraction of the range.
   2 · TRACKING IS ~ZERO ON DISPLAY. Negative tracking now appears only in
       the middle of the ramp, where it does optical work. This REVERSES
       the Planhat tracking curve adopted in July — the two references
       disagree, and this is the bet we are taking.

   The ramp is DENSE SMALL, SPARSE LARGE: 12·13·14·16·20·24 all in active
   use, then 24 → 40 → 48 → 80. Fine distinctions where the reading
   happens, wide gaps where the shouting happens. The workhorse is 14/1.71,
   not 16 — 16 is a lead-in size now.

   ⚠ WEIGHT IS NOT PART OF THIS, and cannot be. Zapier runs 400/500/600/700
   and that is half of why their scale holds; we are single-weight and
   licence-bound to one cut, so these sizes arrive WITHOUT the mechanism
   that makes them work there. Compensate with size, space and case — never
   by reaching for a weight. -- */
  /* THE DISPLAY FACE IS THE SERIF. Changed 16 Aug 2026 to follow index-v5:
     headlines, statements and pull quotes are Tiempos; --f-text stays grotesk.
     Two faces, one weight each — hierarchy never comes from weight. */
  --f-disp      : "Tiempos Text", Georgia, "Times New Roman", serif;
  --f-text      : "NHG Text", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --s-display-xl: 80px;
  --t-display-xl: 0em;
  --l-display-xl: 0.90;
  --s-display   : 48px;
  --t-display   : 0em;
  --l-display   : 1.00;
  --s-headline  : 40px;
  --t-headline  : -0.010em;
  --l-headline  : 1.08;
  --s-title     : 24px;
  --t-title     : -0.020em;
  --l-title     : 1.20;
  --s-sub       : 20px;
  --t-sub       : -0.025em;
  --l-sub       : 1.25;
  --s-quote     : 20px;
  --t-quote     : -0.020em;
  --l-quote     : 1.35;
  --s-body-l    : 16px;
  --t-body-l    : 0em;
  --l-body-l    : 1.50;
  --s-body      : 14px;
  --t-body      : 0em;
  --l-body      : 1.71;
  --s-body-s    : 13px;
  --t-body-s    : 0em;
  --l-body-s    : 1.54;
  --s-label     : 13px;
  --t-label     : 0.077em;
  --l-label     : 1.50;
  --s-micro     : 12px;
  --t-micro     : 0em;
  --l-micro     : 1.50;

  /* -- SCHEMATIC TYPE — every figure is set at ONE optical size --
   A drawing is not a picture, it is a passage of the page set in
   lines instead of words, so its type obeys the same discipline as
   body copy: two sizes, no more, identical in every figure. This is
   only true if the SVG renders at scale 1 — see --fig-* below.
   Tracked in px, not em: inside an SVG the em box is the drawing's
   coordinate space, and a ratio there means nothing to the eye. */
  --s-schem     : 11px;
  --t-schem     : 1.3px;   /* node names · uppercase */
  --s-schem-s   : 10px;
  --t-schem-s   : 1.1px;   /* meta, captions, callouts */

  /* -- FIGURE WIDTHS — the three frames a figure can land in --
   An SVG scales its whole coordinate space, TEXT INCLUDED. So a
   figure drawn 520 wide and stretched into a 1140px frame renders
   its 10px labels at 22px, while the same 10px in a half-width
   frame renders at 8px — the defect that made the figures read as
   five unrelated drawings. Fix: draw each figure at the width of
   the frame it actually lands in, and cap it there. Scale is then
   1.0 and the label sizes above are literal. Below the design
   width everything shrinks together, which is correct. */
  --fig-full    : 1140px;  /* full-spine frame                */
  --fig-half    :  500px;  /* half of the 1fr 1fr splits      */
  --fig-media   :  528px;  /* the 1.1fr media column          */

  /* -- SPACING — one ladder, 8-based (§ 8) --
   Every gap between blocks is a rung on this ladder. A margin that
   is not one of these is a bug: unequal gaps read as drift, not as
   intent, and drift is most of what makes a page look unfinished.
   The 4px rung is control chrome only — never between blocks. */
  --sp-4        :   4px;
  --sp-8        :   8px;
  --sp-12       :  12px;
  --sp-16       :  16px;
  --sp-24       :  24px;
  --sp-32       :  32px;
  --sp-48       :  48px;
  --sp-64       :  64px;
  --sp-96       :  96px;
  --sp-128      : 128px;
  --sp-160      : 160px;

  /* semantic step: space below a heading, everywhere */
  --gap-head    : var(--sp-32);

  /* -- LAYOUT — one spine, seven splits (§ 7) -- */
  /* THE SPINE IS CAPPED, not fluid. It ran to 2200px, which on a wide screen
     stretched every measure and left the page feeling loose. Zapier — the
     closest thing we have to a convention here — uses FIXED content columns
     of 1240/1280 (References/zapier-reference-audit.md §1) and lets the
     margin grow instead. Same here: 1440 outer minus an 80px gutter caps the
     content at 1280 and holds it there however wide the screen gets. Below
     that the clamp keeps the old behaviour, so nothing changes on a laptop. */
  /* -- BREAKPOINTS — introduced 7 Aug 2026 --
     Until now the system had exactly ONE breakpoint, 900px, typed as a
     literal into every sheet that needed to collapse. Three named rungs now.

     ⚠ THESE CANNOT BE READ BY @media. CSS custom properties are not
     available inside a media query — `@media (max-width: var(--bp-md))`
     simply does not work, in any browser. The numbers still have to be typed
     into each @media rule by hand.

     Tokenising them anyway is deliberate and it is not decoration: it gives
     the three values one name, one place and one description, it is what the
     spec sheet reads to document them, and it gives tools/check.py something
     to check a media query against. A literal that appears in six files with
     no name is how 900 and 720 both ended up in this system meaning
     different things. */
  --bp-sm    :  640px;   /* one column throughout                */
  --bp-md    :  900px;   /* grids collapse — the existing rung    */
  --bp-lg    : 1280px;   /* the content cap; the spine stops here */

  --page-max : 1440px;
  --page-pad : clamp(24px, 5vw, 80px);
  --gutter      : var(--sp-64);
  --gutter-tight: var(--sp-48);

  /* -- CONTROLS & SURFACES (§ 6) -- */
  /* One radius across every control and card. Blur exists twice, both
   restrained — the darkening carries the effect, not the blur. */
  --r-card      : 4px;
  --r-chip      : 4px;
  --r-btn       : 4px;
  /* One exception to the single radius, and it is deliberate: SHOWCASE
     PANELS only — the product artefact and the story plate, the two places
     the page shows a rendered thing rather than a control. Controls and
     cards stay at 4px. Called by the operator, 4 Aug 2026. */
  --r-soft      : 6px;
  --shadow-float: 0 1px 2px rgba(4,40,57,.05), 0 12px 32px rgba(4,40,57,.10);
  --blur-overlay: 18px;
  --blur-menu   : 28px;
  --scrim       : rgba(4,40,57,.42);

  /* -- THE WARMTH LAYER (§ 5) — recipe lives in components/surface.css -- */
  --grain      : url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.1' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='1.55' intercept='-0.28'/%3E%3CfeFuncG type='linear' slope='1.55' intercept='-0.28'/%3E%3CfeFuncB type='linear' slope='1.55' intercept='-0.28'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  --grain-alpha: 0.30;

  /* -- MOTION — four verbs: draw · type · commit · arrive (§ 9) -- */
  --t-draw     : 600ms;
  --t-char     : 16ms;
  --t-char-fade: 150ms;
  --t-fade     : 240ms;
  --t-step     : 90ms;

  /* CONTROL feedback, not narrative. Lifted from index-v5 16 Aug 2026 — 32 sites,
     and the only line of that page's motion block with no system counterpart.
     ⚠ 10ms from --t-char-fade by ROLE, not by accident. */
  --t-ui        : 160ms;
  --draw       : 900ms;
  --ease-draw  : cubic-bezier(.25,.1,0,1);
  /* entering, distinct from drawing: the hard out-ease behind the commit
     verb's rise — fully legible at half-time, settling the rest. Lifted
     from the measured Zapier reveal (zapier-motion-audit.md §2.1). */
  --ease-enter : cubic-bezier(.22,1,.36,1);

  /* The load-in's one multiplier — every duration and delay in
     motion/loadin.css is stated as a factor of this, so the whole
     ~1.9s sequence can be slowed for review without re-timing it.
     Real playback is 1; the proof page sets 3. Graduated out of a
     second :root in loadin.css, 7 Aug 2026. */
  --ld-k       : 1;

  /* -- THE PRELOAD (motion/preload.css) -----------------------------
     Same discipline as --ld-k above, and declared here for the same
     reason: a module that declares its own :root gives a token two
     homes, which is the C1 failure. preload.css reads every one of
     these with a fallback, so it still works on a page that does not
     link this spine — index-v4 is exactly that page.

     THE WIPE IS A HINGE, not a slide. --pl-px/--pl-py place the pivot
     OUTSIDE the bottom-left corner of the frame; the panel rotates
     about it, so its left edge angles in from that point and lies down
     as it swings. --pl-side is the panel's reach and must exceed the
     distance from the pivot to the far corner or the sweep runs out of
     panel mid-wipe. In vmax, never vw/vh: sized in vw it fails on tall
     viewports, in vh on wide ones. Cut in site/_preload-lab.html. */
  --pl-k       : 1;
  --pl-px      : -6vw;
  --pl-py      : 106vh;
  --pl-side    : 260vmax;
  --pl-swing   : 96deg;
  --pl-wipe    : 1450ms;      /* the sheet leaving */
  --pl-roll    : 500ms;       /* one square rolling into its slot */
  --pl-sq      : 15px;
  --pl-gap     : 3px;

  /* -- THE GROUND-RELATIVE LADDER — the light cut (§ below) --
     The default. Its reversed twin is the .dark-region block at the
     foot of this file; the long rationale, the naming hazard and the
     measured contrast ramp are written there. Declared HERE, inside
     the one :root, rather than in a second root block — check.py C1
     wants exactly one, and it is right to. */
  --rev-ground: var(--paper);
  --rev-tint  : var(--ink);
  --rev-hair  : var(--rule-hair);
  --rev-rule  : var(--rule);
  --rev-edge  : var(--rule-hair);
  --rev-hover : var(--rule-hover);
  --rev-fg    : var(--ink);
  --rev-fg-2  : var(--slate);
  --rev-fg-3  : var(--muted);
}

/* ============================================================
   ONE INK ON PAPER — the print / security-review fallback
   ------------------------------------------------------------
   The ladder is alpha, and alpha is exactly what flattens badly
   through a print driver or a PDF export: a 16% rule can come out
   as nothing at all. The rubric's survives-security-review clause
   is about the ARTEFACT someone prints and walks into a meeting
   with, so on paper the whole ladder collapses back to one ink and
   the printed page is identical to what it was before the ladder
   existed. Screen gets the hierarchy; print gets the guarantee.

   This is the one media query in the token file, and it belongs
   here rather than in a component: it re-points the tokens, it
   does not restyle anything. --rule-hair stays a step down so a
   printed table still reads as rows, not as a solid grid.
   ============================================================ */
@media print {
  :root {
    --rule-hair     : rgba(4,40,57,.45);
    --rule          : var(--rule-ink);
    --rule-hover    : var(--rule-ink);
    --rule-hair-rev : rgba(249,248,245,.45);
    --rule-rev      : var(--paper);
  }
}

/* ============================================================
   THE GROUND-RELATIVE LADDER — the semantic tier
   ------------------------------------------------------------
   Graduated 7 Aug 2026 out of site/index-next.html's page-local
   :root, where it was built as --d-* ("dark") for the dark-default
   proof site/_dark.html. That proof is ARCHIVED — #04131C is not
   being adopted as a page ground — but the ladder paid out, so it
   comes up here under a name that says what it actually does.

   --rev-* is the rung that REVERSES WITH THE GROUND. Not "the dark
   value": the value for whichever ground the region is on. On paper
   --rev-fg is ink; inside a .dark-region it is paper. One drawing,
   one set of names, either ground, no second copy — which is the
   whole reason a semantic tier exists. A token named by ROLE can be
   re-pointed for a context; a token named by colour cannot.

   ⚠ NAMING HAZARD, AND HOW IT IS NEUTRALISED. --rev-rule sits one
   transposition away from --rule-rev above, and the two are
   different tiers: --rule-rev is an ABSOLUTE reversed value that
   on-ink components already read directly; --rev-rule is the
   GROUND-RELATIVE role. Rather than rename around the collision,
   the .dark-region block below re-points the absolute names ONTO
   the ladder, so inside a dark region the two provably resolve to
   the same value and the transposition cannot mean two different
   things on screen.

   That re-point is also the cheap half of this change: every
   existing on-ink component — .split-triptych dividers, .chip.on-ink
   borders, .story-say cells, .dark-region frames, the schematic
   strokes — picks the ladder up without being chased selector by
   selector.

   --rev-tint is the colour mixed INTO the ground to make a face:
   ink on paper, paper on ink. Without it a color-mix on a light
   section mixes paper into paper and the faces come out flat. It is
   what lets one isometric drawing sit on either ground.

   THE REVERSED RUNGS SIT LOWER THAN A NAIVE MIRROR — .08 / .15
   against the light .10 / .16 — because a light-on-dark line
   accumulates weight where a dark-on-light one thins.

   --rev-edge is the FULL-BLEED SECTION BREAK, and it is set lower
   than an object's own frame (.10 vs .15). That looks wrong written
   down and is right on screen: a line accumulates weight over its
   length, so length does what alpha does. It is the rung the light
   page does not need — on paper, sections separate by alternating
   ground; inside a dark region there is no alternation left, so the
   hairline carries the break alone.

   TEXT IS A THREE-STEP RAMP, NOT ONE ALPHA. --glyph-rev is a single
   value doing every non-primary job, which is fine for a caption and
   not fine for a whole region's supporting copy.

   MEASURED ON --ink #042839, 7 Aug 2026 — not ported. The original
   cut was tuned against #04131C, and site/_dark.html §3 asserted
   that paper at 50% over #042839 came out "under 4". Re-measured,
   that is WRONG: it is 4.63:1. The ramp survives the archive of the
   deeper ground intact, all three rungs clearing AA for body text.

     --rev-fg    paper ......... 14.46:1
     --rev-fg-2  paper at 72% ....8.09:1   leads, card body
     --rev-fg-3  paper at 50% ....4.63:1   eyebrows, captions, meta

   ⚠ 50% IS A FLOOR, not a preference. 4.63 clears 4.5 by 3%. Any
   step down in alpha, or any lightening of the band ground, breaks
   AA for body copy — so --rev-fg-3 is the one rung that cannot be
   tuned by eye. On the archived #04131C it had 5.05:1 and room to
   move; on --ink it does not.

   Note what is NOT here: --white. Pure #FFF on a near-black haloes
   badly for astigmatic readers, and paper's off-white is already the
   fix. Nothing in body copy reaches past --rev-fg.

   Also not here: the elevation pair (--d-raise-1 / -2). They were
   declared for the dark-default page and nothing ever used them —
   the story block took a flat fill at the ground colour instead,
   because neither the quote nor the wordmark is an artefact being
   presented and neither earns elevation. Archived with the ground.

   THE LIGHT CUT IS DECLARED IN THE MAIN :root ABOVE, not here. It
   wanted to sit beside this comment and it cannot — check.py C1
   allows exactly one top-level :root in this file, which is the rule
   that stops a second hand-kept copy appearing. Only the reversed
   cut lives down here, because a class is not a root.
   ============================================================ */

/* The reversed cut. A CLASS in the token file is a departure worth
   naming: this sheet is otherwise :root and one @media. It belongs
   here because it re-points tokens and restyles nothing — the same
   argument the print block above makes — and because splitting one
   ladder across two files is precisely the hand-kept-copy drift
   tools/check.py C1 exists to catch. */
.dark-region {
  --rev-ground: var(--ink);
  --rev-tint  : var(--paper);
  --rev-hair  : rgba(249,248,245,.08);
  --rev-rule  : rgba(249,248,245,.15);
  --rev-edge  : rgba(249,248,245,.10);
  --rev-hover : rgba(249,248,245,.55);
  --rev-fg    : var(--paper);
  --rev-fg-2  : rgba(249,248,245,.72);
  --rev-fg-3  : rgba(249,248,245,.50);

  /* the absolute names, re-pointed onto the ladder — see the hazard
     note above. One place, so the whole on-ink vocabulary moves with
     the ladder instead of being chased. */
  --rule-hair-rev : var(--rev-hair);
  --rule-rev      : var(--rev-rule);
  --rule-hover-rev: var(--rev-hover);
}
