/*
 * TeamNest — Design Tokens
 * ==================================================================
 * THE source of truth for the visual language. Every surface consumes
 * these; nothing hardcodes a value. See ADR 0063.
 *
 * The rule everything else depends on: components consume tokens, never
 * literal values. No hex, no px, no hardcoded font size in a template or
 * a page stylesheet — var(--tn-…) or nothing. That is what makes a
 * re-skin a one-file change instead of a migration.
 *
 * Theming: light is the default (:root). Dark is opt-in via
 * [data-theme="dark"] on <html>, stamped before first paint by the
 * inline script in base.html.
 *
 * There is a second axis: skin, via [data-skin="nest"] on <html>, also
 * stamped before first paint. It remaps existing roles and declares no new
 * name, which is what keeps all four generated palettes the same shape.
 *
 * ORDER MATTERS in two places, and they pull in opposite directions:
 *
 *   1. The alias layers near the bottom sit on `:root, [data-theme="dark"]`
 *      and hold only var() references, so they re-derive in dark
 *      automatically. Never put a --tn-* colour definition in those blocks;
 *      it would outrank the dark block above them.
 *
 *   2. The skin blocks sit at the very END, AFTER those alias layers. They
 *      have to: [data-skin="nest"] is (0,1,0), the same weight as :root, and
 *      the back-compat layer declares --tn-radius-lg as a LITERAL on :root.
 *      A skin block placed above it loses that one declaration while still
 *      winning --tn-radius-md -- because md is a var() alias, substituted at
 *      computed-value time and so order-independent. The result is an 18px
 *      card with a 16px "large" corner, and nothing reports it.
 */

:root {
  /* ================================================================
     RAMPS — two, and only two. Everything else is a ROLE pointing at a
     step on one of them, which is what lets dark mode flip roles
     without redefining a single hue.
     Consume roles, never ramp steps (adoption rule 4).
     ================================================================ */

  /* --- Brand ----------------------------------------------------
     600 is the brand: #4f46e5 at 6.29:1 on white. Pinned across the web
     app, the marketing site and the mobile app by
     core/navigation/tests/test_components.py — change it in all three
     or not at all. */
  --tn-primary-50:  #eef2ff;
  --tn-primary-100: #e0e7ff;
  --tn-primary-200: #c7d2fe;
  --tn-primary-300: #a5b4fc;
  --tn-primary-400: #818cf8;
  --tn-primary-500: #6366f1;
  --tn-primary-600: #4f46e5;
  --tn-primary-700: #4338ca;
  --tn-primary-800: #3730a3;
  --tn-primary-900: #312e81;
  --tn-primary: var(--tn-primary-600);
  --tn-primary-contrast: #ffffff;
  /* Channels, so a translucent wash of the brand (the focus ring, the active
     rail row) can be built without restating the hex. core/branding emits this
     alongside --tn-primary when a tenant repaints, which is what stops a
     repainted workspace showing an indigo focus ring around a green button. */
  --tn-primary-rgb: 79, 70, 229;

  /* --- Neutral --------------------------------------------------
     Cooled slightly toward the brand rather than a pure grey, and
     deliberately re-spaced at the light end: the three lightest steps
     have to be tellable apart, because the chrome uses them to separate
     the rail from the canvas from a card. */
  --tn-neutral-0:   #ffffff;
  --tn-neutral-50:  #f7f8fa;
  --tn-neutral-100: #f1f2f6;
  --tn-neutral-200: #e6e8ee;
  --tn-neutral-300: #cfd3dc;
  --tn-neutral-400: #98a0b0;
  /* One step darker than the #667085 the design document prints. That value
     measures 5.0:1 on the surface as published, but --tn-text-muted points at
     it and muted text also renders on --tn-surface-alt (hover rows, wells),
     where the same hex is 4.45:1 -- just under AA. The document states the
     >=4.5 principle and publishes the on-white figure; only the combination
     falls short, which is why nobody caught it by reading. #656f84 is 5.05:1
     on the surface (still the published 5.0) and 4.51:1 on the well plane. */
  --tn-neutral-500: #656f84;
  --tn-neutral-600: #4b5565;
  --tn-neutral-700: #344054;
  --tn-neutral-800: #1d2433;
  --tn-neutral-900: #101828;
  --tn-neutral-950: #060a13;

  /* ================================================================
     SEMANTIC ROLES — what components actually reference. A component
     that says var(--tn-surface) keeps working when dark mode repoints
     it.
     ================================================================ */

  /* Three planes, and only three: the canvas a page sits on, the
     surface a card raises off it, and a recessed plane for table heads
     and hover rows. More planes than that and elevation stops meaning
     anything. */
  --tn-bg:            var(--tn-neutral-50);
  --tn-surface:       var(--tn-neutral-0);
  --tn-surface-alt:   var(--tn-neutral-100);
  --tn-surface-sunk:  var(--tn-neutral-50);

  --tn-border:        var(--tn-neutral-200);
  --tn-border-subtle: var(--tn-neutral-100);
  --tn-border-strong: var(--tn-neutral-300);

  /* Measured on --tn-surface. Figures are computed, not estimated —
     backend/tests/test_design_tokens.py recomputes them from this file
     and fails if a value drifts out of band. */
  --tn-text:       var(--tn-neutral-900);  /* 17.8:1 body and headings  */
  --tn-text-soft:  var(--tn-neutral-600);  /*  7.5:1 secondary prose    */
  --tn-text-muted: var(--tn-neutral-500);  /*  5.0:1 captions, th       */
  --tn-text-faint: var(--tn-neutral-400);  /*  2.6:1 PLACEHOLDERS ONLY,
                                                    never real text     */

  /* ================================================================
     STATUS — every hue is an INK and a TINT chosen together for
     >=4.5:1, because a badge puts one directly on the other. Picked
     independently they measured 2.86:1 — a warning nobody with
     imperfect eyesight could read. Use the pairs; never hand-mix.
     ================================================================ */
  --tn-success: #15803d;  --tn-success-bg: #e8f8ee;  /* 4.56:1 */
  --tn-warn:    #b45309;  --tn-warn-bg:    #fdf2e0;  /* 4.53:1 */
  --tn-danger:  #b91c1c;  --tn-danger-bg:  #fdecec;  /* 5.66:1 */
  --tn-info:    #0369a1;  --tn-info-bg:    #e7f2fb;  /* 5.22:1 */

  /* Channels for the badge's inset ring, which is the ink at 22%. */
  --tn-success-rgb: 21, 128, 61;
  --tn-warn-rgb:    180, 83, 9;
  --tn-danger-rgb:  185, 28, 28;
  --tn-info-rgb:    3, 105, 161;

  /* A fifth, deliberately non-status tone: "no state worth colouring".
     Templates reached for .tn-badge--muted 16 times before it existed,
     which is a requirement stated by usage. */
  --tn-muted:    var(--tn-neutral-600);
  --tn-muted-bg: var(--tn-neutral-100);

  /* --- Decorative accents (shared with the marketing site) -------
     Used sparingly, and mostly on pre-login surfaces: the ambient blobs
     behind the auth card, illustration fills, soft chips. */
  --tn-mint:       #10b981;
  --tn-mint-soft:  #e7f8f1;
  --tn-coral:      #f97366;
  --tn-coral-soft: #fdece9;
  --tn-sun:        #f59e0b;
  --tn-sun-soft:   #fef6e3;
  --tn-sky:        #0ea5e9;
  --tn-sky-soft:   #e6f4fb;

  /* CHART — categorical series in a FIXED order (never cycled), validated
     for colour-vision deficiency on the light and dark surfaces; up/down are
     the diverging pair for a waterfall; total is the neutral bar. */
  --tn-chart-1:     #2a78d6;
  --tn-chart-2:     #eb6834;
  --tn-chart-3:     #1baf7a;
  --tn-chart-up:    #2a78d6;
  --tn-chart-down:  #e34948;
  --tn-chart-total: #52514e;
  --tn-chart-grid:  #e6e5e0;
  --tn-chart-gap:   #fcfcfb;

  /* ================================================================
     CHROME — the sidebar and topbar are SURFACES, not slabs.

     Polarity is a token: anything that depends on light-or-dark ground
     — hover washes, hairlines, ink — is named here, never written as a
     literal rgba(255,255,255,.06) in a component. A tenant can repaint
     --tn-sidebar-bg (core/branding), and the product measures that
     colour's luminance and ships the matching ink, hover wash and
     hairline with it. Hardcoded white made light rails lose their hover
     state and dark ones lose their text.
     ================================================================ */
  --tn-sidebar-bg:         var(--tn-surface);
  --tn-sidebar-ink:        var(--tn-text-soft);
  --tn-sidebar-ink-strong: var(--tn-text);
  --tn-sidebar-ink-muted:  var(--tn-text-muted);
  --tn-sidebar-hover:      var(--tn-surface-alt);
  --tn-sidebar-border:     var(--tn-border);

  /* Colour marks the active thing. Brand is spent on the current item
     and on actions — nowhere else. A rail with one saturated element
     tells you where you are; a rail with ten tells you nothing.
     A soft tint plus brand ink at 7.07:1, not a solid block. */
  --tn-sidebar-active-bg:   var(--tn-primary-50);
  --tn-sidebar-active-text: var(--tn-primary-700);

  --tn-topbar-bg:     var(--tn-surface);
  --tn-topbar-border: var(--tn-border);

  /* ================================================================
     TYPOGRAPHY

     Three faces, all self-hosted (see fonts.css). Fetched from a
     third-party font host, the product's typography depends on that
     origin being reachable — and a network that filters it degrades to
     the system stack silently, because nothing anywhere logs a font
     that failed to arrive.
     ================================================================ */
  /* The vendored Geist faces are `unicode-range` limited to latin + latin-ext,
     so Devanagari, Bengali, Telugu, Tamil and Arabic never download them and
     fall through to whatever the OS supplies. Naming the script families
     explicitly is what turns that fall-through from "whatever happens" into a
     choice: font matching is PER CHARACTER, so a Hindi name picks Noto Sans
     Devanagari while the English label beside it still renders in Geist.

     These are LOCAL family names, not @font-face rules -- the CSP forbids
     Google Fonts (font-src 'self') and the binaries are not vendored. Nirmala
     UI ships on Windows, the Kohinoor/Sangam faces on macOS, and Noto on
     Android, ChromeOS and most Linux. Vendoring Noto subsets is the real fix
     and needs the ~8 woff2 files added; until then this is the difference
     between correct glyphs and tofu on a lean container. */
  --tn-font-sans: "Geist", system-ui, -apple-system, "Segoe UI", Roboto,
                  "Noto Sans Devanagari", "Nirmala UI", "Kohinoor Devanagari",
                  "Noto Sans Bengali", "Noto Sans Telugu", "Noto Sans Tamil",
                  "Tamil Sangam MN", "Noto Sans Arabic", "Geeza Pro",
                  "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --tn-font-mono: "Geist Mono", ui-monospace, SFMono-Regular, "SF Mono",
                  Menlo, Consolas, "Liberation Mono", monospace;
  --tn-font-display: "Instrument Serif", Georgia, "Times New Roman", serif;

  /* --- The ramp: a LEVEL, not a size ----------------------------
     Type is specified as bundles — size, line height, weight and
     tracking together. They only work as a set: 24px at the body's 1.55
     leading and no tracking is a large paragraph, not a title.

     Line height falls as size rises (1.15 at 32px against 1.55 at 14px)
     and tracking goes the other way (-0.022em to 0). Both are the same
     correction: at display sizes the gaps a typeface is drawn with are
     already ample, so they need taking back.

     Body is 14px. This is dense software — payroll runs, invoice lists,
     attendance registers — and rows visible per screen is what the
     density buys. Sizes are in rem, so a reader who raises their
     browser's base size scales the whole product with it. */
  --tn-display-size: 2rem;
  --tn-display-leading: 1.15;
  --tn-display-weight: 600;
  --tn-display-tracking: -0.022em;

  --tn-h1-size: 1.5rem;
  --tn-h1-leading: 1.25;
  --tn-h1-weight: 600;
  --tn-h1-tracking: -0.017em;

  --tn-h2-size: 1.25rem;
  --tn-h2-leading: 1.3;
  --tn-h2-weight: 600;
  --tn-h2-tracking: -0.014em;

  --tn-h3-size: 1.125rem;
  --tn-h3-leading: 1.35;
  --tn-h3-weight: 600;
  --tn-h3-tracking: -0.011em;

  --tn-h4-size: 1rem;
  --tn-h4-leading: 1.4;
  --tn-h4-weight: 600;
  --tn-h4-tracking: -0.008em;

  --tn-h5-size: 0.9375rem;
  --tn-h5-leading: 1.45;
  --tn-h5-weight: 600;
  --tn-h5-tracking: -0.006em;

  --tn-h6-size: 0.875rem;
  --tn-h6-leading: 1.45;
  --tn-h6-weight: 600;
  --tn-h6-tracking: -0.003em;

  --tn-body-size: 0.875rem;
  --tn-body-leading: 1.55;
  --tn-body-weight: 400;
  --tn-body-tracking: 0em;

  --tn-body-sm-size: 0.8125rem;
  --tn-body-sm-leading: 1.5;
  --tn-body-sm-weight: 400;
  --tn-body-sm-tracking: 0em;

  --tn-caption-size: 0.75rem;
  --tn-caption-leading: 1.45;
  --tn-caption-weight: 500;
  --tn-caption-tracking: 0em;

  --tn-overline-size: 0.6875rem;
  --tn-overline-leading: 1.4;
  --tn-overline-weight: 600;
  --tn-overline-tracking: 0.07em;

  --tn-stat-size: 1.75rem;
  --tn-stat-leading: 1.1;
  --tn-stat-weight: 600;
  --tn-stat-tracking: -0.02em;

  --tn-mono-size: 0.875rem;
  --tn-mono-leading: 1.5;
  --tn-mono-weight: 400;
  --tn-mono-tracking: 0em;

  /* ================================================================
     SPACE & SHAPE
     ================================================================ */

  /* A 4px base. Use the scale; never an arbitrary value. Lay groups out
     with flex or grid `gap` rather than per-element margins, which
     collapse and double in ways that are painful to trace. */
  --tn-space-1:  0.25rem;  /*  4px — icon-to-label inside a badge  */
  --tn-space-2:  0.5rem;   /*  8px — between related controls      */
  --tn-space-3:  0.75rem;  /* 12px — cell padding, nav row padding */
  --tn-space-4:  1rem;     /* 16px — between cards in a grid       */
  --tn-space-5:  1.25rem;  /* 20px — card interior padding         */
  --tn-space-6:  1.5rem;   /* 24px — page padding, section gaps    */
  --tn-space-8:  2rem;     /* 32px \                               */
  --tn-space-10: 2.5rem;   /* 40px  } major section separation     */
  --tn-space-12: 3rem;     /* 48px /                               */

  /* One corner language, expressed as two INTENTS. Controls — buttons,
     inputs, selects, icon buttons — all wear the control radius.
     Containers wear the card radius. Buttons at 8px beside inputs at
     12px is obvious the moment they share a filter bar, which is
     exactly how that bug was found. Prefer these over the t-shirt
     sizes in the back-compat block below. */
  --tn-radius-control: 0.5rem;   /*  8px */
  --tn-radius-card:    0.75rem;  /* 12px */

  /* ================================================================
     ELEVATION — the chrome is separated by hairlines, so shadow is
     reserved for things that genuinely float. A card gets a hint; a
     menu gets the real thing.
     ================================================================ */
  --tn-shadow-xs: 0 1px 2px 0 rgba(16, 24, 40, 0.05);
  --tn-shadow-sm: 0 1px 3px 0 rgba(16, 24, 40, 0.08),
                  0 1px 2px -1px rgba(16, 24, 40, 0.08);
  --tn-shadow:    0 4px 12px -2px rgba(16, 24, 40, 0.1),
                  0 2px 6px -2px rgba(16, 24, 40, 0.08);
  --tn-shadow-lg: 0 12px 28px -6px rgba(16, 24, 40, 0.18);

  /* ================================================================
     FOCUS & MOTION — one ring, defined once and reused. Components each
     inventing their own meant tabbing across a single form changed the
     ring's shape twice.
     ================================================================ */
  --tn-focus-ring-color: rgba(var(--tn-primary-rgb), 0.24);
  --tn-focus-ring: 0 0 0 3px var(--tn-focus-ring-color);
  --tn-focus-outline-width: 2px;

  /* One duration and curve. Every transition is muted under
     prefers-reduced-motion (app.css). */
  --tn-duration: 150ms;
  --tn-ease: cubic-bezier(0.4, 0, 0.2, 1);
  --tn-transition: var(--tn-duration) var(--tn-ease);

  /* ================================================================
     LAYOUT DIMENSIONS — geometry, not design values. A rail width is
     allowed to be a px number the way a 28px avatar is.
     ================================================================ */
  --tn-sidebar-width: 248px;
  --tn-topbar-height: 60px;
  --tn-settings-rail-width: 232px;

  /* ================================================================
     BOOTSTRAP BRIDGE — map our tokens onto Bootstrap 5.3's own CSS
     variables so stock components inherit the system without a Sass
     recompile.

     EVERY variable bridged here MUST also appear in the dark block
     below. Bootstrap's own [data-bs-theme=dark] values sit at the same
     specificity as :root, so a variable set only once silently keeps
     its light value in dark mode. This is the single most common way
     the system breaks, and test_design_tokens.py holds the two lists
     equal.
     ================================================================ */
  --bs-primary: var(--tn-primary);
  --bs-primary-rgb: var(--tn-primary-rgb);
  --bs-link-color: var(--tn-primary);
  --bs-link-hover-color: var(--tn-primary-700);
  --bs-body-font-family: var(--tn-font-sans);
  --bs-font-monospace: var(--tn-font-mono);
  --bs-body-font-size: var(--tn-body-size);
  --bs-body-line-height: var(--tn-body-leading);

  /* Planes and ink. .text-muted alone is 1566 template uses, and it reads
     --bs-secondary-color. */
  --bs-body-bg: var(--tn-bg);
  --bs-body-color: var(--tn-text);
  --bs-emphasis-color: var(--tn-text);
  --bs-heading-color: var(--tn-text);
  --bs-secondary-color: var(--tn-text-muted);
  --bs-tertiary-color: var(--tn-text-faint);
  --bs-secondary-bg: var(--tn-surface-alt);
  --bs-tertiary-bg: var(--tn-surface-sunk);
  --bs-border-color: var(--tn-border);
  --bs-border-color-translucent: var(--tn-border);
  --bs-code-color: var(--tn-danger);
  --bs-highlight-bg: var(--tn-warn-bg);
  --bs-highlight-color: var(--tn-text);

  --bs-border-radius: var(--tn-radius-control);
  --bs-border-radius-sm: var(--tn-radius-sm);
  --bs-border-radius-lg: var(--tn-radius-card);

  /* Status. Mapping the four *-subtle / *-text-emphasis triples onto the
     pairs re-skins 168 .alert and 82 .badge.bg-* sites at once, and keeps
     them on ink-over-its-own-tint rather than Bootstrap's stock mixes. */
  --bs-success: var(--tn-success);
  --bs-success-bg-subtle: var(--tn-success-bg);
  --bs-success-border-subtle: var(--tn-success-bg);
  --bs-success-text-emphasis: var(--tn-success);
  --bs-warning: var(--tn-warn);
  --bs-warning-bg-subtle: var(--tn-warn-bg);
  --bs-warning-border-subtle: var(--tn-warn-bg);
  --bs-warning-text-emphasis: var(--tn-warn);
  --bs-danger: var(--tn-danger);
  --bs-danger-bg-subtle: var(--tn-danger-bg);
  --bs-danger-border-subtle: var(--tn-danger-bg);
  --bs-danger-text-emphasis: var(--tn-danger);
  --bs-info: var(--tn-info);
  --bs-info-bg-subtle: var(--tn-info-bg);
  --bs-info-border-subtle: var(--tn-info-bg);
  --bs-info-text-emphasis: var(--tn-info);
  --bs-primary-bg-subtle: var(--tn-primary-50);
  --bs-primary-border-subtle: var(--tn-primary-100);
  --bs-primary-text-emphasis: var(--tn-primary-700);
  --bs-secondary-bg-subtle: var(--tn-surface-alt);
  --bs-secondary-border-subtle: var(--tn-border);
  --bs-secondary-text-emphasis: var(--tn-text-soft);

  --bs-form-valid-color: var(--tn-success);
  --bs-form-valid-border-color: var(--tn-success);
  --bs-form-invalid-color: var(--tn-danger);
  --bs-form-invalid-border-color: var(--tn-danger);

  --bs-focus-ring-color: var(--tn-focus-ring-color);
}

/* ==================================================================
   DARK THEME
   Only roles flip. The ramps are untouched — that is the whole point of
   having them.
   ================================================================== */
[data-theme="dark"] {
  /* Brand resolves one step lighter, which keeps it at 6.16:1 on the
     dark surface. At 600 it would be 3.4:1. */
  --tn-primary: var(--tn-primary-400);
  --tn-primary-contrast: var(--tn-neutral-950);
  --tn-primary-rgb: 129, 140, 248;

  /* Bespoke, slightly blue-lifted planes rather than ramp steps: the
     neutral ramp's dark end is tuned for ink, not for stacked surfaces,
     and three near-black planes still have to be tellable apart. */
  --tn-bg:            var(--tn-neutral-950);
  --tn-surface:       #0d1424;
  --tn-surface-alt:   #161f33;
  --tn-surface-sunk:  #0a1120;

  --tn-border:        #1f293f;
  --tn-border-subtle: #162033;
  --tn-border-strong: #2c3853;

  --tn-text:       var(--tn-neutral-50);
  --tn-text-soft:  var(--tn-neutral-300);
  --tn-text-muted: var(--tn-neutral-400);
  --tn-text-faint: var(--tn-neutral-500);

  /* Status: the tint becomes a translucent wash of its OWN ink at 14%.
     A fixed light tint would glow; a fixed dark one would not separate
     from the surface.

     The ink lightens too. Carried over unchanged, the light inks land
     at 2.7-3.3:1 on their own composited dark tint. The pair rule
     (>=4.5:1, ink on its own tint) is what makes a badge readable and it
     has to hold in both themes, so these are the measured light-end
     equivalents rather than the same hex twice. */
  --tn-success-rgb: 34, 197, 94;
  --tn-warn-rgb:    245, 158, 11;
  --tn-danger-rgb:  248, 113, 113;
  --tn-info-rgb:    56, 189, 248;

  --tn-success: #22c55e;  --tn-success-bg: rgba(var(--tn-success-rgb), 0.14);  /* 6.43:1 */
  --tn-warn:    #f59e0b;  --tn-warn-bg:    rgba(var(--tn-warn-rgb), 0.14);     /* 6.87:1 */
  --tn-danger:  #f87171;  --tn-danger-bg:  rgba(var(--tn-danger-rgb), 0.14);   /* 5.52:1 */
  --tn-info:    #38bdf8;  --tn-info-bg:    rgba(var(--tn-info-rgb), 0.14);     /* 6.68:1 */

  --tn-muted:    var(--tn-neutral-400);
  --tn-muted-bg: var(--tn-surface-alt);

  /* Accent softs need real values, not the light tints. */
  --tn-mint-soft:  #0e2a22;
  --tn-coral-soft: #2e1b18;
  --tn-sun-soft:   #2a2210;
  --tn-sky-soft:   #0e2030;

  /* Chart series stepped for the dark surface, not flipped. */
  --tn-chart-1:     #3987e5;
  --tn-chart-2:     #d95926;
  --tn-chart-3:     #199e70;
  --tn-chart-up:    #3987e5;
  --tn-chart-down:  #e66767;
  --tn-chart-total: #c3c2b7;
  --tn-chart-grid:  #383835;
  --tn-chart-gap:   #1a1a19;

  /* Chrome. Every one of these is a role, so the rail follows the theme
     without a single literal. */
  --tn-sidebar-bg:         var(--tn-surface);
  --tn-sidebar-ink:        var(--tn-text-soft);
  --tn-sidebar-ink-strong: var(--tn-text);
  --tn-sidebar-ink-muted:  var(--tn-text-muted);
  --tn-sidebar-hover:      var(--tn-surface-alt);
  --tn-sidebar-border:     var(--tn-border);
  --tn-sidebar-active-bg:   rgba(var(--tn-primary-rgb), 0.16);
  --tn-sidebar-active-text: var(--tn-primary-300);

  --tn-topbar-bg:     var(--tn-surface);
  --tn-topbar-border: var(--tn-border);

  /* Slate-tinted shadows are invisible on near-black, where elevation is
     carried by the hairline instead. Black ones still read. */
  --tn-shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.32);
  --tn-shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.4),
                  0 1px 2px -1px rgba(0, 0, 0, 0.4);
  --tn-shadow:    0 4px 12px -2px rgba(0, 0, 0, 0.48),
                  0 2px 6px -2px rgba(0, 0, 0, 0.4);
  --tn-shadow-lg: 0 12px 28px -6px rgba(0, 0, 0, 0.6);

  /* The ring needs more presence on a dark ground. */
  --tn-focus-ring-color: rgba(var(--tn-primary-rgb), 0.38);

  /* --- Bootstrap bridge, re-declared in full --------------------
     Not redundant: see the note in :root. Every name declared there
     appears here, or it silently keeps its light value in dark mode. */
  --bs-primary: var(--tn-primary);
  --bs-primary-rgb: var(--tn-primary-rgb);
  --bs-link-color: var(--tn-primary-300);
  --bs-link-hover-color: var(--tn-primary-200);
  --bs-body-font-family: var(--tn-font-sans);
  --bs-font-monospace: var(--tn-font-mono);
  --bs-body-font-size: var(--tn-body-size);
  --bs-body-line-height: var(--tn-body-leading);

  /* Planes and ink. .text-muted alone is 1566 template uses, and it reads
     --bs-secondary-color. */
  --bs-body-bg: var(--tn-bg);
  --bs-body-color: var(--tn-text);
  --bs-emphasis-color: var(--tn-text);
  --bs-heading-color: var(--tn-text);
  --bs-secondary-color: var(--tn-text-muted);
  --bs-tertiary-color: var(--tn-text-faint);
  --bs-secondary-bg: var(--tn-surface-alt);
  --bs-tertiary-bg: var(--tn-surface-sunk);
  --bs-border-color: var(--tn-border);
  --bs-border-color-translucent: var(--tn-border);
  --bs-code-color: var(--tn-danger);
  --bs-highlight-bg: var(--tn-warn-bg);
  --bs-highlight-color: var(--tn-text);

  --bs-border-radius: var(--tn-radius-control);
  --bs-border-radius-sm: var(--tn-radius-sm);
  --bs-border-radius-lg: var(--tn-radius-card);

  /* Status. Mapping the four *-subtle / *-text-emphasis triples onto the
     pairs re-skins 168 .alert and 82 .badge.bg-* sites at once, and keeps
     them on ink-over-its-own-tint rather than Bootstrap's stock mixes. */
  --bs-success: var(--tn-success);
  --bs-success-bg-subtle: var(--tn-success-bg);
  --bs-success-border-subtle: var(--tn-success-bg);
  --bs-success-text-emphasis: var(--tn-success);
  --bs-warning: var(--tn-warn);
  --bs-warning-bg-subtle: var(--tn-warn-bg);
  --bs-warning-border-subtle: var(--tn-warn-bg);
  --bs-warning-text-emphasis: var(--tn-warn);
  --bs-danger: var(--tn-danger);
  --bs-danger-bg-subtle: var(--tn-danger-bg);
  --bs-danger-border-subtle: var(--tn-danger-bg);
  --bs-danger-text-emphasis: var(--tn-danger);
  --bs-info: var(--tn-info);
  --bs-info-bg-subtle: var(--tn-info-bg);
  --bs-info-border-subtle: var(--tn-info-bg);
  --bs-info-text-emphasis: var(--tn-info);
  --bs-primary-bg-subtle: var(--tn-primary-50);
  --bs-primary-border-subtle: var(--tn-primary-100);
  --bs-primary-text-emphasis: var(--tn-primary-700);
  --bs-secondary-bg-subtle: var(--tn-surface-alt);
  --bs-secondary-border-subtle: var(--tn-border);
  --bs-secondary-text-emphasis: var(--tn-text-soft);

  --bs-form-valid-color: var(--tn-success);
  --bs-form-valid-border-color: var(--tn-success);
  --bs-form-invalid-color: var(--tn-danger);
  --bs-form-invalid-border-color: var(--tn-danger);

  --bs-focus-ring-color: var(--tn-focus-ring-color);

  /* Deliberately NOT bridged: Bootstrap's --bs-*-rgb channel variants for
     the neutral roles (--bs-body-bg-rgb, --bs-secondary-color-rgb, ...) and
     its literal `light`/`dark` colour names. The channel variants exist only
     so Bootstrap can build rgba() opacity utilities; bridging them would mean
     maintaining twelve neutral channel triples by hand for a handful of
     .bg-body-secondary-subtle uses this product does not make. If one of
     those utilities ever shows a stock grey in dark mode, that is the reason,
     and the fix is to add the channel here rather than to patch the call
     site. */
}

/* ==================================================================
   BACK-COMPAT ALIASES
   ------------------------------------------------------------------
   The names app.css and board.css grew before the ramp was formalised.
   Kept as aliases rather than renamed across 1800 lines in one commit,
   so component CSS can migrate to the intent names incrementally.

   Every value here is a var() reference, so these re-derive in dark for
   free. Do not add a new one — point new code at the roles above.
   ================================================================== */
:root,
[data-theme="dark"] {
  /* Radius: the t-shirt sizes that predate the two intents. */
  --tn-radius-sm:   0.25rem;
  --tn-radius:      var(--tn-radius-control);
  --tn-radius-md:   var(--tn-radius-card);
  --tn-radius-lg:   1rem;
  --tn-radius-pill: 999px;

  /* Type: the old bare-size scale, repointed at the ramp so anything
     still using it lands on a real level rather than a loose number. */
  --tn-text-xs:   var(--tn-caption-size);
  --tn-text-sm:   var(--tn-body-sm-size);
  --tn-text-base: var(--tn-body-size);
  --tn-text-lg:   var(--tn-h4-size);
  --tn-text-xl:   var(--tn-h2-size);
  --tn-text-2xl:  var(--tn-h1-size);
  --tn-text-3xl:  var(--tn-stat-size);
  --tn-leading:   var(--tn-body-leading);

  /* These four were referenced with a fallback by app.css and board.css
     before they were ever defined, so the fallback is what actually
     rendered — including an off-brand #2563eb standing in for the
     brand indigo on the recruit board. */
  --tn-surface-2:    var(--tn-surface-alt);
  --tn-accent:       var(--tn-primary);
  --tn-motion:       var(--tn-transition);
  --tn-border-color: var(--tn-border);

  --tn-sidebar-text: var(--tn-sidebar-ink);
}

/* ==================================================================
   MARKETING-SITE ALIAS LAYER
   ------------------------------------------------------------------
   web/assets/css/styles.css grew its own vocabulary — --bg, --text,
   --brand, --r-md, --shadow-lg — while the app grew --tn-*. Same brand,
   two dialects, which is why a signed-out app page and a marketing page
   never quite matched: every shared value had to be hand-copied, and
   ADR 0032 already recorded that they had drifted once.

   Rather than rename hundreds of declarations on either side, the
   marketing names are ALIASED onto the --tn-* scale here. Component CSS
   written for the marketing site works unchanged inside the app, and
   there is exactly one place a colour is defined.

   Direction matters: --tn-* is the source, these are the aliases. Never
   define a new colour here — add it above and alias it.
   ================================================================== */
:root,
[data-theme="dark"] {
  /* Surfaces */
  --bg:       var(--tn-bg);
  --bg-soft:  var(--tn-surface-alt);
  --bg-elev:  var(--tn-surface);
  --bg-muted: var(--tn-surface-alt);

  /* Text */
  --text:        var(--tn-text);
  --text-soft:   var(--tn-text-soft);
  --text-muted:  var(--tn-text-muted);
  --text-faint:  var(--tn-text-faint);
  --text-invert: var(--tn-primary-contrast);

  /* Borders */
  --border:        var(--tn-border);
  --border-soft:   var(--tn-border-subtle);
  --border-strong: var(--tn-border-strong);

  /* Brand */
  --brand-50:   var(--tn-primary-50);
  --brand-100:  var(--tn-primary-100);
  --brand-200:  var(--tn-primary-200);
  --brand-500:  var(--tn-primary-500);
  --brand-600:  var(--tn-primary-600);
  --brand-700:  var(--tn-primary-700);
  --brand-soft: var(--tn-primary-50);
  --brand:      var(--tn-primary);
  --brand-ink:  var(--tn-primary-800);

  /* Accents */
  --mint:      var(--tn-mint);
  --mint-soft: var(--tn-mint-soft);
  --coral:     var(--tn-coral);
  --coral-soft: var(--tn-coral-soft);
  --sun:       var(--tn-sun);
  --sun-soft:  var(--tn-sun-soft);
  --sky:       var(--tn-sky);
  --sky-soft:  var(--tn-sky-soft);

  /* Radius — the marketing scale is chunkier than the app's, and that
     is the point: it is what makes a card read as a landing surface. */
  --r-sm: var(--tn-radius-control);
  --r-md: var(--tn-radius-card);
  --r-lg: var(--tn-radius-lg);
  --r-xl: 1.5rem;

  /* Shadows */
  --shadow-xs: var(--tn-shadow-xs);
  --shadow-sm: var(--tn-shadow-sm);
  --shadow-md: var(--tn-shadow);
  --shadow-lg: var(--tn-shadow-lg);
}

/* ==================================================================
   SKIN: NEST -- the second theming axis
   ------------------------------------------------------------------
   [data-skin] sits on <html> beside [data-theme], so every block in
   this file cascades on ONE element. Two consequences decide where
   these blocks live and what they must contain:

   1. PLACEMENT -- LAST IN THE FILE. See point 2 of ORDER MATTERS in
      the file header. SkinParityTests asserts the ordering, because
      the failure it prevents is silent.

   2. COMPLETENESS. This block also sits AFTER [data-theme="dark"] at
      equal specificity, so on a dark page it WINS. Every name declared
      here MUST be declared again in the nest+dark block below --
      exactly the rule the --bs-* bridge already lives under, with a
      worse failure mode: a dark page on a warm off-white ground.

   Nest declares no --bs-* and no alias name. It does not need to: the
   whole Bootstrap bridge and both alias layers are var() references,
   so they re-derive against these planes on their own.

   Ratios are measured with core.design.colors.contrast_ratio and
   recomputed by backend/tests/test_design_tokens.py -- the figures
   below are documentation, not the enforcement.
   ================================================================== */
[data-skin="nest"] {
  /* Warm ground. All four planes and the three hairlines move onto the
     same warm axis, so a card does not read as a cool patch on a warm
     page. */
  --tn-bg:           #faf7f2;
  --tn-surface:      #fffdf9;
  --tn-surface-alt:  #f3ede3;
  --tn-surface-sunk: #f7f2e9;

  --tn-border:        #e5ddd0;
  --tn-border-subtle: #f0e9dd;
  --tn-border-strong: #d3c7b6;

  /* Ink re-warmed to match. First figure is on --tn-surface; the second
     is the MINIMUM across all four planes, which is the number that
     actually has to clear AA -- muted on a table head is the sunk
     plane, not the surface. */
  --tn-text:       #1b1712;  /* 17.55:1  min 15.31:1  body and headings */
  --tn-text-soft:  #544c42;  /*  8.30:1  min  7.24:1  secondary prose */
  --tn-text-muted: #6e6457;  /*  5.70:1  min  4.98:1  captions, th */
  --tn-text-faint: #a79c8c;  /*  2.66:1  PLACEHOLDERS ONLY, never real text */

  /* The neutral chip follows the warm plane rather than staying a cool
     grey on a warm page. */
  --tn-muted-bg: var(--tn-surface-alt);

  /* Chunkier corners are the other half of what reads as nest. lg is
     restated because the alias layer above declares it as a literal. */
  --tn-radius-card: 1.125rem;  /* 18px */
  --tn-radius-lg:   1.25rem;   /* 20px */
}

/* ==================================================================
   NEST + DARK -- (0,2,0), so it outranks :root, the dark block, both
   alias layers and the nest block above, whatever the source order.

   It restates every name the nest block declares. That is not
   redundancy: without it the nest block wins on a dark page, because
   it is later in the file at equal specificity.

   Everything the quiet dark block sets and nest does not remap -- the
   status washes, the shadow ramp, the brand step, the whole --bs-*
   bridge -- is inherited from it unchanged. The four
   rgba(ink, .14) status washes were re-composited onto THIS surface
   and re-measured: success 6.01, warn 6.26, danger 5.07, info 6.30,
   so nest-dark declares no status variable at all. --tn-primary
   resolves to --tn-primary-400 #818cf8 at 5.80:1 here.
   ================================================================== */
[data-skin="nest"][data-theme="dark"] {
  --tn-bg:           #14110c;
  --tn-surface:      #1e1a14;
  --tn-surface-alt:  #292319;
  --tn-surface-sunk: #100e09;

  --tn-border:        #332c21;
  --tn-border-subtle: #252019;
  --tn-border-strong: #4b4132;

  --tn-text:       #faf7f2;  /* 16.20:1  min 14.57:1  body and headings */
  --tn-text-soft:  #d8cdbc;  /* 11.03:1  min  9.92:1  secondary prose */
  --tn-text-muted: #ac9f8b;  /*  6.67:1  min  6.00:1  captions, th */
  --tn-text-faint: #7d7263;  /*  3.68:1  PLACEHOLDERS ONLY, never real text */

  --tn-muted-bg: var(--tn-surface-alt);

  --tn-radius-card: 1.125rem;
  --tn-radius-lg:   1.25rem;
}
