/* 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");

/* 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%}

/* 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;--line:#e4dfd4;--line2:#efeae0;--pill:#ece7db;--pilltx:#5b5648;
  --field:#ffffff;--fieldln:#d8d2c5;
  --blue-line:#b9d2f4;--err:#8a3b2c;--err-line:#d99c8f;
  --text:#1a1e24;--dim:#645f53;--faint:#8b8578;--hoverbg:#f7f4ee;
  --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;--line:#2c2d2f;--line2:#242527;--pill:#26272b;--pilltx:#b9b4a8;
  --field:#101114;--fieldln:#34353a;
  --blue-line:rgba(111,162,240,.34);--err:#e0a496;--err-line:#7a4d44;
  --text:#e9e6df;--dim:#9a968c;--faint:#77746c;--hoverbg:#1d1e21;
  --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;--line:#2c2d2f;--line2:#242527;--pill:#26272b;--pilltx:#b9b4a8;
  --field:#101114;--fieldln:#34353a;
  --blue-line:rgba(111,162,240,.34);--err:#e0a496;--err-line:#7a4d44;
  --text:#e9e6df;--dim:#9a968c;--faint:#77746c;--hoverbg:#1d1e21;
  --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;--line:#e4dfd4;--line2:#efeae0;--pill:#ece7db;--pilltx:#5b5648;
  --field:#ffffff;--fieldln:#d8d2c5;
  --blue-line:#b9d2f4;--err:#8a3b2c;--err-line:#d99c8f;
  --text:#1a1e24;--dim:#645f53;--faint:#8b8578;--hoverbg:#f7f4ee;
  --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").
     Byte-identical to the copies in crew-ui.css:20 and grid.html:40, so
     defining it here changes nothing and lets assets/shell-strip.css and
     assets/components.css read it instead of hardcoding a system stack. */
  --crew-font:"Archivo",-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);
  --sel:var(--blue-soft);--mask:rgba(0,0,0,.58);
  --head:var(--dim); /* recessed column-header ink : headers are furniture, they recede below data */
}

/* 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:36px;
  --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:17px;
  --field-r:5px;
  --card-r:17px;
  --gap:5px;
  --edge:16px;
  --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;
  --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;
  --line2a:25;
  --hovera:10;
  --btnhovera:33;
  --dima:72;      /* field names, summary labels: % of her ink */
  --fainta:48;    /* row numbers, placeholders */

  /* --- LIGHT (the mirror) --- */
  --blue:#1865d8; --on-blue:#ffffff; --line:#c5c0b5;
  --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:#1865d8; --on-blue:#ffffff; --line:#c5c0b5;
  --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:#1865d8; --on-blue:#ffffff; --line:#c5c0b5;
  --page:#000000; --card:#3b3b3b; --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:#1865d8; --on-blue:#ffffff; --line:#c5c0b5;
  --page:#000000; --card:#3b3b3b; --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;
  }
}
