/* THE CROSS-APP PALETTE IS THE SAME SOURCE (20 Aug 2026). Measured that day: this
   file and assets/rollon-tokens.css NEVER MET, so the estate and the Player were
   reading two different sets of values while both claiming to be one app. The
   palette file says in its own header that it is "loaded by every surface
   INCLUDING the Player"; it just was not loaded by this one. An @import at the top
   is load-bearing: css requires @import before any other rule. */
@import url("/assets/rollon-tokens.css");

/* THE APPROVED FACE, LOADED AT THE ROOT (04 Sep 2026, project_design_suite_rulings_2026_09_04).
   Plus Jakarta Sans is the one approved brand face. It loads HERE, in the file already first in the
   critical path, so --crew-font below resolves to a real face on every surface. CSP-safe: the sheet
   is fetched from fonts.googleapis.com and its faces from fonts.gstatic.com, both on the allowlist.
   Weights 400/500/600/700/800 back the map 800 caps / 600 names / 500 body. @import must precede
   every non-import rule, so it sits beside the palette import above. */
@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap");

/* NOTHING PAINTS BEFORE ITS OWN STYLESHEET (19 Aug 2026).
   Celina, on a screenshot of app.rollonent.com mid-refresh showing the dock as raw browser buttons
   ("DM CELINA ROLLON0  ASK AI  ALERTS  BACK  SEARCH") and no wordmark: "it needs to be removed. is
   this a pattern across the estate?" It is. Three shared components INJECT their own stylesheet at
   runtime and render immediately: assets/rollon-dock.js (26 surfaces), assets/record-card.js (8) and
   assets/rollon-xnav-auto.js (1). Each has a window where its markup is in the DOM and its CSS is
   still in flight, which is invisible on a warm desktop and plainly visible on a phone refresh.
   The rule lives HERE, in the file that is already in the critical path, so a component only has to
   set an attribute rather than race to inject a style of its own. Each one reveals on its
   stylesheet's load event and FAILS OPEN on error or after a backstop timeout: a component nobody
   can see is worse than one that arrives plain. */
[data-rollon-cloak]{visibility:hidden !important}

/* iOS SAFARI RESIZES TEXT ON ITS OWN, AND NOTHING HERE HAD EVER TOLD IT NOT TO (19 Aug 2026).
   Celina: "on the mobile the dates are trunchicating but not on the mock...why is it different live?"

   MEASURED OFF HER SCREENSHOT at native resolution (1206px wide, DPR 3, so 402 CSS px): the EXPIRY
   field rendered 352 x 75 CSS px while PASSPORT NUMBER and ISSUING COUNTRY, in the same block and on
   the same rule, rendered 325 x 47. A 47px control that is 75px tall is a 47px control whose text has
   WRAPPED. Chromium and WebKit both render all three at 336 x 47.

   iOS Safari's text auto-sizing inflates text PER BLOCK on its own heuristics, which is exactly why
   one field grew and its neighbours did not, and why no desktop engine reproduces it: Playwright's
   WebKit does not implement the iOS feature. Nothing in this estate set text-size-adjust anywhere:
   zero files, measured.

   100%, not none: none would also block her from pinch-zooming the page, which is a real thing people
   do with a passport number.

   NOT VERIFIED ON A HANDSET. This is the leading cause and it fits every measurement I have, but the
   only proof is her phone. If a date still wraps after this, the cause is something else. */
html{-webkit-text-size-adjust:100%;text-size-adjust:100%}

/* VIEW SCALE · 75% IS THE NEW 100% (job 411). Her ruling 14 Sep 2026, off her own screenshots at 75%
   browser zoom (16 rows and 10 columns in the finance grid where 100% showed 9 and 7): "I think our 100%
   zoom is too much. Can we reduce down to 75% is 100%". One token scales the root of every surface that
   loads this file; the artboards stay the 1:1 reference and the numbers in them are still the numbers in
   the CSS. THE RECORD CARD FRAME: the iframe element wears the inverse (1 / .75) so its box maps to the
   screen 1:1, and the frame's own document wears the page's scale (its head copies it from the page that
   holds it), so the card renders at .75 with nothing compounding. MEASURED 15 Sep 2026: with the page
   zoomed and the frame at 1, Playwright mapped a frame element to the screen without the zoom and every
   press inside the card landed on the wrong field (three door gates red); with this composition the
   mapping is identity and the frame keeps the same screen box (642.75 x 450 of a 644.75 x 452 window in
   the probe, both ways). Phones stay 1:1: her ruling was the desktop canvas. Chromium keeps vh/vw, innerWidth,
   getBoundingClientRect and pointer coordinates in screen pixels while layout runs in zoomed pixels
   (MEASURED 15 Sep: a calc(100vh / var(--ui-zoom,1)) block stood 600 of 800 screen px), so every viewport unit in the estate
   divides by this token and every script that reads the screen divides by uiZoom() (assets/view-scale.js). */
:root{--ui-zoom:.75}
iframe[title="RECORD"]{zoom:calc(1 / var(--ui-zoom,1))}
@media (max-width:700px){:root:not([data-card="1"]){--ui-zoom:1}}
html{zoom:var(--ui-zoom)}

/* ONE FACE, DECLARED, NOT ARRIVED AT (22 Aug 2026). Celina: "We can't keep adding unapproved fonts
   to the site." MEASURED on live before this: 457 elements Archivo, 18 Arial, 3 Times - the root had
   no family, and native form controls do not inherit one, so every element without an explicit
   family landed on a system default. The face is declared ONCE, here, at the root, and controls
   inherit it. Fixing it per-element is the pattern this file exists to end. */
html{font-family:var(--crew-font,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif)}
button,input,select,textarea{font-family:inherit}

/* THE STATUS TRIO, PROMOTED NOT COLLAPSED (14 Aug). crew-readiness.html carried --ok / --warn as a
   private palette and its own comment said why it had been left alone: "the estate's status language is
   --grn-* / --amb-*, whose values are NOT these, so aliasing the two would be a visible change to every
   readiness row and is not a refactor to make quietly." That was right.
   Celina has since asked for one palette across the tour surfaces. Aliasing --ok onto --grn-tx would
   ALSO collapse --ok and --ok-tx into a single value and flatten a real distinction (the solid dot
   against the label beside it). So the purpose-built status colours move UP into this file instead:
   every page can use them, the private copy can go, and nothing changes colour anywhere. */
/* ============================================================================
   ROLLON : tokens.css v2 : THE ONE VISUAL LANGUAGE
   ----------------------------------------------------------------------------
   Chartered by DP6 blueprint §7.1 as the single token source. v2 keeps that job
   and CHANGES THE CONTENT, per FABLE_DESIGN_SCOPE_2026-08-12 §5.1 (M2):

     the canonical set is now the TOUR-FAMILY language, not the grid's.

   WHY. Fable measured six design systems where there should be one. The tour
   family (tour-mgmt, travel-board, crew-home, agent-portal, crew-tour,
   crew-readiness, crew-tours, travel-folder, inbox, shell) is the most
   complete, the most modern (3-state data-theme), and already covers ~10
   surfaces by copy-paste. It is promoted here so the copies can be deleted.

   VALUES ARE VERBATIM from travel-board.html:19-69, the fullest existing copy.
   Nothing in the palette was invented, redrawn or "improved". A tour surface
   that links this file and deletes its inline :root block renders identically.

   ORDER OF THE FILE
     1. CANONICAL : the tour-family set, 3-state (bare :root light /
        @media prefers-color-scheme:dark on :root:not([data-theme="light"]) /
        :root[data-theme="dark"] / :root[data-theme="light"]).
     2. SYSTEM : the ONE type scale + the ONE brand font stack. Theme-free.
     3. GRID LEGACY : fenced. M8 mechanism half DONE 12 Aug (grid left body.dark); the fence retires with the palette half, which is review-gated.

   RULES THAT STAND (ledger + blueprint §8)
     - No raw hex on any surface that has a token; the §5.1 semantic exceptions
       (the near-black brand bar, which is dark in BOTH themes) are the only
       permitted literals.
     - The wordmark is only ever /rollon-wordmark-white.png. Never CSS text.
     - This file defines CUSTOM PROPERTIES ONLY. It sets no element rules, so
       linking it into a surface is inert until that surface consumes a token.
     - Served same-origin (copied into each deploy, never a CDN): CSP-safe.
     - Bump each surface's ?v cache token when this file changes (deploy rule).

   CONSUMERS TODAY: grid.html only (verified 2026-08-12 : every other
   tokens.css string in the estate is a comment recording the 2026-07-06
   removal, not a <link>). Linking the tour family is the HTML half of M2 and
   is owned elsewhere; read the GRID LEGACY fence note before doing it.
   ============================================================================ */


/* ════════════════════════════════════════════════════════════════════════════
   1. CANONICAL : THE TOUR-FAMILY SET
   Source: travel-board.html:19-69 (verbatim).
   ════════════════════════════════════════════════════════════════════════════ */

/* --blue-line / --err / --err-line : promoted out of crew-chat 14 Aug, at its exact values, so
   nothing on that page moved. --err is NOT --err-tx: crew-chat's error is a browner #8a3b2c
   against the estate's #c22f2f, and aliasing them would have recoloured every error on the
   page under the word 'refactor'. NOTE ON --blue-line: crew-travel defines the same name with
   the SAME light value and a DIFFERENT dark one (#2b3f5c against this rgba, which composites
   to roughly #354763 over --card). Near-identical, not identical. crew-travel keeps its own
   private palette for now, which overrides this file, so nothing moves under it either; the
   two must be reconciled deliberately when that page is migrated. */
/* --field / --fieldln : the FORM CONTROL ground and its hairline, promoted out of
   crew-invoices/crew-travel 14 Aug. An input sits ON a card and must not read as the
   card, so it is its own pair rather than an alias of --card/--line. Both pages carried
   byte-identical values in all four states; crew-chat's dark copy differs by two shades
   (#101116/#333437) and keeps its own until it is migrated, so nothing moves under it. */
/* LIGHT : warm paper. The default state, and the state an unset OS gets. */
:root{
  --page:#0c0c0e;--topbar:#0c0c0e;--topln:rgba(255,255,255,.10);
  --card:#ffffff;--pill:#ece7db;--pilltx:#5b5648;
  --field:#ffffff;--fieldln:var(--ring);--file-ground:rgba(127,127,127,.14);
  --blue-line:#b9d2f4;--err:#8a3b2c;--err-line:#d99c8f;
  --text:#1a1e24;--dim:#645f53;--faint:#8b8578;--hoverbg:#f7f4ee;--pick-hi:rgba(45,108,203,.10);
  --blue:#2d6ccb;--blue-hi:#2258ae;--on-blue:#ffffff;--blue-soft:#eaf1fc;--blue-tx:#1f4f9e;
  --sel:#d9e7fb;
  --org-tx:#c4571a;--org-bg:#fae7da;--org-bd:#efc4a4;
  --grn-bg:#ddf3d8;--grn-tx:#1e5a22;--grn-bd:#bfe4b7;--err-tx:#c22f2f;--err-bd:#e2b0b0;
  --ok:#1f8a54;--ok-soft:#e4f4ea;--ok-tx:#166b41;--warn:#b5701a;--warn-soft:#f6ecdd;--warn-tx:#8a5312;
  --amb-tx:#8a5a12;--amb-bg:#f7ecd0;--amb-bd:#e6cf9c;
  --shadow:0 1px 2px rgba(40,34,22,.05),0 1px 3px rgba(40,34,22,.05);
  --bulkshadow:0 6px 30px rgba(30,25,15,.22);
}

/* OS DARK, no explicit choice. The :not() guard is load-bearing: without it a
   user who picked LIGHT on a dark-OS machine gets the dark palette anyway.
   (crew-home.html has this bug today and only survives on specificity luck.) */
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){
  color-scheme: dark;
  --field-bg:#1B1C1E;
  --page:#0d0d0f;--topbar:#000;--topln:rgba(255,255,255,.09);
  --card:#17181b;--pill:#26272b;--pilltx:#b9b4a8;
  --field:#101114;--fieldln:var(--ring);--file-ground:rgba(127,127,127,.14);
  --blue-line:rgba(111,162,240,.34);--err:#e0a496;--err-line:#7a4d44;
  --text:#e9e6df;--dim:#9a968c;--faint:#77746c;--hoverbg:#1d1e21;--pick-hi:rgba(255,255,255,.08);
  --blue:#6fa2f0;--blue-hi:#86b4f5;--on-blue:#0b1d33;--blue-soft:#17222f;--blue-tx:#9cc0f5;
  --sel:#1f3350;
  --org-tx:#f09a66;--org-bg:#33200f;--org-bd:#5f3a1d;
  --grn-bg:#183a1c;--grn-tx:#8fd694;--grn-bd:#2b5730;--err-tx:#f08a8a;--err-bd:#6b2b2b;
  --ok:#5cc98c;--ok-soft:#152417;--ok-tx:#7fd6a4;--warn:#e0a35c;--warn-soft:#251c11;--warn-tx:#e8b678;
  --amb-tx:#e0b968;--amb-bg:#33290f;--amb-bd:#5a481f;
  --shadow:0 1px 2px rgba(0,0,0,.4);--bulkshadow:0 6px 30px rgba(0,0,0,.6);
}}

/* EXPLICIT DARK : the ar_theme choice, set on <html> before first paint by
   assets/theme.js. Wins over the OS in both directions. */
:root[data-theme="dark"]{
  color-scheme: dark;
  --field-bg:#1B1C1E;
  --page:#0d0d0f;--topbar:#000;--topln:rgba(255,255,255,.09);
  --card:#17181b;--pill:#26272b;--pilltx:#b9b4a8;
  --field:#101114;--fieldln:var(--ring);--file-ground:rgba(127,127,127,.14);
  --blue-line:rgba(111,162,240,.34);--err:#e0a496;--err-line:#7a4d44;
  --text:#e9e6df;--dim:#9a968c;--faint:#77746c;--hoverbg:#1d1e21;--pick-hi:rgba(255,255,255,.08);
  --blue:#6fa2f0;--blue-hi:#86b4f5;--on-blue:#0b1d33;--blue-soft:#17222f;--blue-tx:#9cc0f5;
  --sel:#1f3350;
  --org-tx:#f09a66;--org-bg:#33200f;--org-bd:#5f3a1d;
  --grn-bg:#183a1c;--grn-tx:#8fd694;--grn-bd:#2b5730;--err-tx:#f08a8a;--err-bd:#6b2b2b;
  --ok:#5cc98c;--ok-soft:#152417;--ok-tx:#7fd6a4;--warn:#e0a35c;--warn-soft:#251c11;--warn-tx:#e8b678;
  --amb-tx:#e0b968;--amb-bg:#33290f;--amb-bd:#5a481f;
  --shadow:0 1px 2px rgba(0,0,0,.4);--bulkshadow:0 6px 30px rgba(0,0,0,.6);
}

/* EXPLICIT LIGHT */
:root[data-theme="light"]{
  --page:#0c0c0e;--topbar:#0c0c0e;--topln:rgba(255,255,255,.10);
  --card:#ffffff;--pill:#ece7db;--pilltx:#5b5648;
  --field:#ffffff;--fieldln:var(--ring);--file-ground:rgba(127,127,127,.14);
  --blue-line:#b9d2f4;--err:#8a3b2c;--err-line:#d99c8f;
  --text:#1a1e24;--dim:#645f53;--faint:#8b8578;--hoverbg:#f7f4ee;--pick-hi:rgba(45,108,203,.10);
  --blue:#2d6ccb;--blue-hi:#2258ae;--on-blue:#ffffff;--blue-soft:#eaf1fc;--blue-tx:#1f4f9e;
  --sel:#d9e7fb;
  --org-tx:#c4571a;--org-bg:#fae7da;--org-bd:#efc4a4;
  --grn-bg:#ddf3d8;--grn-tx:#1e5a22;--grn-bd:#bfe4b7;--err-tx:#c22f2f;--err-bd:#e2b0b0;
  --ok:#1f8a54;--ok-soft:#e4f4ea;--ok-tx:#166b41;--warn:#b5701a;--warn-soft:#f6ecdd;--warn-tx:#8a5312;
  --amb-tx:#8a5a12;--amb-bg:#f7ecd0;--amb-bd:#e6cf9c;
  --shadow:0 1px 2px rgba(40,34,22,.05),0 1px 3px rgba(40,34,22,.05);
  /* travel-board's explicit-light copy omits --bulkshadow. Carried here so the
     block is self-complete; the value is the bare :root value, so no surface
     changes. */
  --bulkshadow:0 6px 30px rgba(30,25,15,.22);
}


/* ════════════════════════════════════════════════════════════════════════════
   2. SYSTEM : ONE TYPE SCALE, ONE BRAND STACK. Theme-independent.
   ════════════════════════════════════════════════════════════════════════════ */
:root{
  /* THE ONE BRAND STACK (Fable §5.1: "--crew-font moves here, one owner").
     Plus Jakarta Sans is the approved face (project_design_suite_rulings_2026_09_04), loaded via the
     Google Fonts @import at the top of this file. This is the SINGLE owner: assets/shell-strip.css and
     assets/components.css read it, html sets it once and button/input/select/textarea inherit. The
     per-surface "Archivo" strays (crew-ui.css, grid.html) fold onto this token. */
  --crew-font:"Plus Jakarta Sans",-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;

  /* DP2 six-step scale (blueprint §2.2). Weight carries rank within a size,
     size between levels. */
  --fs-title:23px;--fs-head:15px;--fs-body:13px;--fs-cell:12.5px;--fs-meta:11.5px;--fs-label:11px;

  /* The three crew-portal steps that have no DP2 equivalent, values verbatim
     from crew-ui.css:25-28. Carried here so the union of both scales has ONE
     owner and crew-ui.css can stop defining them.
     OPEN COLLISION, NOT RESOLVED HERE: crew-ui.css also redefines --fs-title
     (27px, not 23px) and --fs-body (15px, not 13px) and loads AFTER this file
     on the three surfaces that link both (crew.html, crew-invoices.html,
     crew-travel.html), so the crew values win there today. Reconciling those
     two numbers is a VISIBLE change to nine crew surfaces and belongs on
     Celina's review list, not in a silent refactor. Until she rules,
     crew-ui.css keeps its two definitions and this file owns the other seven. */
  --fs-section:13px;--fs-data:12px;--fs-caption:10.5px;
}


/* ════════════════════════════════════════════════════════════════════════════
   3. GRID LEGACY : FENCED. M8 mechanism half done 12 Aug; retires fully with the review-gated palette release.
   ----------------------------------------------------------------------------
   grid.html is the ONLY surface linking this file today, and Fable's migration
   order puts it LAST on purpose: biggest surface, riskiest, and nothing else
   waits on it. So the grid's own names stay, unrenamed, and the grid renders
   byte-identically to before v2.

   Two groups, deliberately separated:

   3a. GRID-ONLY NAMES : no collision with the canonical set, so they sit on a
       plain :root and are simply carried forward verbatim.

   3b. SHARED NAMES : every name the grid consumes that the canonical set also
       defines, pinned to the grid's LIGHT values and scoped to the grid alone
       with :root:has(#tokens-fallback). #tokens-fallback is the <style> block
       at grid.html:9, present on that surface and nowhere else in the estate
       (verified), and it retires with the review-gated palette half of M8.

       THE SCOPE IS LOAD-BEARING, TWICE OVER.
       (i) HISTORY (pre-M8): grid.html carried its theme on body.dark. Since 12 Aug it sets
           data-theme explicitly; the fence still pins the grid's LIGHT values against the
           canonical dark blocks, which is state (i)'s job either way.
           So on a dark-OS machine where the reader has chosen LIGHT, the
           canonical @media block above still matches <html> and out-specifies
           a plain :root (0,2,0 beats 0,1,0). Without this fence the grid would
           paint a light --bg under a dark --text. The :has() scope is (1,1,0)
           and settles it in every one of the four theme states.
       (ii) It is also what lets a TOUR surface link this file and get the TOUR
           hairlines rather than the grid's, so M2's remaining HTML half is a
           true pixel no-op.

       Only three of these values actually differ between the two sets:
             --line   #E8E3D9 (grid)  vs  #e4dfd4 (tour)
             --line2  #F1EDE5 (grid)  vs  #efeae0 (tour)
             --sel    #C7DBFB (grid)  vs  #d9e7fb (tour)
       The rest (--text --dim --blue --card --on-blue) are the same colour in
       both sets, differing only in hex case; they are pinned anyway, because
       the fence has to hold the whole set for reason (i) to work.

   DARK: the grid's own dark layer below, converted to html[data-theme] at M8. It is the grid's own mechanism,
   no other surface uses it, and it out-specifies :root for the whole body
   subtree : which is precisely what keeps the grid's dark palette intact even
   while the canonical dark blocks above are also matching <html>.
   ════════════════════════════════════════════════════════════════════════════ */

/* 3a. grid-only names, verbatim from v1. */
:root{
  --blue-active:#1c4a94;
  /* --bg WAS A LIGHT LITERAL IN A THEME-NEUTRAL BLOCK, AND THAT IS A LIVE BUG, NOT A TIDY-UP.
     Celina, 17 Aug, on /travel-party: "is both dark and light mixed. we are really reverting on the
     premium luxury."
     MEASURED headless in dark: body background rgb(241,238,231) with body TEXT rgb(233,230,223) and 25
     panels dark. Near-white ink on warm paper, with dark cards on top of it.
     THE CAUSE: --bg was declared ONCE here as #F1EEE7, in a bare :root that applies in every theme, and
     its dark value existed ONLY inside :root[data-theme="dark"]:has(#tokens-fallback), which is
     GRID-SCOPED. So on every non-grid surface --bg has been light in dark mode all along. travel.html
     was shielding itself with its own --bg:var(--page); removing that in the palette sweep did not
     create the bug, it exposed it, and it was latent for every other page that ever used --bg.
     THE FIX IS var(), NOT A SECOND LITERAL: --bg now follows --page, which is defined in all four theme
     states, so --bg is correct in every one of them and can never drift from the page ground again.
     grid.html's own :has(#tokens-fallback) override still wins on grid, so nothing moves there. */
  /* THE WHOLE GRID-LEGACY FAMILY HAD THE --bg BUG, not just --bg (17 Aug).
     Celina: "whne I press views the left bar pops out light on dark", and "the fixes need to be over
     EVERY single surface of the ONE APP."
     MEASURED: the VIEWS rail is painted by control-bar.js with background:var(--toolbar) and its dark
     values for --toolbar, --hover, --cell, --cell-alt, --hcell, --rownum, --sortcol and --cline live
     ONLY inside :root[data-theme="dark"]:has(#tokens-fallback), which is GRID-SCOPED. So on every
     non-grid surface this entire family was a LIGHT LITERAL in every theme. In dark the rail rendered
     rgb(248,245,239) with rgb(233,230,223) text: a near-white panel over a dark page, with near-white
     text on it, so the view names were nearly invisible as well. Reproduced on /advancing, /board and
     /travel-party, which is every surface that mounts the rail.
     FIXED THE SAME WAY AS --bg AND FOR THE SAME REASON: each points at a canonical token that resolves
     in all four theme states, so it can never drift from the ground it sits on. grid.html's own
     :has(#tokens-fallback) overrides still win on grid, so nothing moves there. */
  --bg:var(--page);--toolbar:var(--card);--hover:var(--hoverbg);
  --cell:var(--card);--cell-alt:var(--card);--hcell:var(--line2);--rownum:var(--card);--sortcol:var(--hoverbg);--cline:var(--line);
  /* color-scheme : THE ONE LINE THAT MAKES NATIVE CONTROLS BELONG TO THE PAGE.
     Celina, 15 Aug, on the tour picker: the dropdown "renders as a light OS menu over a dark page",
     and: "Impliment an easy UI keeping premium Luxury minamilist design".
     MEASURED: nothing in the estate declared `color-scheme` at all. Every hit in the codebase was the
     @media (prefers-color-scheme) QUERY, which asks the OS what it prefers and never TELLS the browser
     what this page is. So a <select> popup, a date picker, a scrollbar and an autofill highlight all
     rendered in the OS light default on top of a dark surface. There are 12 native selects on the tour
     surfaces alone, plus the two date inputs the `is between` range control adds.
     Declaring it is the whole fix, and it is the minimal one: no custom listbox, no re-implementation
     of a control the browser already does well, nothing to keep in step. */
  color-scheme: light;
  /* --field-bg : THE BACKGROUND OF A TEXT INPUT, and it is a TOP-LEVEL token on purpose.
     Found 15 Aug: the shared dialog's input read background:var(--cell,...), and --cell is a
     GRID-ONLY name whose dark value lives in `:root[data-theme="dark"]:has(#tokens-fallback)`.
     A page without that marker element (travel-party, and most others) therefore kept --cell:#FFFFFF
     in DARK mode, so rollonPrompt rendered #E9E6DF text on #FFFFFF : cream on white, unreadable.
     Celina, on Save View: "the name pops up but its yellow on white". A token a page has to opt into
     is not a theme; this one is defined in every theme block and gated by nothing. */
  --field-bg:#FFFFFF;
  --pill-bg:#EDE8DC;--pill-text:#4A443A;--chip-bg:#ECE6D9;--chip-text:#2C4A78;
  --groupbar:#EFE9DC;--foot:#F8F5EF;--note:#8a6d00;
  --mask:rgba(40,34,22,.34);
  --head:#57524A; /* recessed column-header ink : headers are furniture, they recede below data */
}

/* 3b. the shared names, pinned to the grid's light values, grid-scoped. */
/* THE GRID FENCE IS RELEASED. 22 Aug 2026.
   The pinned SHARED names are gone. They were a migration fence, and the migration finished: the
   canonical blocks below define all four theme states, so pinning --line, --text, --blue, --card and
   --on-blue here only kept her biggest surface on an older palette than the one she signed off.
   Measured before this change: --line was #E8E3D9 light / #33342F dark on /grid and #c5c0b5 on
   /onegrid. Same token, two answers, which is the whole reason the Directory's hairlines never
   matched the approved template. The comment that used to sit here called this release REVIEW-GATED,
   "a visible change to her biggest surface is hers to accept". She has asked for it in as many words:
   "everything must match and be ONE APP", "fix the table borders", "NO MORE PATCHING". */
:root:has(#tokens-fallback){
  /* grid.html:40 declares --topln inline, but the canonical OS-dark block
     out-specifies a plain :root, which would have shifted the topbar hairline
     from .10 to .09 alpha on a dark-OS machine. Mirrored here at the grid's
     own value; both retire at M8. */
  --topln:rgba(255,255,255,.10);
  /* grid.html:4556 renders var(--blue-soft,var(--sel)). --blue-soft was
     undefined on the grid before v2, so that pill has always painted --sel.
     The canonical set now defines --blue-soft, which would have silently
     recoloured it. Pinned back to --sel so the grid is unchanged; it releases
     to the canonical value at M8. */
  --blue-soft:var(--sel);
}

/* dark : the grid's own layer, on html[data-theme] since M8 (12 Aug) : the SAME attribute every
   other surface rides, set explicitly (dark|light) by the grid's own boot/toggle, never left to the
   OS media query. The :has(#tokens-fallback) scope does two load-bearing jobs at once: it keeps this
   block GRID-ONLY (no other surface carries that style id), and at (0,3,0) it outranks both the 3b
   light pins (0,2,0) and the canonical dark blocks, exactly as body.dark's body-level shadowing used
   to. VALUES UNTOUCHED : M8's mechanism half. The palette release (three shared values joining the
   canonical) is the other half and is REVIEW-GATED : a visible change to her biggest surface is hers
   to accept, not a refactor's to slip in. */
/* THE GRID'S DARK LAYER NOW POINTS AT THE CHASSIS INSTEAD OF RESTATING IT.
   Every name the grid's own CSS reads still exists - nothing there has to change - but each one is
   now an ALIAS onto the canonical token rather than a second hex. That is the difference between a
   surface that shares the design system and a surface that owns a copy of it: the copy agrees only
   on the day it is written, and this one had drifted to a different hairline, a different card and a
   different blue. The grid-only names stay because the grid's markup asks for them by name; what
   they no longer do is carry values of their own. */
:root[data-theme="dark"]:has(#tokens-fallback){
  --bg:var(--page);--toolbar:var(--card);--hover:var(--hoverbg);
  --cell:var(--card);--cell-alt:var(--card);--hcell:var(--line2);--rownum:var(--card);
  --sortcol:var(--hoverbg);--cline:var(--line);
  --field-bg:var(--page);
  --pill-bg:var(--pill);--pill-text:var(--pilltx);--chip-bg:var(--blue-soft);--chip-text:var(--blue-tx);
  --groupbar:var(--card);--foot:var(--card);--note:var(--amb-tx);--blue-active:var(--blue-hi);
  /* THE CYCLE THAT KILLED ROW SELECTION IN DARK (theme seat, 02 Sep 2026). Line 346 pins
     `--blue-soft:var(--sel)` and this line said `--sel:var(--blue-soft)`, and this block never
     redeclared --blue-soft. A custom-property cycle is INVALID AT COMPUTED-VALUE TIME, so --sel,
     --blue-soft AND --chip-bg all resolved to the empty string: `background:var(--sel)` came out
     rgba(0,0,0,0) and the selection wash, the blue-soft fill and every chip ground painted NOTHING
     in explicit dark. `var(--blue-soft, var(--sel))` at grid.html:4556 could not save it either,
     because the fallback is the other half of the same cycle.
     Broken by giving the dark grid a LITERAL, so nothing in here points at anything that points
     back. The literal was chosen against the dark card as it stood on 02 Sep, #3b3b3b; her ruling
     of 08 Sep moved that ground to the canvas value #17181b (B7). The wash is a literal, so it did
     not move with it, and it sits on a DARKER ground than it was picked on, not a lighter one. */
  --blue-soft:#2b3a52;
  --sel:#2b3a52;--mask:rgba(0,0,0,.58);
  --head:var(--dim); /* recessed column-header ink : headers are furniture, they recede below data */
}

/* AND THE SAME BLOCK FOR SYSTEM DARK, WHICH IS WHERE MOST PEOPLE ACTUALLY ARE (theme seat,
   02 Sep 2026, measured: on system dark with nothing stamped, TWELVE grid-only tokens kept their
   LIGHT value inside a dark card. The column-header ink measured 1.45:1, --blue-active 1.31:1,
   --note 2.28:1, and --groupbar, --foot, --pill-bg and --chip-bg painted near-WHITE bands inside
   a dark card).
   WHY IT WAS MISSED: the block above is keyed on [data-theme="dark"] ONLY. The chassis palette in
   this same file deliberately spells out all three states, explicit light, explicit dark, and
   system with nothing stamped; this grid layer never got its system twin. The comment above assumes
   the grid stamps the attribute itself, which was true when grid.html booted alone and is false now
   that it is an iframe under assets/theme.js, which stamps NOTHING until someone has toggled once.
   So the card was half-light for every user who had never touched the theme control. */
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]):has(#tokens-fallback){
    --bg:var(--page);--toolbar:var(--card);--hover:var(--hoverbg);
    --cell:var(--card);--cell-alt:var(--card);--hcell:var(--line2);--rownum:var(--card);
    --sortcol:var(--hoverbg);--cline:var(--line);
    --field-bg:var(--page);
    --pill-bg:var(--pill);--pill-text:var(--pilltx);--chip-bg:var(--blue-soft);--chip-text:var(--blue-tx);
    --groupbar:var(--card);--foot:var(--card);--note:var(--amb-tx);--blue-active:var(--blue-hi);
    --blue-soft:#2b3a52;
    --sel:#2b3a52;--mask:rgba(0,0,0,.58);
    --head:var(--dim);
  }
}

/* M3 IS DONE, 2026-08-12 16:xx, in grid.html where it belonged.
   grid.html:9 claimed its inline #tokens-fallback was "byte-identical to this
   file" and three values drifted:
       --blue-active  light : fallback #1F57A8  ->  #1c4a94
       --on-blue      dark  : fallback #0E2138  ->  #0b1d33
       --blue-active  dark  : fallback #9CC0F8  ->  #9ac2f8
   This file is linked AFTER the fallback, so THESE values are what the grid
   actually painted and the fallback only showed on a 404: correcting grid.html
   to match was the zero-visual-change direction, and it is the direction that
   was taken. Editing the three values HERE instead would have repainted a live
   surface. The fallback and this file now agree on all three. */

/* ════════════════════════════════════════════════════════════════════════════
   4. ONE SKIN : THE ALIAS LAYER.

   Celina, 17 Aug: "Can we make 1 universal design skin for every single page
   (VERIFIED) and keep the format the same so it really is one site?"

   MEASURED FIRST, over all 37 .html surfaces, because the shape of the problem
   decided the shape of the fix:
     21 surfaces linked NO token file at all and carried their own :root palette,
        up to 30 private variables each (finance 30, build 27, ask 27, crew-ask
        25, copyright 24, inbox 23, shell 23, crew-travel 23, sync 22 ...)
     13 linked /tokens.css
      3 linked /assets/rollon-tokens.css, a SECOND and largely different
        vocabulary: 72 names here against 42 there, only 20 in common.
   So this was never "one page is missing a stylesheet". More than half the
   estate had its own private palette and there were two competing token files.

   WHY ALIASES RATHER THAN A RENAME SWEEP. 77 distinct names are used by pages
   and not defined here, and almost every one is a token this file ALREADY has
   under a different word: --accent is --blue, --ink is --text, --muted is
   --dim, --green-soft is --grn-bg, --ctl-card is --card. Renaming 77 names
   across 21 files is a large, risky, purely cosmetic edit. Defining them HERE,
   as var() references, makes every page speak the same palette without touching
   a single call site.

   AND var() IS THE POINT, NOT A SHORTCUT: an alias resolves at use time, so it
   inherits light, OS-dark, explicit-dark and explicit-light from the blocks
   above automatically. A copied hex value would need four more copies and would
   drift the first time one of them changed. There are no colour literals below.

   THIS BLOCK CANNOT BREAK A PAGE, which is what makes it safe to ship in one
   go: a page's own :root sits LATER in the document than a linked stylesheet,
   so where a page still defines a name locally, the local value still wins. The
   alias only fills in what a page does NOT define. Removing those local palettes
   is the ratchet that follows, one page at a time, and gate_one_skin.mjs holds
   the count so it can only ever shrink.

   NOT ALIASED, DELIBERATELY: --fkj, --personal, --oneoff (inbox's per-account
   tints), --rc, --rcbg, --pl-* (grid internals), --topbar-h (a length, not a
   colour). Those are one page's private vocabulary and inventing a shared
   meaning for them would be a false claim of sharedness.
   ════════════════════════════════════════════════════════════════════════════ */
:root{
  /* ACCENT : the brand blue under the other name half the estate uses. */
  --accent:var(--blue);
  --accent-hover:var(--blue-hi);
  --accent-active:var(--blue-active,var(--blue-hi));
  --accent-soft:var(--blue-soft);
  --accent-soft-2:var(--blue-soft);
  --accent-quiet:var(--blue-soft);
  --accent-border:var(--blue-line);
  --accent-ring:var(--blue-line);
  --accent-tx:var(--blue-tx);
  --on-accent:var(--on-blue);
  --link:var(--blue);
  --link-hover:var(--blue-hi);

  /* STATUS : one green, one amber, one red, however a page spells them. */
  --green:var(--grn-tx);
  --green-tx:var(--grn-tx);
  --green-d:var(--grn-tx);
  --green-dot:var(--grn-tx);
  --green-soft:var(--grn-bg);
  --green-l:var(--grn-bg);
  --grn-line:var(--grn-bd);
  --grn-soft:var(--grn-bg);

  --amber:var(--amb-tx);
  --amber-tx:var(--amb-tx);
  --amber-soft:var(--amb-bg);
  --amber-band:var(--amb-bg);
  --amb-line:var(--amb-bd);
  --amb-soft:var(--amb-bg);
  --gold:var(--amb-tx);
  --gold-dot:var(--amb-tx);

  --red:var(--err-tx);
  --red-tx:var(--err-tx);
  --red-line:var(--err-line);
  --red-soft:var(--err-bd);
  --bad:var(--err-tx);
  --bad-soft:var(--err-bd);
  --bad-bg:var(--err-bd);
  --ok-bg:var(--ok-soft);
  --warn-bg:var(--warn-soft);
  --warn-line:var(--warn);

  /* NEUTRALS. */
  --ink:var(--text);
  --muted:var(--dim);
  --pagetx:var(--text);
  --cardln:var(--line);
  --panel:var(--card);
  --panel2:var(--cell-alt,var(--line2));
  --track:var(--line);
  --shadow-lg:var(--bulkshadow);

  /* THE ctl- PREFIX, which inbox and ingest carry from an older control bar. */
  --ctl-blue:var(--blue);
  --ctl-blue-soft:var(--blue-soft);
  --ctl-blue-tx:var(--blue-tx);
  --ctl-on-blue:var(--on-blue);
  --ctl-text:var(--text);
  --ctl-dim:var(--dim);
  --ctl-faint:var(--faint);
  --ctl-card:var(--card);
  --ctl-page:var(--card);
  --ctl-line:var(--line);
  --ctl-shadow:var(--shadow);
  --ctl-warn:var(--warn);

  /* TYPE : four names finance and shell use, mapped onto the DP2 scale rather
     than given sizes of their own, so the estate keeps ONE type scale. */
  --fs-display:var(--fs-title);
  --fs-h1:var(--fs-head);
  --fs-h2:var(--fs-section);
  --fs-micro:var(--fs-caption);

  /* --tx : SHARED ASSETS USE THIS NAME AND THE SKIN NEVER DEFINED IT, so every var(--tx,#e9e6df)
     in the estate fell back to a near-white literal in BOTH themes. control-bar.js's views rail is
     the visible case: the selected view name was near-white on a near-white panel. Aliased rather
     than renamed at 20-odd call sites, which is the same reasoning as the rest of the alias layer. */
  --tx:var(--text);

  /* THE ONE MONO STACK, for figures that must align in a column. */
  --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
}


/* ════════════════════════════════════════════════════════════════════════════
   5. THE CONTROL RHYTHM.

   Celina, 17 Aug: "is Search a venue... and the ALL FITER etc buttons in same
   rhythm? and rest of the page? I want slick and the rhythm is what makes it
   slick", then "measure every page and fix the rhythm > thats what makes this
   site luxury."

   MEASURED across 16 live surfaces, every input, select and button rendered in
   the first 1100px: FORTY-THREE distinct control heights.
     18, 19, 21.2, 22, 22.2, 22.3, 23.4, 24, 25, 25.4, 25.8, 26, 27, 27.2, 28,
     29.4, 29.5, 30.2, 30.5, 30.7, 31.2, 31.5, 33.2, 33.4, 34, 35, 36, 36.1,
     36.3, 37, 37.4, 37.5, 38, 38.7, 40, 44, 46, 51, 51.9, 53, 67, 76, 111.7
   /grid.html carries 11 of them, /travel-party 11, /board 6. On /advancing the
   ALL chip is 27.2px and the FILTER button beside it in the SAME BAR is 36px.
   That 9px step, repeated across a row, is what reads as unfinished however
   good the colours are.

   THREE HEIGHTS, AND EVERY CONTROL TAKES ONE. Not a suggestion in a document:
   a token, so a control cannot be built off-rhythm without naming its own
   number, and gate_one_design counts how many distinct heights survive.
     --ctl-h-sm  a chip, a badge, a dense data control
     --ctl-h     the default. Search, filter, sort, every bar button.
     --ctl-h-lg  a primary action that must be findable without reading

   FRACTIONAL HEIGHTS ARE THE TELL. 36.1 beside 36, 27.2 beside 27, 22.3 beside
   22.2: those come from padding plus a line-height plus a border landing on a
   sub-pixel, which is what happens when a control is sized by its text instead
   of BY A HEIGHT. Setting the height explicitly and centring the content with
   inline-flex removes the whole class of them at once.
   ════════════════════════════════════════════════════════════════════════════ */
:root{
  --ctl-h-sm:28px;
  /* --ctl-h WAS DECLARED TWICE IN :root (30 Aug 2026). 36px here, then 18px again further down in
     the block that carries her 21 Aug ruling about the controls sitting flusher on the field
     titles. The second wins, silently, so this line has never painted anything and the estate has
     been rendering at 18px all along. Removed rather than left to read as the default, because a
     value that loses every time is worse than no value: it tells the next person the wrong number.
     RAISED FOR HER: what remains is not a scale. sm is 28px and lg is 44px, both larger than the
     18px default they are meant to bracket. Changing the default resizes every control on every
     surface, so it is hers to rule on, not a thing to correct quietly at speed. */
  --ctl-h-lg:44px;
}

/* ============================================================================================
 * HER LOCKED CHASSIS - 21 Aug 2026. DARK IS EXACT; LIGHT IS ITS MIRROR.
 *
 * Celina: "We need to master that mode I sent you for DARK EXACT then create a light version ie
 * light background etc", and "my filter etc cell text colors or summary text font colors" have no
 * control and look wrong.
 *
 * WHY THE INKS WERE OFF, which is the actual bug behind both notes. She set --text:#ffffff. But the
 * estate does not paint everything with --text: field names, summary labels, row numbers and
 * placeholders use --dim and --faint, and those were still the old warm greys (#b8b3a7, #8f8a7e).
 * So "text is white" was true of cells and false of every secondary label on the same table - which
 * reads exactly as "cell text colors or summary text font colors" being wrong.
 *
 * They are now DERIVED FROM HER INK rather than chosen separately. Set --text and the whole family
 * follows, in both skins, and there is no second ink to keep in sync. The two strengths are hers on
 * the panel (--dima, --fainta) so she can decide how far the secondary text drops back.
 *
 * LIGHT IS THE MIRROR, NOT A SECOND DESIGN. Same accent, same hairline, same geometry, same
 * relationships: her dark puts a LIGHTER card on a darker ground, so light puts a lighter card
 * (#ffffff) on a darker ground (#ece8e0), and the ink flips #ffffff -> #000000.
 * ============================================================================================ */
:root{
  /* --- CHASSIS, SHARED BY BOTH SKINS: one app, two skins --- */
  --btn-h:30px;
  --btn-r:4px;
  /* ONE CORNER, HER B RULING (31 Aug 2026): every fillable box, pill, chip and card shell takes
     the same 4px as --btn-r above. These two were 5px and 17px, so a field and the card it sat on
     disagreed with the button beside them on every surface but finance-v2, which had aliased all
     three onto its own page-local --rad to hide it. The chassis holds one number now. */
  --field-r:4px;
  --card-r:4px;
  --gap:5px;
  --edge:16px;
  /* HER RULING 14 Sep 2026: ONE border weight on every object (pill, chip, button, field box, card,
     picker), thick so the whole card pops. The single tunable source; gate_one_border_weight locks it. */
  --edge-w:2px;
  --row:22px;
  --rollon-strip-h:24px;
  --strip-pad-y:0px;
  --nav-pad-y:0px;
  --nav-item-gap:2px;
  /* FLUSH ON THE FIELD NAMES. 22 Aug: "The grid(column) names boarder is not full, it also feels
     too high." MEASURED: the bar's bottom line sat at y=227 and the header row began at y=235 - an
     8px band in which the COLUMN DIVIDERS DO NOT RUN, so the grid box read as broken and the header
     floated off the bar. Her 21 Aug ruling said the same thing in words: "those controls can sit
     flusher ontop of the field(row) titles". 0 closes it; the slider still moves it. */
  --ctl-bottom:0px;
  --ctl-h:18px;          /* the one that wins, and the one the estate paints */
  /* THE APPROVED CORNER BELONGS TO THE CHASSIS (30 Aug 2026). --rad lived only inside
     finance-v2.html:132, page-scoped, so the reference every surface is held to kept its corner to
     itself and no other page could inherit it. Declared here it is available estate-wide; nothing
     changes for anyone until a surface asks for it, and finance-v2's own copy still wins on
     finance-v2. */
  --rad:4px;
  --ctl-q-pad:23px;
  --ctl-pad-y:13px;
  --view-pad-y:5px;
  --col-w:150px;
  --record-w:600px;
  --sec-gap:12px;
  --dz-pad:28px;
  --linea:100;
  /* THE ROW LINE IS THE SAME LINE (04 Sep 2026). At 25 the line under every field row ran at a
     quarter of the hairline she chose, so one surface carried two hairlines and the fainter one
     was the one under every row. The canvas draws ONE line per theme on rows, header borders,
     field boxes and dividers, so the row line runs at full strength like the rest. */
  --line2a:100;
  --hovera:10;
  --btnhovera:33;
  --dima:72;      /* field names, summary labels: % of her ink */
  /* 56, NOT 48 (job 115, 02 Sep 2026). At 48 this token painted 3.69:1 in light and 3.93:1 in
     dark, failing AA in BOTH themes at every one of its ~295 sites: row numbers, counts,
     placeholders, the review panel headers. It is the one place the two themes agreed, and
     agreed on being wrong. 54 is the minimum that clears 4.5 on both grounds; 56 takes the
     margin (light 4.95:1, dark 4.77:1) and is still the quietest ink the estate has. */
  --fainta:56;    /* row numbers, placeholders */

  /* --- LIGHT (the mirror) --- */
  /* THE ONE HAIRLINE PER THEME (her ruling 04 Sep 2026, ledger row 114, and the canvas).
     MEASURED before this: #c5c0b5 in all four theme states, one cream hairline used on the dark
     ground as well as the light one, which is the opposite direction of the ruling on every row
     she scans. The canvas draws two: #c9c2b3 on light (1,452 uses) and #3a3b40 on dark (1,455).
     Both are now declared, per state, so a theme cannot borrow the other theme's line. */
  /* ONE LINE VALUE IN THIS FILE (B-c4-2, 08 Sep 2026): the four theme blocks near the top used to
     declare --line and --line2 at the retired creams (#e4dfd4, #efeae0) and greys (#2c2d2f, #242527)
     and lose to these blocks by order alone. They declare neither now, so the hairline is said once
     per theme state and a reorder cannot bring the old value back. */
  --blue:#2d6ccb; --on-blue:#ffffff; --ring:rgba(0,0,0,.26); --line:var(--ring); --card-edge:#c9c2b3;
  --page:#0c0c0e; --card:#ffffff; --text:#000000; --page-ink:#e9e6df;
  --blue-hi:#1257c2; --blue-soft:#e8f0fd; --blue-tx:#14539f; --blue-line:#b9d2f5;
  --line2:#d9d4c8; --pill:#e4dfd4; --pilltx:#403c33; --topbar:#0c0c0e;
  --shadow:0 1px 2px rgba(30,26,18,.07),0 2px 10px rgba(30,26,18,.08);
}

/* SPECIFICITY, AND WHY THIS IS SPELLED OUT FOUR TIMES.
 *
 * MEASURED 21 Aug across all three theme states. The derived inks below were written once on a plain
 * :root and never took effect: the imported palette declares --dim inside
 * `@media(prefers-color-scheme:dark){ :root:not([data-theme="light"]) }` and
 * `:root[data-theme="light"]`, both of which OUTRANK a bare :root. So the summary label measured
 * rgb(154,150,140) in dark and rgb(100,95,83) in explicit light - the old warm greys - while the
 * cells beside them were her pure white and black. That is precisely "sum line is too dark still":
 * not a colour choice, a rule that lost.
 *
 * Being last in the file is not enough when the other rule is more specific. So every state she can
 * actually be in is named, at matching specificity, and each one derives its secondary ink from HER
 * --text rather than from a grey nobody chose.
 *
 * The three states: system (nothing stamped, prefers-color-scheme decides), explicit light, explicit
 * dark. Light is declared on bare :root as the default AND on [data-theme="light"] to beat the
 * imported block.
 */
:root,
:root[data-theme="light"]{
  --dim:color-mix(in srgb, var(--text) calc(var(--dima,72) * 1%), transparent);
  --faint:color-mix(in srgb, var(--text) calc(var(--fainta,48) * 1%), transparent);
}
/* --- LIGHT, EXPLICIT: same values as the :root default, at the specificity needed to win --- */
:root[data-theme="light"]{
  --blue:#2d6ccb; --on-blue:#ffffff; --ring:rgba(0,0,0,.26); --line:var(--ring); --card-edge:#c9c2b3;
  --page:#0c0c0e; --card:#ffffff; --text:#000000; --page-ink:#e9e6df;
  --blue-hi:#1257c2; --blue-soft:#e8f0fd; --blue-tx:#14539f; --blue-line:#b9d2f5;
  --line2:#d9d4c8; --pill:#e4dfd4; --pilltx:#403c33; --topbar:#0c0c0e;
}
@media (prefers-color-scheme:dark){ :root:not([data-theme="light"]){
  --dim:color-mix(in srgb, var(--text) calc(var(--dima,72) * 1%), transparent);
  --faint:color-mix(in srgb, var(--text) calc(var(--fainta,48) * 1%), transparent);
}}
:root[data-theme="dark"]{
  --dim:color-mix(in srgb, var(--text) calc(var(--dima,72) * 1%), transparent);
  --faint:color-mix(in srgb, var(--text) calc(var(--fainta,48) * 1%), transparent);
}

/* --- DARK, EXACTLY AS SHE SENT IT --- */
@media (prefers-color-scheme:dark){ :root:not([data-theme="light"]){
  --blue:#2d6ccb; --on-blue:#ffffff; --ring:rgba(255,255,255,.26); --line:var(--ring); --card-edge:#3a3b40;
  --page:#000000; --card:#17181b; --text:#ffffff; --page-ink:var(--text);
  --blue-hi:#2a78e8; --blue-soft:#10203a; --blue-tx:#8fbaf7; --blue-line:#1d3f6e;
  --line2:#5c584f; --pill:#4a4a4a; --pilltx:#ffffff; --topbar:#000000;
  --shadow:0 1px 2px rgba(0,0,0,.5),0 6px 22px rgba(0,0,0,.45);
}}
:root[data-theme="dark"]{
  --blue:#2d6ccb; --on-blue:#ffffff; --ring:rgba(255,255,255,.26); --line:var(--ring); --card-edge:#3a3b40;
  --page:#000000; --card:#17181b; --text:#ffffff; --page-ink:var(--text);
  --blue-hi:#2a78e8; --blue-soft:#10203a; --blue-tx:#8fbaf7; --blue-line:#1d3f6e;
  --line2:#5c584f; --pill:#4a4a4a; --pilltx:#ffffff; --topbar:#000000;
  --shadow:0 1px 2px rgba(0,0,0,.5),0 6px 22px rgba(0,0,0,.45);
}


/* ── INK FOR THE PAGE GROUND ─────────────────────────────────────────────────────────────────────
   Celina, 24 Aug 2026: "can you make the full skin black on light mode. only the table etc needs to
   be light for light mode. The beige is bleeding".

   --page went black in LIGHT, which is what she asked for and gives light the same two-material
   structure dark has: chassis and ground one field, the card floating on it. It also exposed a gap
   the beige had been hiding for as long as light mode has existed. LIGHT MODE HAD ONLY ONE INK.
   --text is near-black because it is written on white cards, and everything sitting DIRECTLY on the
   page ground borrowed it. On beige that was merely low contrast; on black it is invisible.

   Measured after the palette change, in light, at 1440: ten text nodes across /finance-v2, /grid and
   /inbox fell below a 2.5 contrast ratio, every one of them on the new black ground. /home and the
   login door were already clean because they were built for a dark ground.

   So the missing thing has a name now. --page-ink is the ink for anything painted on the page rather
   than on a card: warm off-white in light (the same value the chassis already uses for its own
   labels), and simply --text in dark, where the page ink and the card ink are the same colour
   already. Four states, spelled out above, so it can never resolve to nothing.

   THE RULES BELOW ARE NOT COSMETIC PATCHES. Each names an element that has no ground of its own and
   was leaning on the page being light:
     .gate            the access-denied panel on finance - a centred message with no card behind it
     .pl-drawer/.pl-comp   grid's bottom drawers, measured background rgba(0,0,0,0) - transparent
                      panels, so their ink was reading against whatever the page happened to be
     header.top .cc   the inbox page heading, on the page, using the card's dim ink
     .err .failact    an inbox INVERTED button - ink ground, page-coloured text. Fixed in inbox.html
                      itself with var(--card), because page-ink there would be white on white in dark.
   A panel gets a ground; page-level text gets page ink. Both are the correct fix rather than a
   colour nudge, which is why they live here beside the token instead of in four separate files. */
.gate { color: var(--page-ink); }
.pl-drawer, .pl-comp { background: var(--card); }
header.top .cc { color: var(--page-ink); }

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
 * THE PHONE FLOOR. HER RULING, 27 Aug 2026: "I like the left side one."
 *
 * She was shown both options measured side by side. The store guideline is 12px text and 44px tap
 * targets; the question was whether to raise the floor EVERYWHERE or only below the phone fold.
 * Everywhere took her desktop from 35 records in view to 18 - halving the density of the screen she
 * actually works on, to satisfy a rule that only applies to a phone. She ruled PHONE ONLY.
 *
 * IT LIVES HERE BECAUSE A RULE ONLY SOME SURFACES LOAD IS NOT A RULE. tokens.css is loaded by 30
 * pages including both engines, so the floor arrives everywhere at once instead of being fixed on
 * the surface that was being looked at. Surfaces that hardcode their own heights rather than
 * reading the tokens are unaffected and will pick it up as they convert - that is a partial reach,
 * and it is stated rather than assumed.
 *
 * AND IT SURVIVES A ROTATION (28 Aug 2026). The board: keyed to (max-width: 719px) alone, every
 * iPhone in LANDSCAPE is wider than the fold, so at 780x375 --row dropped back to 22px, --btn-h to
 * 30px, the rail returned and 65 of 65 visible controls fell under 44px. Turning the phone sideways
 * switched the floor off. The second condition is (pointer: coarse) and (max-height: 500px): a
 * touch device shorter than 500px is a phone on its side, and nothing else is - an iPad is 768 tall
 * in landscape and a touch laptop taller still, so neither is dragged into the phone's numbers.
 *
 * NOTHING ABOVE 719px CHANGES on a mouse. Her 22px row, 18px controls and 30px buttons are untouched at every
 * desktop width, and gate_phone_floor asserts that as a CEILING so the floor cannot creep upward.
 * ═══════════════════════════════════════════════════════════════════════════════════════════════ */
@media (max-width: 719px), (pointer: coarse) and (max-height: 500px) {
  :root {
    --row: 44px;
    --btn-h: 44px;
    --ctl-h: 44px;
    /* CAPTION IS A SIZE, NOT A DECORATION. --fs-caption is 10.5px and it is what pills, footers,
       hints and captions read at - "Travel" on a tour day, "PAID" on an invoice, "Writer" on a
       person. crew-ui.css applies it to .pill with !important, so no rule anywhere could beat it
       and the pill stayed at 10.5px on a phone however many selectors were pointed at it. Moving
       the TOKEN is the only fix that reaches through an !important, and it is the honest one: the
       caption size on a phone IS 12px. */
    --fs-caption: 12px;
    /* THE STRIP'S CONTROLS SIZE TO THE STRIP, by an existing rule this must not fight. grid-page.css
       derives the theme toggle and the avatar from --rollon-strip-h minus 6, with !important, so
       hard-coding 44px on those buttons lost silently and left them at 18px in WebKit while every
       other control on the page had come up. Moving the TOKEN moves the band and its controls
       together, which is what that rule was written to guarantee. 50 minus 6 is 44. */
    --rollon-strip-h: 50px;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
 * THE STATUS LADDER. SEVEN STATUSES HAD ONE FILL (B9, 08 Sep 2026).
 *
 * MEASURED on the drive shot s02_grid_boot_light_fin.png, modal colour of a 72 by 14 crop over each
 * STATUS pill: DRAFT, VOID, ISSUED, PAID, SENT, WRITTEN OFF and DISPUTED all painted rgb(228,223,212),
 * 7 of 7 identical, which is --pill in her light skin. The cause was one token for every state, so
 * the STATUS column carried no information the word did not already carry, and an ageing ledger
 * could not be read by scanning down it.
 *
 * MEASURED in design-source/canvas-plus-jakarta-2026-09-04/FinanceV2Light.dc.html: the canvas draws
 * five of the seven and gives them FOUR distinct fills. DRAFT transparent with a 0.40 border, SENT a
 * 0.10 neutral, DISPUTED a 0.24 neutral, PAID #e3edfb with a #b9d0f2 border and #1f4f96 ink, WRITTEN
 * OFF transparent with a fainter border and dimmed ink. Those five are carried verbatim below.
 *
 * HER HUE RULE IS NOT BROKEN. The ladder is monochrome plus the estate's one blue: no green, no red,
 * no amber. Weight carries the meaning, not colour. The steps rise DRAFT, ISSUED, SENT, then land on
 * blue at PAID; the three dead ends sit outside the ladder, DISPUTED heaviest and VOID and WRITTEN
 * OFF as the faintest outline. VOID and WRITTEN OFF share one treatment on purpose: both are ends of
 * the same kind and the word tells her which.
 *
 * THE TWO THE CANVAS DOES NOT DRAW are ISSUED and VOID. ISSUED takes a 0.06 step between the
 * transparent DRAFT and the 0.10 SENT, VOID takes the WRITTEN OFF outline. Both are named as
 * DEFAULTS, not as the canvas.
 *
 * ONE DEVIATION FROM THE CANVAS, DELIBERATE AND FOR AA. The canvas's dead-end ink is
 * rgba(26,30,36,0.50), which paints rgb(141,143,146) on the white row and reads 3.0:1 against it,
 * under the 4.5 floor at 10.5px. It is taken to 0.62, rgb(113,116,119), 4.65:1. The FILLS are the
 * canvas's; only that one ink moves.
 * ═══════════════════════════════════════════════════════════════════════════════════════════════ */
:root,
:root[data-theme="light"]{
  --st-draft-bg:transparent;      --st-draft-bd:rgba(26,30,36,.40);  --st-draft-tx:#1a1e24;
  --st-issued-bg:rgba(28,28,30,.06); --st-issued-bd:rgba(28,28,30,.12); --st-issued-tx:#2e2f32;
  --st-sent-bg:rgba(28,28,30,.10);   --st-sent-bd:rgba(28,28,30,.16);   --st-sent-tx:#2e2f32;
  --st-paid-bg:#e3edfb;           --st-paid-bd:#b9d0f2;              --st-paid-tx:#1f4f96;
  --st-disputed-bg:rgba(28,28,30,.24); --st-disputed-bd:rgba(28,28,30,.32); --st-disputed-tx:#111214;
  --st-dead-bg:transparent;       --st-dead-bd:rgba(26,30,36,.22);   --st-dead-tx:rgba(26,30,36,.62);
}
@media (prefers-color-scheme:dark){ :root:not([data-theme="light"]){
  --st-draft-bg:transparent;      --st-draft-bd:rgba(255,255,255,.40); --st-draft-tx:#ffffff;
  --st-issued-bg:rgba(255,255,255,.06); --st-issued-bd:rgba(255,255,255,.14); --st-issued-tx:#f2f0ec;
  --st-sent-bg:rgba(255,255,255,.12);   --st-sent-bd:rgba(255,255,255,.20);   --st-sent-tx:#f2f0ec;
  --st-paid-bg:#16304f;           --st-paid-bd:#2b5286;               --st-paid-tx:#9cc4f7;
  --st-disputed-bg:rgba(255,255,255,.26); --st-disputed-bd:rgba(255,255,255,.34); --st-disputed-tx:#ffffff;
  --st-dead-bg:transparent;       --st-dead-bd:rgba(255,255,255,.22);  --st-dead-tx:rgba(255,255,255,.55);
}}
:root[data-theme="dark"]{
  --st-draft-bg:transparent;      --st-draft-bd:rgba(255,255,255,.40); --st-draft-tx:#ffffff;
  --st-issued-bg:rgba(255,255,255,.06); --st-issued-bd:rgba(255,255,255,.14); --st-issued-tx:#f2f0ec;
  --st-sent-bg:rgba(255,255,255,.12);   --st-sent-bd:rgba(255,255,255,.20);   --st-sent-tx:#f2f0ec;
  --st-paid-bg:#16304f;           --st-paid-bd:#2b5286;               --st-paid-tx:#9cc4f7;
  --st-disputed-bg:rgba(255,255,255,.26); --st-disputed-bd:rgba(255,255,255,.34); --st-disputed-tx:#ffffff;
  --st-dead-bg:transparent;       --st-dead-bd:rgba(255,255,255,.22);  --st-dead-tx:rgba(255,255,255,.55);
}

/* THE AUTOFILL FACE IS THE FIELD'S OWN, ESTATE-WIDE (her 17 Sep 2026 08:44 shot of /invite: an EMAIL box painted in the browser's
   autofill blue-grey; ledger 497). The 15 Aug note above names the autofill highlight as one of the four native faces this file must own,
   and color-scheme tells the browser the page's scheme but never the field's ground. One rule, every input on every page. */
input:-webkit-autofill,input:-webkit-autofill:hover,input:-webkit-autofill:focus,textarea:-webkit-autofill,select:-webkit-autofill{-webkit-box-shadow:0 0 0 1000px var(--field,var(--field-bg)) inset;box-shadow:0 0 0 1000px var(--field,var(--field-bg)) inset;-webkit-text-fill-color:var(--text);caret-color:var(--text);transition:background-color 99999s ease-out 0s}
