/* tokens.css — the Polaris design system, one layer beneath every screen.
 *
 * Direction (docs/SESSION_LOG.md, Phase 4 brief): minimal, functional, modern.
 * Apple supplies the mechanics: hierarchy carried by alpha and weight rather
 * than hue, three surface levels, a four-step text ladder, tracking that
 * changes with size, and colour reserved for state. Claude supplies the
 * temperature: a warm neutral ramp (hue about 40deg, saturation under 6%) and
 * one clay accent instead of SaaS indigo. That single choice is what keeps this
 * from looking like every other Linear clone, and it costs nothing.
 *
 * THE GOVERNING RULE: the performance surface is about 95% greyscale. Colour
 * appears in four places only: delta chips, status pills, threshold breaches,
 * and focus/selection. Platform badges (Google/Meta/Amazon) are NEUTRAL,
 * because platform is identity, not state, and hue spent on identity is hue
 * unavailable for state.
 *
 * Light-first with a real dark theme, not a dark app with a light mode bolted
 * on: Polaris is read in the morning to decide where money goes.
 *
 * LEGACY ALIASES live at the bottom. The 560 lines of app.css written before
 * this system refer to --ink, --paper, --card, --line, --blue and friends.
 * Those names now resolve INTO this system rather than carrying their own hex,
 * so every existing screen inherits the new palette without being rewritten,
 * and screens get migrated to the semantic names as they are touched.
 */

/* ============================================================ A. neutrals */
:root {
  --n-0:    #FFFFFF;
  --n-25:   #FCFBF9;
  --n-50:   #F7F6F3;   /* canvas */
  --n-100:  #F2F0EC;   /* sunken */
  --n-150:  #EAE8E3;   /* gridline */
  --n-200:  #E2DFD9;
  --n-300:  #D3CFC7;
  --n-400:  #B5B0A6;
  --n-500:  #8F8A80;
  --n-600:  #6E6963;
  --n-700:  #55514C;
  --n-800:  #3B3835;
  --n-900:  #262421;
  --n-1000: #171512;   /* ink */

  --ink-rgb:   23, 21, 18;
  --paper-rgb: 245, 241, 232;
}
:root[data-theme="dark"] {
  --n-0:    #1F1E1C;   /* raised */
  --n-25:   #1C1B19;
  --n-50:   #171614;   /* canvas */
  --n-100:  #1A1917;   /* sunken */
  --n-150:  #2A2825;
  --n-200:  #33302C;
  --n-300:  #413D38;
  --n-400:  #57534C;
  --n-500:  #787369;
  --n-600:  #A29C91;
  --n-700:  #C9C4BA;
  --n-800:  #DEDACF;
  --n-900:  #EDEAE3;
  --n-1000: #F5F1E8;
}

/* ================================ B. surfaces, text, borders (3 levels only) */
:root {
  --surface-canvas:   var(--n-50);
  --surface-raised:   var(--n-0);
  --surface-sunken:   var(--n-100);
  --surface-hover:    rgba(var(--ink-rgb), 0.035);
  --surface-selected: rgba(var(--ink-rgb), 0.060);
  --surface-pressed:  rgba(var(--ink-rgb), 0.085);
  --surface-backdrop: rgba(var(--ink-rgb), 0.45);

  /* Four tones. Only the first three ever carry data; the fourth is ornament. */
  --text-primary:     rgba(var(--ink-rgb), 0.92);
  --text-secondary:   rgba(var(--ink-rgb), 0.72);
  --text-tertiary:    rgba(var(--ink-rgb), 0.60);
  --text-quaternary:  rgba(var(--ink-rgb), 0.38);
  --text-inverse:     var(--n-0);

  --border-subtle:    rgba(var(--ink-rgb), 0.06);
  --border-default:   rgba(var(--ink-rgb), 0.10);
  --border-strong:    rgba(var(--ink-rgb), 0.16);
  --gridline:         var(--n-150);       /* OPAQUE: table rules only */
  --backdrop-ring:    var(--surface-canvas);
}
/* Dark alpha overlays run 1.3x to 1.8x the light value: a wash has to work
   harder against a dark ground. Never mirror an alpha across themes. */
:root[data-theme="dark"] {
  --surface-hover:    rgba(var(--paper-rgb), 0.045);
  --surface-selected: rgba(var(--paper-rgb), 0.075);
  --surface-pressed:  rgba(var(--paper-rgb), 0.105);
  --surface-backdrop: rgba(0, 0, 0, 0.62);

  --text-primary:     rgba(var(--paper-rgb), 0.94);
  --text-secondary:   rgba(var(--paper-rgb), 0.68);
  --text-tertiary:    rgba(var(--paper-rgb), 0.50);
  --text-quaternary:  rgba(var(--paper-rgb), 0.30);
  --text-inverse:     var(--n-1000);

  --border-subtle:    rgba(var(--paper-rgb), 0.07);
  --border-default:   rgba(var(--paper-rgb), 0.11);
  --border-strong:    rgba(var(--paper-rgb), 0.18);
}

/* ====================================================== C. accent (clay) */
/* One chromatic hue in the chrome: primary action, focus ring, active nav and
   tab indicator, selected-row rail, current-period chart series. Nothing else. */
:root {
  --accent-50:  #FDF6F2;
  --accent-100: #F8E6DC;
  --accent-200: #F0C9B4;
  --accent-300: #E39E7C;
  --accent-400: #D97757;
  --accent-500: #C25E3E;
  --accent-600: #A34A2E;
  --accent-700: #7E3722;

  --accent-fill:       var(--accent-500);
  --accent-fill-hover: var(--accent-600);
  --accent-text:       var(--accent-600);
  --accent-on-fill:    #FFFFFF;
  --focus-ring-color:  rgba(217, 119, 87, 0.55);
}
:root[data-theme="dark"] {
  --accent-fill:       #C25E3E;
  --accent-fill-hover: #D97757;
  --accent-text:       #E8926E;
  --accent-on-fill:    #171614;
  --focus-ring-color:  rgba(232, 146, 110, 0.60);
}

/* ======================================= D. meaning-carrying palette (5 roles) */
/* Every role is legible on its own tint at 4.5:1+ in both themes, and every
   pair differs in LIGHTNESS as well as hue, so the screen survives greyscale
   and colour vision deficiency. This is the entire non-neutral budget.
   Three stock states share amber on purpose: the hue does not distinguish
   them, the label does. Do not invent a fourth amber. */
:root {
  --neutral-text:   rgba(var(--ink-rgb), 0.62);
  --neutral-fill:   #EFEDE8;
  --neutral-border: rgba(var(--ink-rgb), 0.10);
  --neutral-mark:   var(--n-500);

  --info-text:      #1A5AA8;
  --info-fill:      #E6EFFA;
  --info-border:    #BFD6F0;
  --info-mark:      #2E7CD6;

  --positive-text:  #1B6B45;
  --positive-fill:  #E3F2E7;
  --positive-border:#BFE0CB;
  --positive-mark:  #2F8F5B;

  --warning-text:   #8A5300;
  --warning-fill:   #FBEFD8;
  --warning-border: #F2DAAB;
  --warning-mark:   #B87A0A;

  --negative-text:  #A81F32;
  --negative-fill:  #FBE7E9;
  --negative-border:#F2C3C9;
  --negative-mark:  #D93B4B;
}
:root[data-theme="dark"] {
  --neutral-text:   rgba(var(--paper-rgb), 0.62);
  --neutral-fill:   rgba(var(--paper-rgb), 0.08);
  --neutral-border: rgba(var(--paper-rgb), 0.14);
  --neutral-mark:   var(--n-500);

  --info-text:      #8CBEF5;
  --info-fill:      rgba(46, 124, 214, 0.20);
  --info-border:    rgba(140, 190, 245, 0.28);
  --info-mark:      #5FA0E8;

  --positive-text:  #7FD8A4;
  --positive-fill:  rgba(47, 143, 91, 0.20);
  --positive-border:rgba(127, 216, 164, 0.28);
  --positive-mark:  #4FBF83;

  --warning-text:   #F0C069;
  --warning-fill:   rgba(184, 122, 10, 0.22);
  --warning-border: rgba(240, 192, 105, 0.28);
  --warning-mark:   #E0A63C;

  --negative-text:  #FF9A9F;
  --negative-fill:  rgba(217, 59, 75, 0.22);
  --negative-border:rgba(255, 154, 159, 0.28);
  --negative-mark:  #F2626D;
}

/* ==================================================== E. typography */
/* Euclid Circular B is the intended face. It is a commercial Swiss Typefaces
   release and there is NO webfont licence in this repo, so these @font-face
   rules resolve to nothing today and the fallback carries the UI. The trial
   cuts are not a workaround: they ship without numerals, which is useless for a
   screen that is largely numbers.
   THE LICENCE IS A PURCHASE BLOCKER, not a design problem to code around.
   --font-numeric is a SEPARATE token so numbers stay on the system stack with
   tabular figures while labels take Euclid when it arrives. That reads as a
   deliberate pairing rather than a broken font, and switching it later is a
   one-line change plus a size-adjust recalibration. */
@font-face {
  font-family: "Euclid Circular B";
  src: url("/fonts/EuclidCircularB-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Euclid Circular B";
  src: url("/fonts/EuclidCircularB-Medium.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Euclid Circular B";
  src: url("/fonts/EuclidCircularB-Semibold.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
/* Metric-matched fallback so the eventual swap does not reflow the page.
   Recalibrate these four overrides against the licensed files on arrival. */
@font-face {
  font-family: "Euclid Fallback";
  src: local("SF Pro Text"), local("Segoe UI Variable Text"), local("Segoe UI"),
       local("Roboto"), local("Helvetica Neue"), local("Arial");
  size-adjust: 97%;
  ascent-override: 92%;
  descent-override: 23%;
  line-gap-override: 0%;
}

:root {
  --font-ui: "Euclid Circular B", "Euclid Fallback", -apple-system,
             BlinkMacSystemFont, system-ui, "Segoe UI Variable Text", "Segoe UI",
             Roboto, "Noto Sans", "Liberation Sans", sans-serif;
  --font-numeric: -apple-system, BlinkMacSystemFont, system-ui,
                  "Segoe UI Variable Text", "Segoe UI", Roboto,
                  "Noto Sans", "Liberation Sans", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "Liberation Mono", "Roboto Mono", monospace;

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

  /* size / line-height / weight / tracking, as one shorthand each.
     Tracking follows the Apple curve: positive below 12px, zero at 12px,
     increasingly negative from 13 to 22px, easing back above 24px. */
  --t-display-32: var(--fw-semi) 32px/36px var(--font-ui);
  --t-display-24: var(--fw-semi) 24px/28px var(--font-ui);
  --t-title-20:   var(--fw-semi) 20px/26px var(--font-ui);
  --t-title-17:   var(--fw-semi) 17px/22px var(--font-ui);
  --t-body-15:    var(--fw-regular) 15px/22px var(--font-ui);
  --t-body-13:    var(--fw-regular) 13px/18px var(--font-ui);
  --t-label-13:   var(--fw-medium) 13px/16px var(--font-ui);
  --t-label-12:   var(--fw-medium) 12px/16px var(--font-ui);
  --t-label-11:   var(--fw-medium) 11px/14px var(--font-ui);
  --t-micro-10:   var(--fw-medium) 10px/13px var(--font-ui);

  --tr-display-32: -0.022em;
  --tr-display-24: -0.018em;
  --tr-title-20:   -0.015em;
  --tr-title-17:   -0.012em;
  --tr-body-15:    -0.011em;
  --tr-body-13:    -0.006em;
  --tr-label-11:    0.006em;
  --tr-micro-10:    0.012em;
}

/* Numerals are enforced by the component, never left to the caller. */
.numeric, td.num, th.num, .kpi-value, .delta-chip, .axis-tick, .timestamp,
.card .v, .sku, .acctmeta {
  font-family: var(--font-numeric);
  font-variant-numeric: lining-nums tabular-nums slashed-zero;
  font-feature-settings: "tnum" 1, "lnum" 1, "zero" 1;
}
.sku, .acctmeta { font-family: var(--font-mono); }

/* ============================== F. spacing, radii, elevation */
:root {
  --sp-0: 0; --sp-2: 2px; --sp-4: 4px; --sp-6: 6px; --sp-8: 8px; --sp-10: 10px;
  --sp-12: 12px; --sp-16: 16px; --sp-20: 20px; --sp-24: 24px; --sp-32: 32px;
  --sp-40: 40px; --sp-48: 48px; --sp-64: 64px; --sp-80: 80px;

  --page-padding: 24px;
  --page-max: 1600px;
  --prose-max: 640px;
  --rail-width: 232px;
  --rail-mini: 56px;
  --content-gutter: 8px;

  /* Radius is bound to control height, roughly height/5 to height/4.
     Buttons are NOT pills: only date-range pills and the platform segmented
     control use --r-full. */
  --r-2: 2px;
  --r-4: 4px;
  --r-6: 6px;
  --r-8: 8px;
  --r-12: 12px;
  --r-full: 9999px;

  /* Hairlines are RINGS, not borders: a ring cannot shift layout on hover and
     two adjacent rings do not double-darken when the backdrop ring is used. */
  --ring:          0 0 0 1px var(--border-default);
  --ring-strong:   0 0 0 1px var(--border-strong);
  --ring-backdrop: 0 0 0 1px var(--backdrop-ring);

  --shadow-xs: 0 1px 1px rgba(var(--ink-rgb), 0.04);
  --shadow-sm: 0 1px 2px rgba(var(--ink-rgb), 0.05),
               0 2px 4px -2px rgba(var(--ink-rgb), 0.04);
  --shadow-md: 0 2px 4px -2px rgba(var(--ink-rgb), 0.05),
               0 8px 16px -6px rgba(var(--ink-rgb), 0.06);
  --shadow-lg: 0 4px 8px -4px rgba(var(--ink-rgb), 0.05),
               0 24px 40px -12px rgba(var(--ink-rgb), 0.10);

  /* Nothing is elevated at rest. Elevation means "this left the plane". */
  --elev-card:    var(--ring);
  --elev-sticky:  var(--ring), var(--shadow-xs);
  --elev-popover: var(--ring), var(--ring-backdrop), var(--shadow-md);
  --elev-modal:   var(--ring), var(--ring-backdrop), var(--shadow-lg);
}
:root[data-theme="dark"] {
  --shadow-xs: none;                       /* a 4% shadow cannot work on dark */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.30);
  --shadow-md: 0 2px 4px -2px rgba(0, 0, 0, 0.28), 0 8px 16px -6px rgba(0, 0, 0, 0.34);
  --shadow-lg: 0 4px 8px -4px rgba(0, 0, 0, 0.30), 0 24px 40px -12px rgba(0, 0, 0, 0.46);
}

/* ================================================ G. motion */
/* Every user-triggered transition is ease-out. No ease-in-out, no springs, no
   bounce. Hover mutates exactly ONE property (background-color): never border
   width, size or shadow, because those move the layout. */
:root {
  --dur-instant: 0ms;
  --dur-exit:    150ms;
  --dur-fast:     80ms;
  --dur-base:    140ms;
  --dur-slow:    200ms;
  --dur-overlay: 240ms;

  --ease-out:      cubic-bezier(0.25, 0.46, 0.45, 0.94);
  --ease-out-soft: cubic-bezier(0.215, 0.61, 0.355, 1);
}
/* Motion is BANNED on KPI values (a number that animates is a number you
   cannot read), on table reorder after a sort, and on sparkline redraw. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-exit: 0ms; --dur-fast: 0ms; --dur-base: 0ms;
    --dur-slow: 0ms; --dur-overlay: 0ms;
  }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ================================================ H. controls */
:root {
  --ctl-sm: 28px;   /* compact density */
  --ctl-md: 32px;   /* default */
  --ctl-lg: 36px;
  /* Pointer scale, not the 44px touch minimum: this is a desktop instrument. */
  --focus-ring: 0 0 0 2px var(--surface-canvas), 0 0 0 4px var(--focus-ring-color);

  /* Table density, switched by [data-density] on the table wrapper. */
  --row-h: 36px;
  --row-pad-y: 8px;
}
[data-density="compact"] { --row-h: 28px; --row-pad-y: 4px; }

/* Focus uses outline, never box-shadow, so it cannot be clipped by an ancestor
   or affect layout. :focus-visible only, so a mouse click does not ring. */
*:focus-visible {
  outline: 2px solid var(--focus-ring-color);
  outline-offset: 2px;
  border-radius: var(--r-4);
}

/* ============================================ LEGACY ALIASES */
/* Everything above is the system. Everything below is the bridge: the names
   app.css already uses, resolved INTO the system so no existing screen breaks
   and the palette still changes everywhere at once. Migrate a screen off these
   when you touch it; do not add new uses. */
:root, :root[data-theme="dark"] {
  --ink:        var(--n-1000);          /* solid: used as a background on toasts/tooltips */
  --ink2:       var(--text-secondary);
  --paper:      var(--surface-canvas);
  --card:       var(--surface-raised);
  --line:       var(--n-200);           /* opaque: these are real 1px borders */
  --line2:      var(--n-150);
  --hover:      var(--n-100);
  --muted:      var(--n-400);

  --blue:       var(--info-mark);
  --blue-soft:  var(--info-fill);
  --green:      var(--positive-mark);
  --green-soft: var(--positive-fill);
  --amber:      var(--warning-mark);
  --amber-soft: var(--warning-fill);
  --red:        var(--negative-mark);
  --red-soft:   var(--negative-fill);
  --violet:     #7C3AED;
  --violet-soft:#F0E9FE;

  --chart-grid: var(--gridline);
  --chart-text: var(--n-500);
  --shadow:     var(--shadow-xs);
  --shadow-pop: var(--shadow-lg);
  --mono:       var(--font-mono);
}
:root[data-theme="dark"] {
  --violet:      #A78BFA;
  --violet-soft: #2A2140;
}

/* ============================================ w4-4 CHART SERIES SCALE */
/* The categorical series ramp. Twelve colours, light and dark, that until now
   lived as two hardcoded arrays in charts.js:7-8 — the one part of the palette
   a token change could not reach (BACKLOG B15 / the reopened chart-palette
   item). They are here because colour belongs in CSS: shared.js already says
   so ("colours come from CSS, always") and reads --blue and friends through
   P.cssVar for exactly this reason.

   ORDER IS LOAD-BEARING. Series 1 is the first line on every chart, so it has
   to be the same blue as --info-mark or a single-series chart looks like a
   different product from the KPI card above it. After that the ramp is ordered
   for adjacent-pair contrast (blue, green, amber, violet, red…), because the
   commonest case is two or three series and those must be the easiest to tell
   apart, including for the ~8% of men with red-green colour vision deficiency:
   1 and 2 differ in lightness as well as hue.

   The dark values are not the light ones lightened. Saturated colour on a dark
   surface reads as glowing, so each is desaturated and lifted separately.

   charts.js reads these with its old hex as the fallback, so a stylesheet that
   fails to load still draws a chart rather than twelve black lines. */
:root {
  --series-1:  #2b71de;   /* = --info-mark: series 1 must match the KPI accent */
  --series-2:  #1e7f4f;
  --series-3:  #9a6b00;
  --series-4:  #7c3aed;
  --series-5:  #b3362c;
  --series-6:  #0e7490;
  --series-7:  #be185d;
  --series-8:  #4d7c0f;
  --series-9:  #b45309;
  --series-10: #475569;
  --series-11: #166534;
  --series-12: #7e22ce;
}
:root[data-theme="dark"] {
  --series-1:  #5b96f0;
  --series-2:  #45b97f;
  --series-3:  #d9a514;
  --series-4:  #a78bfa;
  --series-5:  #e0645a;
  --series-6:  #22b8cf;
  --series-7:  #f06595;
  --series-8:  #94c748;
  --series-9:  #e8912d;
  --series-10: #8b95a5;
  --series-11: #38a169;
  --series-12: #b197fc;
}
