/* ===========================================================================
   Teletoken — design tokens
   ---------------------------------------------------------------------------
   The palette has TWO POLES and they mean different things. Cool (signal) is
   the machine: telemetry, interactive affordances, anything the pipeline does
   by itself. Warm (ember -> alarm) is a finding: something a person should
   look at. Nothing is warm for decoration, so warmth on this screen always
   carries information. That is the whole concept — this pipeline is 87% noise,
   so the ground stays quiet and cool and the rare signal is unmissable.

   Light mode is NOT an inversion. Per Apple's guidance, the two appearances
   are designed separately and each re-checked for contrast: in dark, raised
   surfaces get LIGHTER than the page; in light they stay white while the page
   goes cool grey, so elevation reads correctly in both.
   ========================================================================= */

:root {
  /* ---- type ------------------------------------------------------------
     Apple's Dynamic Type scale, adapted for desktop density. The structure
     and the ratios are Apple's (34/28/22/20/17 ...); the body anchor is
     pulled from iOS's 17pt to 15px because this is a pointer-driven data
     console, not a phone. Copying iOS point sizes literally onto a table of
     80 million rows would be following the letter of the HIG against its
     intent. --t-data is the one below body, for table cells only. */
  --t-display: 34px;
  --t-t1: 28px;
  --t-t2: 22px;
  --t-t3: 20px;
  --t-headline: 17px;
  --t-body: 15px;
  --t-data: 14px;
  --t-foot: 13px;
  --t-cap: 12px;
  --t-cap2: 11px;

  /* Three faces, three jobs, all three self-hosted from assets/fonts/ — see
     the @font-face block at the top of base.css for why nothing here is
     allowed to come from a CDN.

     Switzer carries the interface. It has UNIFORM-WIDTH DIGITS at every
     weight (all 576/1000 em) and carries no tnum/pnum pair at all, so columns
     line up whether or not font-variant-numeric reaches them — including the
     places inheritance cannot reach, like an SVG <text> label in a chart. */
  --f-sans: Switzer, ui-sans-serif, system-ui, -apple-system, sans-serif;

  /* Mono means MACHINE IDENTIFIER: a sha256, a bot id, a token, a storage
     path. It is never used for labels, headings or prose — using it as a
     decorative "technical" texture is the thing that makes dashboards look
     generic. Here it tells you the string is something a computer generated
     and you can copy verbatim. Commit Mono is monospaced, so every digit is
     600/1000 em and figures align by construction; there is no tnum feature
     and none is needed. */
  --f-mono: "Commit Mono", ui-monospace, "SF Mono", Menlo, monospace;

  /* DISPLAY ONLY, and the restriction is a property of the face, not taste.
     Used on exactly two things: the login wordmark (.gate__word in auth.css)
     and the page title (h1.t1 in base.css).

     PREFER NOT TO PUT A NUMBER IN THIS FACE. Space Grotesk's digits are
     PROPORTIONAL by default — 452/1000 em for "1" against 648 for "0" — so a
     figure that ticks reflows the text around it and a figure cannot be
     compared with the one above it. The subset does keep tnum, and body sets
     font-variant-numeric: tabular-nums globally, so a digit that reaches this
     face lands on a uniform 620/1000 em rather than breaking the line. That
     makes the rule a preference rather than the hard prohibition the serif
     before it needed. It is still the rule: the vitals strip's big numbers,
     every metric, every table cell and every chart label stay in --f-sans,
     because a display face carrying data is the thing this type system is
     built to avoid.

     It is also a SINGLE 700 WEIGHT, the only master in the subset. Anything
     that asks it for 400, 500 or 600 gets a synthetic face unless
     `font-synthesis: none` is in force — it is, on html in base.css — and a
     real 700 only because .gate__word and h1.t1 both restate the weight.
     Both halves are required; neither is decoration.

     The fallbacks are a grotesque stack, not a serif one: this face is a
     geometric sans, and falling back to Georgia would change the screen's
     voice rather than merely its texture. */
  --f-display: "Space Grotesk", var(--f-sans);

  --w-regular: 400;
  --w-medium: 500;
  --w-semibold: 600;

  /* ---- spacing: 8pt grid, 4pt subdivisions ---------------------------- */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 24px;
  --s6: 32px;
  --s7: 48px;
  --s8: 64px;

  /* ---- radii: deliberately NOT one value on everything ----------------
     Hierarchy is carried by shape as well as size. Chips are fully round,
     controls are tight, panels are soft, sheets are softest, and charts have
     no radius at all because they run to the edge of their panel. */
  --r-chip: 999px;
  --r-control: 8px;
  --r-panel: 14px;
  --r-sheet: 20px;

  /* ---- motion ---------------------------------------------------------
     --e-spring is a real translation of SwiftUI's default spring, not a
     guess: response 0.55, dampingFraction 0.825. Sampling the damped
     oscillator x(t) = 1 - e^(-zwt)(cos(w_d t) + (zw/w_d) sin(w_d t)) at
     55ms intervals gives the stops below, including the ~1% overshoot at
     ~0.5s. That is why motion here feels like the platform rather than like
     a cubic-bezier someone liked the look of.

     --e-exit has no overshoot: things leaving should not bounce on the way
     out, they should just go. */
  --e-spring: linear(0, 0.139, 0.393, 0.627, 0.799, 0.908, 0.968, 0.997,
                     1.008, 1.010, 1.008, 1.006, 1.003, 1);
  --e-standard: cubic-bezier(0.32, 0.72, 0, 1);
  --e-snappy: cubic-bezier(0.25, 1, 0.5, 1);
  --e-exit: cubic-bezier(0.4, 0, 1, 1);

  /* Feedback has to answer a question in under 300ms or it stops feeling
     like a response and starts feeling like a wait. */
  --d-micro: 120ms;
  --d-fast: 200ms;
  --d-base: 300ms;
  --d-slow: 500ms;
  --d-spring: 620ms;
  --d-hero: 900ms;

  /* Minimum hit target. Apple's 44pt; kept as a floor on every control even
     where the visible box is smaller. */
  --hit: 44px;

  --rail-w: 232px;
  --rail-w-collapsed: 68px;
  --vitals-h: 56px;
  --max-measure: 68ch; /* prose line length, under 80 characters */
}

/* ---- dark: the default appearance -------------------------------------- */
:root,
:root[data-theme="dark"] {
  color-scheme: dark;

  --abyss: #080e14;   /* page ground — blue-black, never neutral #111 */
  --slate: #0f1821;   /* panel */
  --shelf: #151f2a;   /* inset / hover / secondary surface */
  --hair: #1e2a36;    /* hairline */
  --hair-strong: #2b3a49;

  --ice: #e6eef6;     /* primary text — cool white, not pure white */
  --mist: #7d90a3;    /* secondary text   5.7:1 on --abyss */
  --dim: #55677a;     /* tertiary / disabled */

  --signal: #3fb5e8;  /* the machine        8.0:1 */
  --signal-soft: rgba(63, 181, 232, 0.14);
  --ember: #f59140;   /* a finding          7.5:1 */
  --ember-soft: rgba(245, 145, 64, 0.14);
  --alarm: #f2545b;   /* critical           5.2:1 */
  --alarm-soft: rgba(242, 84, 91, 0.14);
  --live: #3ecf9b;    /* healthy / moving */
  --live-soft: rgba(62, 207, 155, 0.14);

  /* Materials. Translucency shows layering and sits BEHIND content, never
     competing with it. */
  --material: rgba(15, 24, 33, 0.72);
  --material-blur: saturate(180%) blur(20px);
  --scrim: rgba(4, 8, 12, 0.6);

  --lift-1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --lift-2: 0 8px 24px -8px rgba(0, 0, 0, 0.7);
  --lift-3: 0 24px 64px -16px rgba(0, 0, 0, 0.8);
}

/* ---- light: designed, not inverted ------------------------------------ */
:root[data-theme="light"] {
  color-scheme: light;

  --abyss: #eef2f6;   /* page ground — cool paper, so white panels lift */
  --slate: #ffffff;
  --shelf: #e6ecf2;
  --hair: #d5dee7;
  --hair-strong: #b9c6d2;

  --ice: #0b1420;
  --mist: #56687a;    /* 5.6:1 on --abyss */
  --dim: #8696a6;

  /* The accents are re-picked, not reused: the dark-mode values would fail
     contrast on paper. */
  --signal: #0d78ab;
  --signal-soft: rgba(13, 120, 171, 0.1);
  --ember: #a9540a;
  --ember-soft: rgba(169, 84, 10, 0.1);
  --alarm: #b82b33;
  --alarm-soft: rgba(184, 43, 51, 0.1);
  --live: #0b7a53;
  --live-soft: rgba(11, 122, 83, 0.1);

  --material: rgba(255, 255, 255, 0.76);
  --material-blur: saturate(180%) blur(20px);
  --scrim: rgba(30, 42, 54, 0.32);

  --lift-1: 0 1px 2px rgba(16, 32, 48, 0.08);
  --lift-2: 0 8px 24px -8px rgba(16, 32, 48, 0.16);
  --lift-3: 0 24px 64px -16px rgba(16, 32, 48, 0.22);
}

/* ===========================================================================
   Chart colours
   ---------------------------------------------------------------------------
   These are NOT eyeballed. Every value below was run through the data-viz
   validator (OKLCH lightness band, chroma floor, CVD separation under
   simulated protanopia/deuteranopia, normal-vision floor, contrast against the
   surface the marks actually sit on — --slate, not the page ground).

   The first pair I tried, blue + violet, failed hard: ΔE 1.9 under protanopia
   and 9.8 under normal vision, against floors of 8 and 15. It looked fine.
   Blue + aqua passes at 19.6 / 20.9.

   --chart-a / --chart-b are CATEGORICAL: two measures compared side by side
   (how many files of a type, versus how many bytes they take).

   --sweep-1..4 are ORDINAL: one hue in steps, because the message-ID walk is a
   SEQUENCE — never probed, walking, deep sweep, halted — and four separate
   hues would throw away the ordering the reader needs to see.

   The status colours (--signal/--ember/--alarm/--live) stay reserved for state
   and are never reused as a series.
   ========================================================================= */

:root,
:root[data-theme="dark"] {
  --chart-a: #3987e5;
  --chart-b: #199e70;
  --sweep-1: #bcdcf8;
  --sweep-2: #86bcf0;
  --sweep-3: #5496e2;
  --sweep-4: #2f6db0;
}

:root[data-theme="light"] {
  /* Re-stepped for the light surface, and re-stepped AGAIN once the charts
     moved out of white panels onto the page ground.
     That move is easy to miss and it changes the answer: #1baf7a passes on
     white at 2.82:1 and fails on --abyss at 2.50:1, and the lightest sweep
     step fell to 1.91:1 against a 2:1 floor. The values below are validated
     against --abyss (#eef2f6), which is where these marks actually sit —
     4.15:1 for the aqua, 2.19:1 for the ramp's light end. */
  --chart-a: #2a78d6;
  --chart-b: #16845d;
  --sweep-1: #74a9e1;
  --sweep-2: #4a82cf;
  --sweep-3: #2a62aa;
  --sweep-4: #153f73;
}
