/* ═════════════════════════════════════════════════════════════════════════════
   MARK - the Openprovider design system
   0 · TOKENS

   A domain is a mark: the thing that makes a business findable, and the thing a
   reseller is trusted to hold on someone else's behalf. That is what this system
   is for - not a generic admin panel, but a place where thousands of names and
   real money are managed by people whose customers go dark if it goes wrong.

   Built from scratch. There is no external library to reconcile against, by
   decision of the product owner on 7 Aug 2026 - aligning to an existing
   component set was constraining what the screens could be.

   ─── THE FIVE RULES THIS FILE ENFORCES ────────────────────────────────────
   1. Money is the content. Everything else is chrome.
   2. Colour always means something. There is no decorative colour in this file.
   3. Consequences come before actions, with their date.
   4. Density without noise - air comes from rhythm, not from emptiness.
   5. One decision per screen, said in words before it is said in numbers.

   ─── HOW TO READ IT ───────────────────────────────────────────────────────
   Two layers, and only the second is ever used in a component:
     · the PALETTE  (--c-*) - raw values, measured, never referenced directly
     · the ROLES    (everything else) - what a thing means
   Re-theming is an edit to the role block and nothing else.

   Every contrast ratio in a comment here was computed, not estimated.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* ─────────────────────────── PALETTE ─────────────────────────────────────
     Ratios are against white unless stated. AA needs 4.5:1 for body text and
     3:1 for large text (≥24px, or ≥19px bold). */

  /* SLATE - the neutral. Cool, because the panel is a working instrument and a
     warm grey at 90% lightness reads as beige, which is the defect that made
     the previous system look dull. Hue 222°, saturation 14%. */
  --c-slate-50:  #F6F7F8;   /*  1.07:1  the page ground                      */
  --c-slate-100: #EBECF0;   /*  1.18:1  input wells, quiet fills             */
  --c-slate-200: #D6D9E0;   /*  1.41:1  hairlines, dividers                  */
  --c-slate-300: #B3B9C6;   /*  1.97:1  control borders, disabled marks      */
  --c-slate-400: #8B93A7;   /*  3.07:1  PLACEHOLDER ONLY - never body text   */
  --c-slate-500: #606A80;   /*  5.43:1  captions, column heads               */
                            /*  Chosen against the DARKEST surface it can sit */
                            /*  on (--well, 1.18:1) rather than against white, */
                            /*  where the old #69748C measured 4.69 and then   */
                            /*  fell to 4.37 on the page ground - AA fails at  */
                            /*  the place the token is most used.              */
  --c-slate-600: #565E71;   /*  6.49:1  secondary copy                       */
  --c-slate-700: #444B5A;   /*  8.75:1  body copy                            */
  --c-slate-800: #303540;   /* 12.29:1  headings                             */
  --c-slate-900: #1F2229;   /* 15.92:1  the darkest text                     */
  --c-slate-950: #14161A;   /* 18.11:1  ink, and the dark surface            */

  /* CRIMSON - Openprovider's own. It is the ACTION and it is MONEY AT RISK,
     which are the same colour on purpose: both mean "this costs you something".
     It is never a brand flourish. */
  --c-crimson-50:  #FFF1F3;
  --c-crimson-100: #FFE0E5;
  --c-crimson-200: #FFC2CC;
  --c-crimson-300: #FF8C9F;
  --c-crimson-400: #F4415F;   /*  3.64:1  large text only                    */
  --c-crimson-500: #D90D32;   /*  5.18:1  the brand. White text on it: 5.18   */
  --c-crimson-600: #B70A2A;   /*  6.80:1  hover                              */
  --c-crimson-700: #920820;   /*  9.22:1  pressed                            */
  --c-crimson-800: #6B0618;   /* 12.62:1                                     */
  --c-crimson-900: #4A0410;   /* 15.96:1  text on a crimson-50 wash          */

  /* TEAL - calm, state, "you are here". The second hue, and the one that says
     a thing is true rather than urgent. */
  --c-teal-50:  #EEF9FA;
  --c-teal-100: #D5F0F3;
  --c-teal-200: #A8E1E8;
  --c-teal-300: #6BC8D5;
  --c-teal-400: #12ACEE;   /*  2.87:1  FILL ONLY                              */
  --c-teal-500: #0091CE;   /*  3.63:1  large text and fills only              */
  --c-teal-600: #0078AB;   /*  4.91:1  AA text - full chroma at hue 198        */
  --c-teal-700: #0A5464;   /*  8.52:1                                         */
  --c-teal-800: #073C48;   /* 12.01:1                                         */
  --c-teal-900: #052932;   /* 15.33:1  text on a teal-50 wash                 */

  /* GREEN - good news, and money coming back. The rarest colour in the system:
     if it is everywhere it means nothing.

     RETUNED to full chroma. The first pass sat at saturation .79 / value .44,
     which is a correct forest green and reads as mud beside anything. Every
     state hue is now the MOST chromatic sRGB colour that still clears 4.5:1
     against the page ground (the harder test - captions live there, not on
     white), searched inside a hue band narrow enough that no two states can
     be confused: green 118-142, amber 36-46, teal 186-198. An amber allowed
     to drift to hue 20 is an orange, and an orange next to crimson-349 is
     two alarms that read as one. */
  --c-green-50:  #EFFAF0;
  --c-green-100: #D6F3D9;
  --c-green-200: #ADE5B4;
  --c-green-300: #74D081;
  --c-green-400: #17BC0E;   /*  2.51:1  FILL ONLY, never text                  */
  --c-green-500: #0C9C05;   /*  4.06:1  large text and fills only              */
  --c-green-600: #048200;   /*  5.00:1  AA text - full chroma at hue 118        */
  --c-green-700: #12571F;   /*  8.72:1                                        */
  --c-green-800: #0D3E17;
  --c-green-900: #082A0F;   /* 15.57:1  text on a green-50 wash               */

  /* AMBER - soon, and owed. Between calm and at-risk. */
  --c-amber-50:  #FFF8E9;
  --c-amber-100: #FFEDC7;
  --c-amber-200: #FBD98C;
  --c-amber-300: #F0BC45;
  --c-amber-400: #F09000;   /*  2.35:1  FILL ONLY                              */
  --c-amber-500: #C87700;   /*  3.36:1  large text and fills only              */
  --c-amber-600: #A16000;   /*  5.01:1  AA text - full chroma at hue 36         */
  --c-amber-700: #6F4D00;   /*  7.67:1                                        */
  --c-amber-900: #362500;   /* 14.77:1  text on an amber-50 wash              */

  /* SAND - the fifth tone, finally a primitive. It was two hardcoded hex pairs
     (#EFE7DC / #6B5540) on `.avatar` and `.rail__tile` `data-tone="5"`, with no
     ramp behind them - the "fifth palette group with no tone left to take" the
     catalog names, byte-duplicated across two files and free to drift. A warm
     neutral, hue ~33: a LIGHT, not a fill, so it never competes with the state
     hues. Anchored so tone 5 is unchanged - `-100` is the old background,
     `-700` the old text (6.1:1 on `-100`, the ratio it always had). */
  --c-sand-50:  #F8F4EE;
  --c-sand-100: #EFE7DC;
  --c-sand-200: #E3D6C6;
  --c-sand-300: #D0BCA4;
  --c-sand-400: #B89D7E;
  --c-sand-500: #9C8060;
  --c-sand-600: #816850;
  --c-sand-700: #6B5540;
  --c-sand-800: #55442F;
  --c-sand-900: #3E301F;

  --c-white: #FFFFFF;

  /* CREAM - the single warm step the light theme's --surface is built from
     (see --surface below). Named as its own scale step, not folded into
     --surface's role comment, because a fixed-light chip (.trust-strip__chip)
     needs this exact value even inside a dark context - see --surface-fixed. */
  --c-cream-50: #FFF9EF;

  /* ─────────────────────────── ROLES ───────────────────────────────────────
     What a thing MEANS. Components reference only these. */

  /* Surface - four layers, and the order is the z-order. A card may not contain
     a card: nesting is how a panel turns into a stack of boxes. */
  /* THE PAGE GROUND IS NOT A COLOUR, IT IS LIGHT - AND IT IS WARM.

     The previous system was called out for a "yellowish, dull" ground, and the
     measurement explains why: #F7F5F2 is hue 36 at 2% saturation. That is not
     warmth, it is grey with a hint of mud. Warmth at 2% chroma always reads as
     dirt, because the eye has nothing to identify as colour and reads the
     desaturation as staining.

     So this goes the other way. The peach stop is hue 33 at 13% saturation -
     six and a half times the chroma of the old ground - and there is a cool
     violet-blue stop at the bottom so the field cannot collapse into sepia.
     Warm plus one cool counterpoint reads as LIGHT; warm alone reads as paper
     that has aged.

     Measured, every stop, against --ink-mute (#606A80), the lowest-contrast
     text token permitted on the ground:
       ivory 5.24 · peach 4.63 · rose 4.68 · amber 4.83 · violet 4.68
     The worst is 4.63 against AA's 4.5. A white card on the warmest stop is
     1.14:1, so cards genuinely lift off it rather than melting into it.

     --ground stays a solid colour for components that need one; --sky is the
     wash, and only the body wears it. */
  --ground:     #FDF4E7;             /* warm ivory, the solid fallback       */
  --sky-solid:  var(--ground);       /* the SOLID form of --sky (which is a gradient) = the ground. Referenced 4x in organisms.css as var(--sky-solid, var(--ground)) but never defined in either era - a pre-existing undefined-token defect that only worked because of the fallback. Defining it as --ground keeps the established look and follows .on-night. Owner 2026-08-21. */
  --sky:
    radial-gradient(1250px 720px at  -4% -10%, #FCE9D2 0%, transparent 62%),
    radial-gradient(1000px 660px at 104%  2%,  #FBE7EC 0%, transparent 58%),
    radial-gradient( 900px 620px at  30% 58%,  #FEF2DC 0%, transparent 56%),
    radial-gradient(1050px 820px at  76% 112%, #F3EAFA 0%, transparent 52%),
    #FDF4E7;

  /* The layers that sit ON the light. A warm ground is what lets these be
     LIFTED PAPER rather than clinical white: paper on a warm field reads as
     lifted and clean when the paper itself has a whisper of the same warmth,
     where a pure-white card on a warm field reads as two different materials
     stacked - a printout laid on a linen tablecloth. That is why --surface
     is not literally --c-white.

     RETUNED 2026-08-18, owner: the panel reads too clinical-white, warm it -
     "a bolder pass, not just semantic colour, take your decision." FIRST CUT
     was --surface alone at #FFFCF8 (2.3% off white) with --ground untouched -
     rejected on measurement, not taste: it moved 3 of 255 in the blue channel,
     invisible in a screenshot pixel-sampled before/after, and it silently ate
     into the --tint-* band's own separation (a tint is defined as its OWN
     step off white, so warming the paper under it while leaving the tint
     alone shrinks the gap between "this region is state-tinted" and "this is
     just the paper" - measured, --tint-soon's contrast against the lone
     warmed surface fell to 1.005:1, which is the wash becoming the paper).

     So the retune moved as a FAMILY, not one token: --ground, --surface and
     every --tint-* / --surface-struct below were re-stepped together so each
     keeps the SAME contrast ratio it always had - just measured against the
     new, warmer --surface instead of against pure white. --surface is now
     #FFF9EF, a real 4.75% warm step off white (1.0475:1) - triple the first
     cut, and enough to read as a colour shift in a screenshot, not just a
     number. It still lifts off --ground by the ORIGINAL 4% (1.0404:1,
     matching the old 1.0399 exactly) because --ground moved down with it, so
     "cards genuinely lift off the page" is unchanged despite both being
     warmer. --ink on the new --surface is 15.20:1 (was 15.92 on pure white) -
     still 3.4x AA's 4.5:1 floor. --ink-mute, the tightest text token this
     system permits on a ground colour, is 4.98:1 on the new --ground - down
     from 5.43 but still clear of AA with margin. Rule 2 still holds: this is
     structure and warmth, not decoration, and every dependent token was
     re-measured rather than left to drift. */
  --surface:    var(--c-cream-50);    /* cards, tables, panels                */
  --raised:     var(--surface);       /* menus, dialogs - same paper, more lift */
  /* FIXED, THEME-INVARIANT SURFACE - the one role that does NOT get
     re-pointed by `.on-night` below. Some artwork (third-party logo marks in
     .trust-strip__chip) is dark ink on a transparent ground: it can only sit
     on light paper, in ANY surrounding context, or it disappears into its own
     background. Every other surface role changes meaning under `.on-night`;
     this one exists so a chip can opt OUT of that and stay lit. Not a general
     escape hatch - use it only when the content itself, not the chrome around
     it, requires light. */
  --surface-fixed: var(--c-cream-50);
  /* THE WELL IS THE SAME PAPER AS --surface, not literal white - see above.
     A grey fill is the cheapest possible way to say "type here" and it costs
     the whole surface: a form of grey boxes reads as a form printed on a
     photocopier. Warm paper with a real edge says the same thing and keeps
     the page one material. */
  --well:       var(--surface);       /* inputs are cut in with an EDGE, not a fill */
  --well-edge:  #E0D7CA;              /* warm, so it belongs to the ground;
                                          re-stepped with --surface so the
                                          input edge keeps its original 1.36:1
                                          step off the paper it cuts into    */
  --night:      var(--c-slate-950);   /* the dark surface: code, the board     */
  /* STRUCTURAL surface, not interactive and not --ground. A card head or a
     column-label band needs to read as furniture at rest, but --tint-cool is
     already spoken for as the hover fill on 12+ selectors (.btn--line:hover,
     .menu__item:hover, .ledger tbody tr:hover and more) - reusing it here would
     make a resting head look permanently hovered, and would render a ledger's
     own header the same colour as one of its hovered rows. --ground is only
     1.04:1 against --surface, which reads as a hole, not a surface. So a
     distinct token: 1.143:1 against --surface (vs --ground's 1.04, still
     three times the step - reads as furniture, re-stepped alongside --surface
     so the ratio holds) and a matching step against --tint-cool's own hover,
     so hover here stays exactly as legible as hover is anywhere else. NEVER
     put this on money or content - it is for a head or a label band, not a
     state and not a result. */
  --surface-struct: #F0EADF;

  /* Line */
  --line:       var(--c-slate-200);   /* 1.41:1 - hairline between rows        */
  --line-soft:  var(--line);          /* soft hairline; alias of --line. Was referenced by organisms.css's .handover border with NO fallback but never defined in either era - a pre-existing undefined-token defect (the border computed invalid). Defined via var(--line) so it follows .on-night. Owner 2026-08-21: "fix pre-existing defects." */
  --line-strong:var(--c-slate-300);   /* 1.97:1 - a control's own edge         */
  --rule:       var(--c-slate-900);   /* the ledger rule: table head and foot  */

  /* Ink */
  --ink:        var(--c-slate-900);   /* 15.92 - identifiers, money, headings  */
  --ink-body:   var(--c-slate-700);   /*  8.75 - body and table copy           */
  --ink-soft:   var(--c-slate-600);   /*  6.49 - secondary                     */
  --ink-mute:   var(--c-slate-500);   /*  5.43 - captions, column heads        */
  --ink-ghost:  var(--c-slate-400);   /*  3.07 - PLACEHOLDER AND DISABLED ONLY */
  --ink-on-fill:var(--c-white);
  --ink-on-night: #E8EAEE;            /* 14.6 on --night                       */

  /* Action - one colour, three states. Filled means go.

     --action is the SURFACE; --action-text is the same meaning as INK, and since
     2026-08-24 they are NO LONGER THE SAME VALUE. The surface is black; the ink
     stays crimson.

     THE SURFACE WENT BLACK, AND IT IS THE DESIGNER'S DECISION, NOT A NEW ONE.
     It shipped in the refresh (8d76d667 on this branch, from her own file), was
     reverted with everything else on 2026-08-21 so the owner could show the old
     identity to his boss, and comes back on its own here - owner's instruction,
     2026-08-24, after a design-team discussion: "Change the colours of button to
     blacks." The ground did NOT come back with it (see --ground), so this is the
     black button on the warm ivory page, which is a pairing neither era shipped.

     WHY BLACK IS NOT A NEW COLOUR. --c-slate-900 was in this palette the whole
     time, and her file explicitly REFUSED to add a --c-ink-900 for the wordmark's
     #1D1E23: that is 15.92:1 on white and --c-slate-900 is 15.94, "a difference
     no eye and no screenshot can find". A second near-black under a second name
     is the near-miss-with-a-new-name the consistency rule forbids.

     WHAT STAYS CRIMSON, AND WHY THE SPLIT IS THE POINT. --action-text,
     --action-wash and --action-ink are unchanged. Crimson is still how the panel
     writes a link and tints an action's own quiet surfaces; what stopped being
     crimson is the FILLED button. So the brand still reads on every screen, and
     the loudest object on the page is no longer claiming money is at risk.

     THE COINCIDENCE THIS COMMENT USED TO RECORD IS GONE, and that is a gain.
     Surface and ink were both crimson-500 on white - 5.18:1 in both directions -
     which is why the two roles were named separately rather than merged. They
     now differ by value as well as by name, so nothing can collapse them by
     accident. On night the surface inverts to white and its ink goes dark; the
     two-layer palette below already handles it.

     CONTRAST, MEASURED: white on --c-slate-900 is 15.94:1, against 5.18:1 for
     the crimson it replaces. Every filled button in the panel got more legible.

     SIDE EFFECT THAT IS NOT FIXED HERE: --firm below now resolves to the same
     --c-slate-900, so `.btn--firm` is provably redundant. Retiring it is its own
     commit - repoint callers to `.btn--go`, then drop the class and --firm*. */
  --action:       var(--c-slate-900);
  --action-fill:  var(--c-slate-900);     /* the surface form, for parity with
                                             the four states - it happens to be
                                             the same value, and naming it means
                                             a caller never has to know that   */
  --action-hover: var(--c-slate-800);
  --action-press: var(--c-slate-950);
  --action-text:  var(--c-crimson-500);   /* 5.18 - crimson used as ink        */
  --action-wash:  var(--c-crimson-50);
  --action-ink:   var(--c-crimson-900);   /* text on --action-wash             */

  /* FIRM - weight, and not a meaning. Added 2026-08-14 with `.btn--firm`.

     Every hue above is spoken for, so there was no way to emphasise a control
     without claiming money: a screen whose one action is not a purchase had to
     overclaim with crimson or ship a ghost button. Ink is the one value in this
     file that is not a state - it is what everything is written in - so a
     control filled with it borrows no meaning at all and reads as weight only.
     15.92:1 both ways, which makes it the highest-contrast control here.

     It is a ROLE and not `var(--ink)` written into the component, because on
     night the fill inverts to paper and its text has to go dark with it. A
     component that referenced --ink directly would render white on white the
     first time it was dropped on a dark region, which is the exact failure the
     two-layer palette exists to prevent.

     NOT a fifth state. Nothing may take it to mean "important", "selected" or
     "recommended" - see the boundary written beside `.btn--firm` in atoms.css. */
  --firm:       var(--c-slate-900);
  --firm-hover: var(--c-slate-800);
  --firm-press: var(--c-slate-950);
  --firm-ink:   var(--c-white);

  /* State - the four things a row can be, and nothing else may use them. */
  --calm:       var(--c-teal-600);    /* fine, and true                        */
  --calm-fill:  var(--c-teal-400);
  --calm-wash:  var(--c-teal-50);
  --calm-ink:   var(--c-teal-900);

  --good:       var(--c-green-600);   /* money back, a thing that worked       */
  --good-fill:  var(--c-green-400);
  --good-wash:  var(--c-green-50);
  --good-ink:   var(--c-green-900);

  --soon:       var(--c-amber-600);   /* due, owed, 8-30 days                  */
  --soon-fill:  var(--c-amber-400);
  --soon-wash:  var(--c-amber-50);
  --soon-ink:   var(--c-amber-900);

  --risk:       var(--c-crimson-500); /* expired, ≤7 days, money at risk       */
  --risk-fill:  var(--c-crimson-500);
  --risk-wash:  var(--c-crimson-50);
  --risk-ink:   var(--c-crimson-900);

  /* QUANTITY - the fill for a bar that is a NUMBER AND NOT A STATE.
     Added 2026-08-14, with `.plot`. Charts are where rule 2 dies first: the
     default in every charting library is a hue per series, which is colour
     assigned to things that have no state - decorative colour, by this system's
     own definition, and forbidden. But a bar still has to be a shape, and the
     five ink roles are all TEXT roles with text ratios; --ink-ghost is
     documented "placeholder and disabled only" and a chart is neither.

     So one role, and its whole boundary is in its name: a quantity with nothing
     to say about itself. The moment a bar means "due", "at risk", "fine" or
     "money back", it takes the state fill it already had. 3.07:1 on white and
     3.20:1 on night - non-text contrast (WCAG 1.4.11) is 3:1, so a bar drawn in
     it is a shape anybody can see, in both contexts, without borrowing a meaning
     it does not have.

     NOT FOR: text of any size, a control's edge, a state, or a second series
     that wants to be told apart from the first. Two series in one plot is two
     plots. */
  --quantity:   var(--c-slate-400);

  /* ─────────────────────────── TINTS ──────────────────────────────────────
     A whisper of hue on a whole surface - 3-5% of the colour over --surface,
     which is below the threshold at which anyone reads it as "coloured" and
     above the threshold at which a page of cards reads as a spreadsheet.

     This is NOT decorative colour. A tint may only be applied to a region whose
     SUBJECT is that state: the renewals panel is warm because renewals are what
     is owed, not because the page needed warming up. Used any other way it
     spends the meaning and the system is back to arbitrary colour.

     RE-STEPPED 2026-08-18 alongside --surface's warmth retune (see the note
     above --surface). Each tint keeps EXACTLY the contrast ratio it had
     against pure white, now measured against the new #FFF9EF --surface
     instead - so a tint still reads as a distinct step off the paper it sits
     on, rather than quietly merging into a paper that moved out from under
     it. Checked, not assumed: measured against the OLD --surface (pure
     white), the new tints looked identical (a tint is still a tint); against
     the new --surface each still clears ≥1.04, matching its original
     separation to within 0.01. --ink at 15.20:1 on the new --surface is
     still ≥14.5:1 on any of them and no text needs re-checking. */
  --tint-calm:  #EBF6F8;
  --tint-good:  #EBF7ED;
  --tint-soon:  #FDF4E1;
  --tint-risk:  #FFF0F2;
  --tint-cool:  #F7F2E9;   /* the neutral tint: structure, not state          */

  /* Focus - 3:1 against both the control and its surround (WCAG 2.4.11) */
  --focus:      var(--c-teal-600);

  /* ─────────────────────────── TYPE ────────────────────────────────────────
     TWO FACES NOW, NOT THREE, AND THE DISPLAY FACE IS THE ONE THAT WENT.
     Owner's instruction, 2026-08-24, after a design-team discussion: "Pick up
     the proposed fonts." The proposal is the designer's, from the refresh that
     shipped in f7b39db0 / 8d76d667 and was reverted on 2026-08-21.

       --font          everything that is read.        Inter
       --font-display  the one big thing per screen.   Inter, heavier and tighter
       --font-mono     things you copy, not read.      JetBrains Mono

     DISPLAY AND BODY SEPARATE BY WEIGHT AND TRACKING, NOT BY TYPEFACE. That is
     the change, and it is hers (point 7 of 8d76d667): --font-display is now
     var(--font) rather than a second stack. What this file used to argue - three
     faces, each with a job the others cannot do - was a real test and Instrument
     Serif passed it; it is being retired because the design team chose one
     voice, not because the argument was wrong.

     WHAT THAT COSTS, SAID PLAINLY SO IT IS NOT DISCOVERED LATER: a headline no
     longer announces itself by SHAPE, only by size and weight. Every rule that
     leaned on the serif to carry a title at a modest size now has to earn it
     with the display metrics below. That is why they move in the same commit.

     THE FALLBACK IS INSTRUMENT SANS, AND IT IS DELIBERATE - hers, and not a call
     this branch would have made. If the webfont fails, the panel lands on the
     face it was designed in rather than on Helvetica.

     SELF-HOSTED, NOT GOOGLE FONTS. Her file loads Inter from Google; the woff2
     were never added, so her copy renders the fallback. Four subsets ship here
     instead - latin, latin-ext, cyrillic, cyrillic-ext, 178KB, Google's own
     unicode-ranges - because the panel ships Russian and a webfont that 404s is
     not a refresh. The @font-face blocks are in base.css.

     The rule that keeps it from sprawling is unchanged: --font-display may set
     exactly one element per screen, and --font-mono is reserved for identifiers -
     domain names, auth codes, API keys, IP addresses. A price in the mono is
     still the system breaking. */
  --font: "Inter", "Instrument Sans", -apple-system, BlinkMacSystemFont,
          "Segoe UI", Helvetica, Arial, sans-serif;
  --font-display: var(--font);
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* THE DISPLAY METRICS ARE NOW THE ONLY THING SEPARATING A TITLE FROM A
     PARAGRAPH, so they are not cosmetic. They were tuned for Instrument Serif -
     a face that runs optically larger at the same px and wants loose-ish
     tracking - and Inter is neither, so all three move together. */
  /* THE ONE TOKEN THAT CANNOT GO BACK, and it is not a refresh value.
     This file is the PRE-REFRESH tokens file, restored 2026-08-21 so the owner can show
     the old design to his boss. But `base.css`, `organisms.css` and this file's own
     `--e*-weight` scale now all say `font-weight: var(--display-weight)` - six call sites
     - and reverting without defining it would leave every title with NO weight
     declaration at all, inheriting whatever its parent had. That is not the old design,
     it is a third thing neither person has seen.
     `var(--w-regular)` is what those six rules literally said before the refresh, so this
     reproduces the old look exactly: the display face here is Instrument Serif, and a
     serif at regular weight was the distinction. It becomes 700 again the moment the
     refresh is re-adopted. */
  /* SIZE JOINS THE SET, AND IT IS THE OWNER'S NUMBER. Instruction, 2026-08-24:
     "font size after change is becoming too big, fix it somewhere around 32 and
     it should not be bold." Said about the proposal page he was sent, which ran
     Inter BOLD at 44px - and both halves of that are corrected here.

     WHY A TOKEN RATHER THAN EDITING --t-3xl DOWN TO 32. --t-2xl is already 32px,
     so redefining --t-3xl would put two names on one value - the exact
     near-miss-with-a-new-name the consistency rule forbids, and it would silently
     shrink two MONEY components (.money--display, .nudge__amount) that were never
     the complaint and that rule 1 says are the content. So the display headings
     move down the existing scale and the money stays where it is.

     WHY IT BELONGS BESIDE THE OTHER THREE. Now that --font-display IS --font, size
     and weight and tracking are the ONLY things separating a title from a
     paragraph. Leaving size scattered across four component rules while its three
     partners sit here is how the four drift apart. One name, one place, five
     callers: h1/.display, .page__title, .band__title, .fork__title, --e1-size. */
  --display-size: var(--t-2xl);      /* 32px */

  /* NOT BOLD, AND THIS IS THE ONE DESIGNER VALUE BEING OVERRULED. Hers is 700
     (8d76d667, point 8), reasoned: "Inter's 600 reads optically lighter than
     Instrument Sans's, so 700 is where Inter's title mass matches what 600 used
     to give." That reasoning was about MATCHING the old mass at the old size. The
     owner has looked at the result and does not want that mass - so regular it
     stays, and at 32px rather than 44px the two decisions are consistent with each
     other: a smaller, lighter title. If a title ever needs to shout, that is what
     --t-4xl is for, once per screen. */
  --display-weight: var(--w-regular);

  /* TRACKING AND LEADING ARE HERS, AND THEY ARE NOT COSMETIC HERE. Both were
     tuned for Instrument Serif - a face that runs optically larger at the same px
     - and Inter is not that face. -0.032em (from -0.015em) is what stops Inter at
     display size reading loose and webby; 1.08 (from 1.04) is what stops a
     two-line title from locking up now that the ascenders are taller. */
  --display-track: -0.032em;
  --display-lh: 1.08;

  /* Money, IDs and anything in a column: same-width digits or the column lies. */
  --nums: tabular-nums lining-nums;

  /* A scale with real jumps. Adjacent sizes that differ by 1px are two sizes
     doing one job - the previous system carried eight roles on one screen. */
  /* THE WHOLE SCALE MOVED UP. 16px body is what most SaaS ships and it is a
     floor, not a target - Apple's system default is 17, Stripe reads at 17-18,
     and the difference between 16 and 17 across a page of dense financial data
     is the difference between a tool and a document.

     What actually felt small was --t-sm, because that is where DENSITY lives:
     status, dates, captions, help, card notes, the second line of every table
     row. It carries more text than the body size does, at --ink-mute, and 14px
     of muted grey is where a premium product turns into an admin panel.

     Gaps are ≥2px at every step, because two sizes a pixel apart are two sizes
     doing one job. The steps accelerate - 2, 2, 3, 5, 7, 12, 20 - because
     display sizes need bigger jumps to read as different, while text sizes need
     smaller ones to stay a family. */
  --t-xs:   13px;   /* column heads, eyebrows - uppercase and tracked only    */
  --t-sm:   15px;   /* supporting data: status, dates, captions               */
  --t-md:   17px;   /* body, EVERY input, every control                       */
  --t-lg:   20px;   /* money in a row, section leads                          */
  --t-xl:   25px;   /* section titles                                         */
  --t-2xl:  32px;   /* stat figures                                           */
  --t-3xl:  44px;   /* page titles                                            */
  --t-4xl:  64px;   /* the one display moment per screen, at most             */

  --w-regular: 400;
  --w-medium:  500;
  --w-semi:    600;

  --lh-tight:   1.15;   /* display and page titles                            */
  --lh-snug:    1.4;    /* headings, dense rows                               */
  --lh-body:    1.6;    /* prose                                              */

  --track-tight: -0.022em;  /* ≥28px                                          */
  --track-snug:  -0.011em;  /* 16-22px                                        */
  --track-wide:   0.08em;   /* uppercase eyebrows only                        */

  /* Prose has a measure. 68ch is where a line stops being comfortable. */
  --measure: 68ch;

  /* ═══════════════════════════ THE EMPHASIS LADDER ═══════════════════════
     THE MOST IMPORTANT THING ON A SCREEN IS DECIDED HERE, NOT PER PAGE.

     Every design system has a type scale and a colour scale. Almost none have
     an emphasis scale, and that is why screens built from good components still
     come out flat: eight elements all technically correct, all shouting at the
     same volume, and a reader who has to construct the hierarchy themselves.

     Five levels. Each has a treatment AND A BUDGET, and the budget is the part
     that does the work - a level with no limit is not a level.

       L1  THE ANSWER          what the screen exists to say
                               display face or the largest figure, --ink
                               BUDGET: exactly one per screen. Not zero. Not two.

       L2  THE CONSEQUENCE     what follows from it, and by when
                               --t-xl/--t-2xl, semibold, --ink
                               BUDGET: one per region. A card has one.

       L3  THE IDENTIFIER      which thing this is about
                               --t-md, medium, --ink, mono if it is a string
                               BUDGET: one per row or per card.

       L4  THE SUPPORT         status, dates, counts, amounts you scan
                               --t-sm, regular, --ink-mute
                               BUDGET: unlimited. This is where density lives.

       L5  THE AMBIENT         column heads, field labels, help, legal
                               --t-xs tracked or --t-sm, --ink-mute
                               BUDGET: unlimited, but it may never be the first
                               thing the eye lands on.

     THE TEST, and it takes ten seconds: squint until the text is unreadable.
     You should see one dark mass, then two or three, then texture. If you see
     an even field, the screen has no hierarchy no matter how good its parts
     are. If you see two equally dark masses, two things are claiming L1 and the
     reader will pick the wrong one.

     THE RULE THAT IS BROKEN MOST: promoting by SIZE alone. Size is only one of
     four instruments here. Weight, colour and - most of all - SPACE around a
     thing promote it without making it bigger, and a screen where everything
     important is also large is a screen that has run out of room. */
  --e1-size:   var(--display-size); --e1-weight: var(--w-regular);  --e1-ink: var(--ink);
  --e2-size:   var(--t-xl);    --e2-weight: var(--w-semi);     --e2-ink: var(--ink);
  --e3-size:   var(--t-md);    --e3-weight: var(--w-medium);   --e3-ink: var(--ink);
  --e4-size:   var(--t-sm);    --e4-weight: var(--w-regular);  --e4-ink: var(--ink-mute);
  --e5-size:   var(--t-xs);    --e5-weight: var(--w-semi);     --e5-ink: var(--ink-mute);

  /* ─────────────────────────── SPACE · 4px base ────────────────────────── */
  --s-1:  4px;
  --s-2:  8px;
  --s-3:  12px;
  --s-4:  16px;
  --s-5:  20px;
  --s-6:  24px;
  --s-8:  32px;
  --s-10: 40px;
  --s-12: 48px;
  --s-16: 64px;
  --s-20: 80px;
  --s-24: 96px;

  /* ─────────────────────────── RADIUS ──────────────────────────────────────
     One step per size of thing: a mark, a control, a container, a surface.
     Friendlier than a 4px system - a 10px control and a 16px card read as made
     rather than as drawn by a border property. */
  --r-sm:   8px;    /* marks under ~28px: checkbox, dot, chip                 */
  --r-md:   12px;   /* controls: inputs, buttons, filters                     */
  --r-lg:   18px;   /* containers: cards, tables, panels                      */
  --r-xl:   26px;   /* the few large surfaces: dialogs, hero cards            */
  --r-full: 999px;

  /* ─────────────────────────── ELEVATION ───────────────────────────────────
     Three, tinted with the slate hue rather than black - a black shadow on a
     cool ground reads as dirt. Never stack two. */
  /* ELEVATION IS FOR THINGS THAT ACTUALLY FLOAT, AND NOTHING ELSE.

     A card does not float. It is paper on a desk, and paper on a desk is
     separated from the desk by an EDGE, not by a shadow. Putting a shadow under
     every surface is the single most common way a product reads as cheap:
     everything is lifted, so nothing is, and the page acquires a soft grey haze
     that no amount of colour elsewhere recovers.

     So --lift-1 is gone. Cards, tables, tiles and results use a hairline. The
     only things that keep a shadow are the things that are genuinely above the
     page and can be dismissed: menus, dialogs, the command palette. Restraint
     here is what the rest of the system spends.

     The one exception is the primary button, which keeps a crimson glow rather
     than a grey shadow - it is the single "go" on a screen, and colour that is
     already meaning does not cost the page anything. */
  --lift-1: none;                     /* deliberately nothing. Use --line.    */
  --lift-2: 0 2px 6px -2px rgba(60, 40, 24, .06),
            0 16px 36px -12px rgba(60, 40, 24, .14);
  --lift-3: 0 4px 10px -3px rgba(60, 40, 24, .07),
            0 40px 72px -24px rgba(60, 40, 24, .24);
  /* THE PRIMARY BUTTON HAS NO GLOW, AND THE SHADOW WAS NOT REPLACED - IT WAS
     REMOVED. Owner's instruction, 2026-08-24, is the black button; this is the
     designer's own consequence of it, from her file (8d76d667, point 2). The
     value used to be a crimson bloom, tuned to make a mid-saturation fill sit
     off the page. A solid near-black on a light ground already separates from
     everything around it, so the glow was doing no work - and a shadow kept only
     because there used to be one is a fourth tier to keep in step with the
     other three forever.
     `none` and not a deleted token: 3 rules read var(--lift-go), and a token
     that stops existing takes the whole declaration down with it. */
  --lift-go: none;

  /* ─────────────────────────── MOTION ──────────────────────────────────────
     Doherty's threshold is 400ms. Nothing a person is waiting on may exceed it,
     which is why there is no slow duration in this file at all. */
  --fast: 120ms;    /* state the user caused: hover, focus, press             */
  --base: 200ms;    /* reveals, small position changes                        */
  --ease: cubic-bezier(.2, 0, 0, 1);

  /* ─────────────────────────── SIZE ────────────────────────────────────────
     44px is the floor: iOS will not zoom a 16px input, and Fitts wants ≥40px.
     Both are satisfied by one number, so there is one number. */
  --h-control:    44px;
  --h-control-sm: 36px;   /* quiet in-row controls only                       */
  --h-control-lg: 56px;   /* the hero field and its button                    */
  --h-row:        46px;

  /* A wordmark needs about 18px of cap height to stay legible inside a 44px
     (--h-control) chip: smaller stops reading, larger crowds the chip. For
     artwork (a logo image) inside a fixed-height chip - not a type size. */
  --logo-cap:     18px;

  --w-rail: 280px;        /* the sidebar, at the larger label size            */
  --w-page: 1180px;       /* the content measure                             */

  /* THE NARROWEST COLUMN A REFERENCE FIELD CAN USE, and it is measured, not
     chosen. .ledger--grid lays a one-column list out as a field of columns,
     and the only question is where a label stops fitting on one line. Against
     all 1,438 IANA extensions at --w-page, in Chrome:

       128px -> 9 cols, 66 ellipsised (4.6%), 5,630px
       144px -> 8 cols, 14 ellipsised (1.0%), 6,334px
       160px -> 7 cols,  8 ellipsised (0.6%), 7,249px
       176px -> 6 cols,  2 ellipsised (0.1%), 8,445px

     144 is the knee. The 704px it costs over 128 buys back 52 truncations, and
     truncation is not cosmetic here: at 128px `.accountant` and `.accountants`
     both read ".accounta..." in adjacent cells, which is a list that has
     stopped answering the only question it was asked. Past 144 each further
     truncation costs about 900px, so the trade turns.

     THE COLUMN COUNTS ABOVE ARE AT --w-page AND NO CARD IS --w-page WIDE. A
     card inside the page measure renders about 1,096px once its own padding is
     taken, so auto-fill lands on SEVEN 156px columns rather than eight, and the
     panel's own /pricing/ measures 7,249px against the 6,334 projected here.
     The knee does not move - wider columns truncate less, 10 cells rather than
     14 - but read the counts as the shape of the trade, not as a promise about
     a particular card. `auto-fill` is used precisely so this does not have to
     be predicted. */
  --field-col: 144px;

  /* THE TOP BAR, AND WHY IT IS 60 AND NOT 44 OR 56.
     It holds one --h-control field and nothing else, so the height is the
     control plus 8px of air above and below - the smallest gap in the scale
     either side of a 44px target. 56 would have crowded the field against both
     edges; 64 buys a second row of nothing. Every offset the shell needs is
     computed from this token, so the bar's height is stated once. */
  --h-bar:  60px;
  /* The field inside it. Wide enough that a domain and a customer name both fit
     without truncating - the two things this search is actually used for - and
     narrow enough that at 1440 it reads as a field placed on the bar rather
     than as the bar itself. Measured: 34ch of --t-md at this width. */
  --w-find: 520px;

  /* THE PLOT AREA OF A SERIES. Twelve columns of --h-plot is a block roughly as
     tall as three ledger rows, which is the budget a chart gets on a screen
     whose content is money: enough that a bar of 2 against a bar of 16 is
     unmistakable, not so much that the chart becomes the page. Below about
     120px a 1-in-16 segment stops being visible at all, which is the real
     floor. */
  --h-plot: 132px;

  --z-base: 0;
  --z-sticky: 20;
  --z-over: 40;
  --z-toast: 60;
}

/* ─────────────────────────── NIGHT, AS A CONTEXT ───────────────────────────
   A dark region RE-POINTS THE ROLES. It does not override components.

   That distinction is the whole reason for the two-layer palette: drop a card,
   a button, a state dot or a table onto `.on-night` and every one of them is
   already correct, because none of them ever referenced a raw colour. The
   alternative - `.auth__show .btn { … }` - is how a system acquires a second
   button, and then a third.

   Every value measured against --night (#14161A), alpha composited first:
     ink 18.11 · ink-body 10.81 · ink-soft 7.43 · ink-mute 5.31 · ink-ghost 3.20 */
.on-night {
  --ground:      var(--c-slate-950);
  --surface:     #1E2128;
  --raised:      #262A33;
  --well:        rgba(255, 255, 255, .06);
  /* Same structural surface as the light theme, re-earned for night rather
     than assumed: 1.185:1 against --surface (#1E2128) and 1.142:1 against
     --tint-cool (#21242B, below) - both clear the 1.07:1 the light theme sets
     as the floor. Not a state, not for money or content - see tokens.css light
     theme for the full reason. */
  --surface-struct: #2A2E37;

  --line:        rgba(232, 234, 238, .13);
  --line-strong: rgba(232, 234, 238, .26);
  --rule:        rgba(232, 234, 238, .34);

  --ink:         var(--c-white);
  --ink-body:    rgba(232, 234, 238, .84);
  --ink-soft:    rgba(232, 234, 238, .68);
  --ink-mute:    rgba(232, 234, 238, .55);
  --ink-ghost:   rgba(232, 234, 238, .38);

  /* The hues lighten, because teal-600 is 6.14:1 on white and 2.4:1 on night.
     Same meanings, same names, ratios re-earned rather than assumed.

     Crimson as INK lightens to crimson-300 at 8.20:1. */
  /* NIGHT KEEPS A VISIBLE PRIMARY, AND IT CANNOT BE THE LIGHT THEME'S BLACK.
     --c-slate-900 on --c-slate-950 is a button you cannot see - 1.06:1. So on
     night the primary fill is the near-white ink and its label flips dark, which
     is the standard inversion and the only one that keeps a filled primary
     legible here. Measured: --c-white on --c-slate-900 is 15.94:1 for the light
     theme's button; --c-slate-900 on --c-white is the same ratio inverted.
     THIS IS WHAT THE SURFACE GOING BLACK COSTS ON NIGHT, and it is why the old
     comment here could say "one button, two legible jobs" and this one cannot:
     crimson-500 was legible against BOTH grounds, so night needed no override at
     all. Black is not, so night now inverts. */
  --action:      var(--c-white);
  --action-fill: var(--c-white);
  /* AND THE LABEL HAS TO FLIP WITH IT. Caught by rendering the night demo during
     the refresh: it showed a blank white block where a primary button should be,
     because --ink-on-fill is white in the light theme and night had no override -
     white text on a white fill. --firm-ink a few lines down has done exactly this
     since it was added, so this follows the pattern already here rather than
     inventing a second answer. --c-slate-950 on --c-white is 17.4:1. */
  --ink-on-fill: var(--c-slate-950);
  --action-hover:rgba(255, 255, 255, .88);
  --action-press:rgba(255, 255, 255, .76);
  --action-text: var(--c-crimson-300);   /*  8.20 */
  --action-wash: rgba(244, 65, 95, .14);
  --action-ink:  var(--c-crimson-200);   /* 12.16 on the wash */

  /* FIRM INVERTS. On paper it is near-black with white text; on night it is
     paper with near-black text. Same job - the highest-contrast control in the
     context - and the ratio is re-earned rather than assumed: slate-950 on white
     is 17.4:1. This is why the button reads a role and not `--ink`. */
  --firm:        var(--c-white);
  --firm-hover:  var(--c-slate-100);
  --firm-press:  var(--c-slate-200);
  --firm-ink:    var(--c-slate-950);

  --calm:        var(--c-teal-300);      /*  9.36 */
  --calm-fill:   var(--c-teal-400);
  --calm-wash:   rgba(107, 200, 213, .13);
  --calm-ink:    var(--c-teal-200);      /* 12.59 */

  --good:        var(--c-green-300);     /*  9.56 */
  --good-fill:   var(--c-green-400);
  --good-wash:   rgba(116, 208, 129, .13);
  --good-ink:    var(--c-green-200);     /* 12.63 */

  --soon:        var(--c-amber-300);     /* 10.34 */
  --soon-fill:   var(--c-amber-400);
  --soon-wash:   rgba(240, 188, 69, .13);
  --soon-ink:    var(--c-amber-200);     /* 13.29 */

  --risk:        var(--c-crimson-300);   /*  8.20 */
  --risk-fill:   var(--c-crimson-400);
  --risk-wash:   rgba(244, 65, 95, .14);
  --risk-ink:    var(--c-crimson-200);

  /* Quantity re-points like everything else. slate-400 is 3.07:1 on white and
     3.20:1 composited on night, so the same token clears 1.4.11 in both - it is
     restated here anyway rather than left to inherit, because a role that
     silently survives the context switch by luck is a role nobody re-checks the
     day the ground moves. */
  --quantity:    var(--c-slate-400);

  /* THE TINTS AND THE WELL EDGE FOLLOW. They did not, and the two-layer palette
     exists precisely to stop that: a card tint or an input edge inside a dark
     region was resolving to its LIGHT value - #FFF7F8 and #E4DCD1, both a hair
     off white - and painting a near-white slab on a #1E2128 surface.

     On night a tint cannot be "a whisper of hue over white", because there is
     no white. It is a whisper of hue over the SURFACE, which means lifting the
     surface slightly and colouring the lift. Same idea, inverted, and the same
     rule still holds: a tint may only be used where the subject IS that state. */
  --tint-calm:  #17262E;
  --tint-good:  #16261A;
  --tint-soon:  #2A2318;
  --tint-risk:  #2C1A20;
  --tint-cool:  #21242B;
  --well-edge:  rgba(232, 234, 238, .16);

  --focus:       var(--c-teal-300);

  --lift-1: none;   /* a shadow on black is not an elevation, it is a smudge -
                       on night, height is carried by --surface stepping up */
  --lift-2: 0 12px 32px -10px rgba(0, 0, 0, .62);
  --lift-3: 0 32px 64px -20px rgba(0, 0, 0, .72);

  background: var(--c-slate-950);
  color: var(--ink-body);
}

/* ─────────────────────────── A FINGER IS NOT A MOUSE ───────────────────────
   THE SMALL CONTROL SIZE ONLY EXISTS FOR POINTERS.

   36px is a reasonable in-row control for a cursor that lands on a single
   pixel. It is not a reasonable target for a fingertip, which covers about 10mm
   - and this system's own rule says the floor is 44. An audit at 360px found
   31 controls under it, every one of them a .btn--sm or something built on the
   small size.

   One token, redefined for coarse pointers, fixes all of them at once - and
   that is the whole argument for having the size in a token rather than in
   each component. Nothing else changes: the type stays the same size, the
   padding grows, and a dense table on a desktop is unaffected because a
   desktop has a fine pointer.

   `pointer: coarse` rather than a width query, because a 1024px tablet is
   touched and a 360px window on a laptop is not. */
@media (pointer: coarse) {
  :root { --h-control-sm: 44px; }
  /* The ones that are not built on --h-control-sm and still have to grow. */
  .rail__link, .menu__item, .tabs__tab, .palette__hit { min-height: 48px; }
  .chip__x, .notice__x { width: 44px; height: 44px; }
  .check input, .switch input { transform: scale(1.15); transform-origin: left center; }
  .row-acts { gap: var(--s-2); }
}

/* Motion is a preference. The functional layer never depends on the motion
   layer - everything below still reads correctly with all of it switched off. */
@media (prefers-reduced-motion: reduce) {
  :root { --fast: 1ms; --base: 1ms; }
}
