/* ============================================================
   Termdeck - Color tokens

   THE NEUTRAL SPINE IS LINEAR'S, COPIED VALUE FOR VALUE.
   Surfaces, borders, text tiers, wells and the accent all come from the
   [data-theme=dark] block of
     https://static.linear.app/web/_next/static/css/index.CtQdVDoA.css
   fetched 2026-08-19. Every value below carries the Linear token it came from
   in a trailing comment, so the next session reads the mapping instead of
   re-deriving one by eye. features/20-palette-swap-freeze.md is why: the
   palette is copied rather than adapted, because a system that gets adapted
   gets re-adapted, and that loop is what this file was changed to end.

   THE SEMANTIC LAYER IS NOT LINEAR'S AND DID NOT MOVE.
   live / primary / danger / tool / info / cat-* / diff-* / ansi-* name a
   state rather than decorate a surface (DESIGN.md, Color-Is-Semantic), they
   are measured against these surfaces by tests/contrast-tokens.mjs, and
   Linear's own green (#27a644) cannot carry the near-black label a filled
   primary button needs. Borrowing them would have been a second design, not
   a closer copy.

   WHERE THE REFERENCE LOSES, IT LOSES TO A FLOOR AND ONLY TO A FLOOR.
   Linear's text-quaternary #62666d measures 2.72:1 on --workspace-surface-raised,
   under the 4.5:1 text floor, so it is not a text tier here. It was the control
   edge for a day, and then the card lift (2026-08-19) raised the surface ladder
   a rung and took it to 2.95:1 on --workspace-surface, under the 3:1 that WCAG
   2.2 SC 1.4.11 asks of a control's boundary. So it lost that job too and is now
   only a categorical data step. The control edge is Linear's text-tertiary
   #8a8f98 instead, at 5.23:1. Linear's own control edge, border-tertiary
   #3e3e44, was never in the running at 1.72:1.

   THE SURFACE LADDER SITS ON LINEAR'S CARD RUNGS, NOT THEIR TINT RUNGS.
   surface / raised / hover are bg-secondary / bg-tertiary / bg-quaternary, which
   is what Linear's own application chrome uses. The first pass took level-2 /
   bg-secondary / bg-tertiary, one rung lower, and a card landed 12 eight-bit
   steps off the canvas: technically a lift, perceptually nothing. It is 20 steps
   now. See the elevation ladder below for why that step, and not a shadow, is
   what carries depth here.

   The ember / gold / rose ramp below is the first-generation warm palette.
   Nothing paints from it directly any more; it survives only because the
   dashboard remap further down re-points those names at the neutral ramp for
   component CSS that still spells them.
   ============================================================ */

:root {
  /* --- Brand: Ember (primary, warm orange) ------------------ */
  --ember-50:  #fdf3ec;
  --ember-100: #fbe2d0;
  --ember-200: #f6c39c;
  --ember-300: #f0a068;
  --ember-400: #ea7f3c;
  --ember-500: #e2611b; /* primary */
  --ember-600: #c64f14;
  --ember-700: #a23e14;
  --ember-800: #813317;
  --ember-900: #692b17;
  --ember-950: #3a1408;

  /* --- Accent: Gold (gradient head / highlights) ------------ */
  --gold-100: #fdefcb;
  --gold-300: #f6cd63;
  --gold-400: #efb42d;
  --gold-500: #d9970a;

  /* --- Accent: Rose (gradient tail / secondary emphasis) ---- */
  --rose-100: #fbdde6;
  --rose-300: #f19bb5;
  --rose-400: #e86d92;
  --rose-500: #d94472;
  --rose-600: #b83160;

  /* --- Semantic: Red (error/danger) -------------------------- */
  --red-100:   #fbdcd6;
  --red-500:   #dc4a37;
  --red-600:   #b8382b;

  /* The warm-sunset --gradient-brand / --gradient-brand-soft that used to sit
     here were first-generation (ember) values, redefined further down this same
     file and therefore never rendered — dead code carrying five colours that are
     not in DESIGN.md. The live definitions are in the dashboard remap below. */
}

/* ============================================================
   Termdeck — Dashboard theme remap
   Black and white carry the interface; brand green is reserved
   for identity, primary actions, focus, live state, and selection.
   Legacy ember/gold/rose token names remain mapped for older
   component CSS until those selectors are fully retired.
   ============================================================ */

/* The second selector paints any element in the DEFAULT palette no matter which
   theme the document is on — the theme picker's preview tiles, which have to
   show Ink while you are looking at them from Slate.
   Two scopes, ONE block, on purpose: a preview built from its own copy of the
   values is a picture of a palette that used to be true, and the first token
   anyone changes here is the one it stops advertising honestly. Inherited values
   always lose to a declaration on the element itself, so no specificity fight —
   see tokens/themes.css for the same pairing on every other theme. */
:root,
[data-theme-preview="ink"] {
  --brand-green-50:  #efffed;
  --brand-green-100: #dcffd8;
  --brand-green-200: #bdf8b5;
  --brand-green-300: #94f087;
  --brand-green-400: #6de95c;
  --brand-green-500: #4fd63d;
  --brand-green-600: #37b828;
  --brand-green-700: #2a8f20;
  --brand-green-800: #226f1d;
  --brand-green-900: #1c5a19;
  --brand-green-950: #0d2f0b;

  --ink-0:   #ffffff;
  --ink-50:  #f7f7f7;
  --ink-100: #eeeeee;
  --ink-200: #d7d7d7;
  --ink-300: #b6b6b6;
  --ink-400: #8c8c8c;
  --ink-500: #6f6f6f;
  --ink-600: #545454;
  --ink-700: #333333;
  --ink-800: #1c1c1c;
  --ink-850: #151515;
  --ink-900: #0b0b0b;
  --ink-950: #000000;

  /* V2 (agent-ai theme): the "accent" is WHITE, not brand-green. The old
     ember->brand-green remap made every primary (send, allow, toggles, focus)
     green; point it at the white/ink ramp instead. Filled primaries pair
     ember-500 white with dark text — see .send-btn / .perm-btn.allow / .app-tile
     glyph fixes in style.css. --live stays green (semantic), it is separate. */
  --ember-50:  var(--ink-800);
  --ember-100: var(--ink-700);
  --ember-200: var(--ink-500);
  --ember-300: var(--ink-200);
  --ember-400: #ffffff;
  --ember-500: #e5e5e6;   /* tracks --workspace-accent */
  --ember-600: var(--ink-200);
  --ember-700: var(--ink-300);
  --ember-800: var(--ink-400);
  --ember-900: var(--ink-500);
  --ember-950: var(--ink-900);

  --gold-100: var(--ink-100);
  --gold-300: var(--ink-200);
  --gold-400: var(--ink-300);
  --gold-500: var(--ink-400);
  --rose-100: var(--ink-100);
  --rose-300: var(--ink-300);
  --rose-400: var(--ink-400);
  --rose-500: var(--ink-500);
  --rose-600: var(--ink-600);

  --gradient-brand: linear-gradient(135deg, #6de95c 0%, #ffffff 100%);
  --gradient-brand-soft: linear-gradient(135deg, rgba(109, 233, 92, 0.16), rgba(255, 255, 255, 0.04));

  /* --- Product workspace ------------------------------------
     The surface ladder, Linear's. Near-black canvas, four rungs above it,
     each one a named Linear level rather than a grey somebody picked. The
     relative steps are the ones the app already had (canvas < overlay <
     surface < raised < hover), so every lift, well and hover state still
     means what it meant before; only the values moved.

     Near-white is still the only "accent" here (focus, selection, identity)
     and colour is still semantic only: green = live, amber = a turn in
     flight, red = needs you, and the categorical steps below are for data.
     Linear's indigo (#5e6ad2 / #7170ff) is deliberately NOT imported: in
     Linear the indigo IS the primary, and dropping it beside a green primary
     would build a two-accent system neither palette has. */
  --workspace-bg: #08090a;              /* linear --color-bg-primary */
  --workspace-surface: #1c1c1f;         /* linear --color-bg-secondary */
  --workspace-surface-raised: #232326;  /* linear --color-bg-tertiary */
  --workspace-surface-hover: #28282c;   /* linear --color-bg-quaternary */
  /* Linear ships both an opaque border ramp and a translucent one. This is the
     translucent member on purpose: white-alpha holds its weight against any
     dark base, which is what lets tokens/themes.css leave it alone rather than
     re-deriving a hairline per theme.

     0.14, not Linear's 0.08. This is the one value in the neutral spine that is
     ours, and it is here because Linear's number is calibrated for a card that
     is already separating on its own. The Flat-Console lift (2026-08-19) needed
     the grouping hairline to carry more of the edge. Over a card, 0.08 composites
     to #2e2e31 and measures 1.26:1 against the card it bounds; 0.14 composites to
     #3c3c3e and measures 1.54:1. Neither is a contrast floor (a grouping border
     has none, which is the whole point of the Two-Border Rule); this is the one
     value here chosen by eye, and it is flagged as such rather than dressed up
     as a measurement. */
  --workspace-border: rgba(255, 255, 255, 0.14);
  --workspace-border-strong: #34343a;   /* linear --color-border-secondary */

  /* Navigation well — the sidebar and the files dock, the two surfaces that sit
     BELOW the canvas rather than on it. They were four literals (#000, #141414,
     #1e1e1e, #262626) declared twice over, once as .files-panel's --fp-* and
     once as .workspace-app .sidebar's --sidebar-ref-*, with the same values in
     both places and nothing keeping them in step. One set, named for the job.

     This is also the only place the product paints pure black, which is what a
     theme has to be able to reach — see tokens/themes.css. */
  --workspace-nav-bg: #010102;          /* linear --color-bg-marketing, their deepest */
  --workspace-nav-hover: #0f1011;       /* linear --color-bg-panel */
  --workspace-nav-selected: #1c1c1f;    /* linear --color-bg-secondary */
  --workspace-nav-border: #23252a;      /* linear --color-border-primary */
  /* The three text tiers are Linear's top three, in order. Their fourth
     (--color-text-quaternary #62666d) is not one: it lands at 2.95:1 on
     --workspace-surface-raised where the floor is 4.5:1, and 64 rules in
     style.css set the faint tier at 12px or smaller. It went to the control
     edge instead, further down, where the floor it has to clear is 3:1.
     Worst ratio across bg / surface / raised, measured by
     tests/contrast-tokens.mjs on every theme: 15.98 / 11.64 / 5.23. */
  --workspace-text: #f7f8f8;            /* linear --color-text-primary */
  --workspace-muted: #d0d6e0;           /* linear --color-text-secondary */
  --workspace-faint: #8a8f98;           /* linear --color-text-tertiary */
  /* Linear's white-button fill, which is the job this token actually does:
     a filled near-white control, one step under body text so a toggle does
     not out-shout the sentence next to it. */
  --workspace-accent: #e5e5e6;          /* linear --color-button-invert-bg */
  --workspace-accent-rgb: 229, 229, 230;
  --workspace-live: #0ac864;   /* streaming / attached — stays green, not accent */
  --workspace-danger: #ff6568;
  /* Danger's hover pair. It already shipped — six declarations spelled #ff8083
     by hand across the armed Allow, the recommended Deny and Stop — so this
     names the value rather than inventing one. Danger needs a hover the way
     --workspace-primary does: a destructive button that does not answer the
     pointer reads as disabled, and reusing the base for both states is how a
     button ends up looking dead. 6.9:1 on --workspace-primary-fg. */
  --workspace-danger-hover: #ff8083;
  /* Categorical series. Three steps of the same neutral ramp, kept far enough
     apart to read as three bars, plus the app's one amber. The names are
     hues that these values have not been for several palettes; they stay
     because ~20 rules and admin.css spell them, and renaming them is not what
     a palette swap is. */
  --workspace-data-peach: #fcbb00;
  --workspace-data-sky: #8a8f98;        /* linear --color-text-tertiary */
  --workspace-data-violet: #d0d6e0;     /* linear --color-text-secondary */
  --workspace-data-yellow: #62666d;     /* linear --color-text-quaternary */

  /* The Usage page runs deliberately quieter than the rest of the app: a grey
     accent rather than the near-white one, so a wall of numbers does not read
     as a wall of primary actions.
     The -rgb member used to disagree with its own hex (174 against 200), kept
     verbatim through earlier passes because nothing was touching the palette.
     This pass is touching the palette, so the two now agree; the tints built
     from the -rgb value lighten very slightly as a result. */
  --workspace-data-accent: #d0d6e0;     /* linear --color-text-secondary */
  --workspace-data-accent-rgb: 208, 214, 224;
  --workspace-data-accent-dim: #3e3e44; /* linear --color-border-tertiary */

  /* The one well in the product that sinks BELOW the canvas instead of lifting
     off it: the Usage hero. It straddles the canvas rather than sitting wholly
     under it, so -from is one rung ABOVE --workspace-bg and -to is Linear's
     deepest value. On a theme whose base is not near-black both have to move
     with the base, or the hero stays a black rectangle in a lighter page. */
  --workspace-well-from: #0f1011;       /* linear --color-bg-panel */
  --workspace-well-to: #010102;         /* linear --color-bg-marketing */

  /* Diff — the one surface where two hues sit side by side. Both name a git
     state, so they pass the color-is-semantic rule rather than decorating.
     The base pair is DESIGN.md's diff-add / diff-remove, used at marker and
     gutter strength (3:1 floor). The -fg pair is lightened for 12px mono code
     text, which needs 4.5:1 on near-black. Text takes -fg, fills take base. */
  --workspace-diff-add: #0ac864;
  --workspace-diff-remove: #e60a64;
  --workspace-diff-modify: #fcbb00;
  --workspace-diff-add-fg: #6fe4a4;    /* 12.6:1 on #08090a */
  --workspace-diff-remove-fg: #ff8ab4; /*  9.1:1 on #08090a */

  /* Transcript: a tool call is the one thing in a turn that is neither prose
     nor a state, so it gets its own hue and nothing else in the app uses it.
     Result status reuses the existing live/danger pair. */
  --workspace-tool: #60a5fa;           /*  7.8:1 on #08090a */

  /* --- Terminal output (ANSI SGR) ----------------------------------------
     The eight ANSI foregrounds, for a tool result rendered with the colour its
     command actually printed (public/js/ansi.js). NOT eight new values: every
     one is a colour DESIGN.md already lists, chosen by what the hue MEANS in
     terminal output rather than by matching a terminal's palette.

     Red, green and amber are the state trio and they need no argument here —
     that IS what a test runner prints them for, so they take the tokens that
     already mean error / added / changed. Blue and purple are borrowed from the
     tool and decision hues, which is safe precisely because this text sits
     inside a payload panel where neither of those meanings is in play. Cyan is
     the only one with no existing token, and #22d3ee is the tool-verb ramp's
     own cyan (DESIGN.md § the tool-verb ramp) rather than a new pick.

     Black and white are the two that must not be taken literally: true black on
     a near-black card is invisible, and terminal "white" is body text. Both
     resolve to the greyscale tier that carries the same weight in this app.

     All eight are TEXT, so all eight clear the Two-Strength Rule's 4.5:1 on
     --workspace-bg; the -fg members of their pairs are chosen for that reason
     (diff-add-fg, not diff-add). Backgrounds are deliberately absent — see
     public/js/ansi.js for what it declines to render and why. */
  --ansi-black: var(--workspace-faint);          /* #8b8b8b — "dim", not invisible */
  --ansi-red: var(--workspace-danger);           /* #ff6568 — a failing test IS an error */
  --ansi-green: var(--workspace-diff-add-fg);    /* #6fe4a4 — 12.5:1, the text-weight green */
  --ansi-yellow: var(--workspace-diff-modify);   /* #fcbb00 — the app's one amber */
  --ansi-blue: var(--workspace-tool);            /* #60a5fa */
  --ansi-magenta: var(--workspace-cat-purple);   /* #c084fc — 7.5:1 */
  --ansi-cyan: #22d3ee;                          /* the tool-verb ramp's cyan */
  --ansi-white: var(--workspace-text);           /* #fafafa — terminal white is body text */

  /* --- Info -------------------------------------------------------------
     The fourth semantic, and the one the palette was actually missing. green
     live / amber working / red needs-you all mean something HAPPENED; nothing
     carried "here is a fact you did not ask for" — a changelog note, a plan
     tier callout, a badge on a nav item. Those were borrowing --workspace-tool,
     which already means "a tool call" in a transcript two panes away.

     The hue is Webflow's info blue (#146ef5) lifted for a near-black canvas.
     #146ef5 itself measures 4.32:1 on --workspace-bg and 3.59:1 on
     --workspace-surface-raised: under the 4.5 text floor on every app surface,
     because it was drawn for a white page. #3d86f7 is the same blue carried up
     until it clears the floor on the worst surface we put text on.

     It is deliberately NOT --workspace-tool's #60a5fa: two blues, two jobs, and
     the info blue is the deeper, more saturated one so a filled info badge does
     not read as a tool call. */
  --workspace-info: #3d86f7;
  --workspace-info-fg: #08090a;        /* filled badge text, on the info fill */
  --workspace-info-soft: rgba(61, 134, 247, 0.14);  /* tinted badge ground */
  --workspace-info-border: rgba(61, 134, 247, 0.36);
  /* The soft badge sets its glyph on its OWN tint, not on the raw surface, and
     that tint lightens the ground it has to beat: #3d86f7 on the tinted raised
     surface measures 3.93:1, under the floor, even though the same blue clears
     4.7:1 on raised bare. Dropping the tint does not rescue it (4.14:1 at 10%)
     — the glyph is what has to lift. The fill and border stay #3d86f7, so a
     FILLED info badge keeps the deeper, more saturated blue that tells it apart
     from a tool call. Measured against the TINTED surface, per the Two-Strength
     Rule, and against EVERY theme: slate's surfaces are lighter than ink's, so
     its tinted grounds are lighter still and it — not the near-black default —
     is the binding constraint. #5b9bfa passes on ink (4.96:1 worst) and fails
     on slate (3.98:1). #7ab3fb clears both with room: 5.13:1 on the worst
     ground either theme presents. */
  --workspace-info-text: #7ab3fb;      /*  worst 5.13:1 on a tinted slate raised */

  /* Decision categories. A pending plan and a pending question are both "we
     stopped and need you", so they need to be told apart at a glance without
     reading. Same value as --workspace-tool for blue, kept separate because the
     names mean different things and will drift apart if the palette moves. */
  --workspace-cat-blue: #60a5fa;       /*  7.8:1 on #08090a — plan ready */
  --workspace-cat-purple: #c084fc;     /*  7.5:1 on #08090a — question */

  /* Primary action. The same green as --workspace-live on purpose: the app has
     exactly one "go" colour, and a filled button never appears where a live dot
     does, so sharing the hue reads as one idea rather than two meanings. */
  --workspace-primary: #0ac864;
  --workspace-primary-hover: #2ee07d;
  /* Named for the primary because that is its measured pairing — 8.9:1 on the
     fill, 11.4:1 on hover — but the job is broader: this is the ink that goes
     on ANY light or saturated fill. The accent-white avatars and toggles, the
     danger-red chips, the gradient tiles and a highlighted syntax token were
     all spelling it by hand (#0a0a0a, #000, #140f09, #15120c, #06210a,
     #1a1a1a — six literals for one idea, three of them left over from the warm
     charcoal palette), which is precisely the set that cannot follow a theme. */
  --workspace-primary-fg: #08090a;

  /* Scrollbar chrome — quiet neutral thumb, white only while dragging */
  --workspace-scrollbar-thumb: rgba(255, 255, 255, 0.14);
  --workspace-scrollbar-thumb-hover: rgba(255, 255, 255, 0.28);
  --workspace-scrollbar-thumb-active: #e5e5e6;

  /* --- Edge highlight -----------------------------------------
     A raised bevel, not a single line: a crisp inset top highlight where
     the light lands, plus a soft inset shadow gathered low on the card
     where it falls off — the two paired reads as a machined panel's
     edge without casting the drop shadow the Flat-Console Rule forbids
     on resting surfaces (see DESIGN.md's Elevation & Depth). The first
     version was the top highlight alone, hand-written once on the
     composer and nowhere else, which is why it barely read next to a
     flat `box-shadow: none` session tile or settings card. The bottom
     shadow's blur/spread is tuned tight (not a tall vignette) so it
     still reads as an edge accent, not a fill, on a ~70px board card —
     the shortest surface this token has to cover. One token, one
     focus-brightened sibling, so every flat card/composer/popover/modal
     in the app carries the same edge instead of each one reinventing
     (or skipping) it. Popovers and modals already carry a real cast
     shadow — they're the two surfaces Flat-Console exempts because they
     float rather than rest — so they LAYER the bevel onto that shadow
     below rather than using this token alone. */
  --workspace-edge-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.10), inset 0 -6px 8px -6px rgba(0, 0, 0, 0.45);
  --workspace-edge-highlight-focus: inset 0 1px 0 rgba(255, 255, 255, 0.13), inset 0 -6px 8px -6px rgba(0, 0, 0, 0.45);

  /* --- Elevation ladder --------------------------------------------------
     Five named levels, so "how raised is this" stops being decided per rule.

     These are NOT the layered drop-shadows they were modelled on. That recipe
     is five stops of rgba(0,0,0,0.01–0.09) — black at up to nine percent, which
     is how you lift a card off a WHITE page. Composited onto #08090a it is
     black on black: the measurable delta is under one 8-bit step for the top
     three stops, so the whole stack costs five composites and shows nothing.

     On a near-black canvas the two things that actually read are a LIGHTNESS
     STEP (the surface ladder: bg -> surface -> raised -> hover) and light
     catching a top edge. That is not a preference, it is arithmetic: the canvas
     is rgb(8, 9, 10), so the darkest a shadow can paint the pixel beside a card
     is eight steps down, and most of that is spent before the blur starts.
     Measured 2026-08-19 by rendering the board four ways: bevel only, Linear's
     --shadow-low, black at 0.6 over 12px, and black at 0.9 over 30px. The last
     is a 9x heavier shadow than the first and the screenshots are impossible to
     tell apart.

     The Flat-Console Rule was lifted for cards on 2026-08-19 at the user's
     request, and --elevation-2 carries a real cast shadow now. Keep the
     expectations right, though: on Ink that shadow is nearly free and nearly
     invisible, and the reason a card reads as raised here is the rung under it,
     not the shadow over it. It earns its keep on themes whose canvas is not
     near-black. Slate's is rgb(24, 27, 33), where the same layers have room to
     bite.

     The two floating levels' cast layers are Linear's --shadow-medium /
     --shadow-high over their --shadow-low, rather than the taller, heavier
     stack that was here. A 40-60px black plume was drawn for a page that lifts
     things off white; on this canvas it reads as a smudge rather than a height. */
  --elevation-0: none;                                   /* flat band, no border */
  --elevation-1: none;                                   /* + 1px hairline border */
  /* Resting card / control. Linear's --shadow-low, doubled: one tight contact
     layer that grounds the bottom edge and one wide, negative-spread pool that
     only exists off the corners. Plus the bevel, which is still the layer doing
     most of the work. */
  --elevation-2: 0 1px 2px rgba(0, 0, 0, 0.9),
                 0 8px 24px -8px rgba(0, 0, 0, 0.9),
                 var(--workspace-edge-highlight);
  --elevation-3: 0 4px 24px rgba(0, 0, 0, 0.2), 0 2px 4px rgba(0, 0, 0, 0.1), var(--workspace-edge-highlight);
  --elevation-4: 0 7px 32px rgba(0, 0, 0, 0.35), 0 2px 4px rgba(0, 0, 0, 0.1), var(--workspace-edge-highlight);
  /* The surface each level is meant to sit on, so a component picks one pair
     rather than a shadow and a background that disagree. */
  --elevation-2-bg: var(--workspace-surface);
  --elevation-3-bg: var(--workspace-popover-bg, #1c1c1f);
  --elevation-4-bg: var(--workspace-overlay-bg, #0f1011);

  /* --- Popover -----------------------------------------------
     Surfaces that float above the workspace rather than reflow it
     (the context/token breakdown, and menus that follow). They annotate
     what is behind them, so they lift by one step off the CANVAS —
     --workspace-surface, the same rung a card sits on — and carry their
     own border/shadow rather than borrowing the flat chrome's.

     (This comment used to claim a step brighter than
     --workspace-surface-raised. The value never was: it is the same rung as
     --workspace-surface. A menu reads as lifted against the near-black page it
     covers, which is what the value actually delivers, so the sentence
     was what needed correcting.) */
  --workspace-popover-bg: #1c1c1f;     /* linear --color-bg-secondary, = --workspace-surface */
  --workspace-popover-border: rgba(255, 255, 255, 0.15);
  --workspace-popover-shadow: var(--elevation-3);
  --workspace-popover-radius: var(--radius-lg);  /* 12 - a popover is a card, and linear's card is 12 (was 8) */
  --workspace-popover-z: 60;

  /* --- Overlay -----------------------------------------------
     Modal surfaces. Unlike a popover they take the whole attention
     rather than annotating something behind them, so they get their own
     rung and one shared scrim for the app underneath.

     The rung sits BELOW --workspace-surface on purpose. A modal is a
     frame that holds cards, and a frame level with its own contents has
     no hierarchy left to give them:

       --workspace-bg              #08090a   the app
       --workspace-overlay-bg      #0f1011   a modal, lifted off it
       --workspace-surface         #141516   a card inside the modal
       --workspace-surface-raised  #1c1c1f   a control on that card

     Neutral at every rung, which is the other half of the job: the
     literals this replaced (#111112, #0d0d0e) were faintly blue in a
     ramp that is grey the whole way up. */
  --workspace-overlay-bg: #0f1011;     /* linear --color-bg-panel / --color-bg-level-1 */
  --workspace-overlay-border: rgba(255, 255, 255, 0.12);
  --workspace-overlay-shadow: var(--elevation-4);
  --workspace-overlay-radius: var(--radius-xl);
  /* linear --color-overlay-primary (#000000d9). Neutral, and heavier than the
     0.72 that was here, which is what lets the softer cast shadows above still
     read as height: the app behind a modal goes further down. */
  --workspace-scrim: rgba(0, 0, 0, 0.85);
  --workspace-scrim-blur: 6px;

  /* Focus. White, because the palette above says so — "primary actions,
     focus, selection, identity" — and two rings rather than one wash.
     A single translucent ring measured 1.25-1.63:1 against the surfaces
     it bordered where WCAG 2.2 SC 1.4.11 wants 3:1, and a solid one
     abutting a green toggle only reaches 1.9:1. The dark inner ring
     gives the light outer one a neighbour it always beats, whatever the
     control underneath is painted.

     Built from other tokens, which makes it the one kind that does NOT follow
     a re-scoped palette: a custom property assembled from var()s computes where
     it is DECLARED, so this resolves here, against this palette, and inherits
     as the finished string "0 0 0 2px #08090a, 0 0 0 4px #e5e5e6". Re-skinning
     a subtree underneath it changes nothing — measured at 1.17:1 on a light
     surface that had re-declared every colour under it. Anything scoping a new
     palette has to re-declare these two by hand. */
  --workspace-focus-ring: 0 0 0 2px var(--workspace-bg), 0 0 0 4px var(--workspace-accent);
  --workspace-focus-ring-inset: inset 0 0 0 2px var(--workspace-bg), inset 0 0 0 4px var(--workspace-accent);

  /* The edge of a CONTROL, which is not the same job as --workspace-border.
     A card's outline groups things that are already legible without it; a text
     field, a select and a bare-fill button are identified by their boundary and
     nothing else, and WCAG 2.2 SC 1.4.11 asks 3:1 of exactly that. The shared
     hairline measured 1.25-1.32:1 against both the control's fill and the
     surface behind it, so every input in the product was a rectangle you had to
     already know was there.

     This is Linear's --color-text-tertiary, which is also --workspace-faint.
     One value, two jobs, and they do not collide: a control edge is measured
     against a 3:1 floor and a text tier against 4.5:1, so the tier is the
     binding one and the edge inherits its headroom. 5.23:1 on
     --workspace-surface, 5.86:1 on --workspace-overlay-bg, 6.13:1 on
     --workspace-bg.

     It was --color-text-quaternary #62666d until the card lift (2026-08-19)
     raised the surface ladder a rung, which took that value to 2.95:1 on
     --workspace-surface and under the floor. Worth knowing before touching the
     ladder again: raising a surface costs contrast at BOTH floors at once, and
     the text floor is now the tighter of the two (faint clears it by 0.32).
     Controls get this; grouping keeps the quiet hairline, which is the
     distinction the criterion actually draws. */
  --workspace-control-border: #8a8f98;
}
