/* ═══ SFTi — the PWA stylesheet, one card per section ════════════════════════════

   THE CASCADE IS POSITIONAL, so the <link> order in system/system.html IS this list, and it may
   not be reordered:

     SHARED — a rule here is used by two or more tabs
       tokens · reset · tap-targets · glass · shell · panel · primitives · modals
     PER TAB — a rule here is used by exactly ONE surface, and the card is named for it
       race · home · scan · discover · telemetry · settings · logs · boot

   If a per-tab rule turns out to be wanted by a second surface it MOVES up into a shared card
   rather than being copied — a duplicated rule is how two surfaces drift apart.
   ═════════════════════════════════════════════════════════════════════════ */

/* ═══ §0 · DESIGN TOKENS ══════════════════════════════════════════════════════════════ */

/* THE COLOUR DECK IS SHARED WITH index/ AND shop/ — the storefront's palette is the app's
   palette, declared once in app/shared/css/tokens.css and imported here so the three
   surfaces cannot drift. Only the tokens SHOP HAS NO OPINION ABOUT are declared below. */
@import url('/app/shared/css/tokens.css?v=1');

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&family=Orbitron:wght@400;500;700&display=swap');

:root {
  /* accents the storefront does not speak */
  --accent-green:       #21854B;
  --accent-green-bright:#2ECC71;
  --accent-violet:      #a855f7;
  --accent-orange:      #ff8c00;

  /* THE OUTCOME PALETTE — the scoreboard's own three colours. These are not a surface's
     colours: Win, Moon and a faded play mean the same thing on every tab, so they are named
     for the OUTCOME they report rather than the tab that first used them. */
  --outcome-win:  #3aff8a;
  --outcome-moon: #ffc84a;
  --outcome-fade: #ff5a7a;

  /* glows for the accents above */
  --glow-green:  0 0 15px rgba(33, 133, 75, 0.4);

  /* typography */
  --font-display: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-mono:    'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;

  /* spacing */
  --sp-xs:  4px;
  --sp-sm:  8px;
  --sp-md:  12px;
  --sp-lg:  16px;
  --sp-xl:  24px;
  --sp-xxl: 32px;

  /* safe areas (iOS notch — top/left/right only; the bottom belongs to the nav) */
  --safe-top:   env(safe-area-inset-top, 0px);
  --safe-left:  env(safe-area-inset-left, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --status-height: 48px;

  /* THE TOP RESERVE — the exact mirror of --nav-clearance below, for the fixed header, and it is
     declared HERE because TWO boxes consume it: `.view-container`'s padding-top and the toast's
     `top`. Two hand-written copies of one reserve is how a box and the space kept for it drift
     apart, which is the defect this token exists to make unsayable.
     `--header-occlusion` is what syncHeaderClearance() MEASURES off the bar's BORDER box on the
     real device; the token sum is the first-frame fallback only. Each consumer adds its OWN gap. */
  --header-clearance: var(--header-occlusion, calc(var(--status-height) + var(--safe-top)));

  /* THE BOTTOM RESERVE. The holo-nav is position:fixed, so every scrollable surface needs a
     clearance equal to what the nav actually occludes. `--nav-height` is the design estimate;
     `--nav-occlusion` is what app.js MEASURES on the real device and sets on <html> — the
     measurement wins when it exists. `--nav-float` is the holoFloat animation's travel, which
     the measurement has to add back because the buttons are mid-float at any instant. */
  --nav-height: 92px;
  --nav-float: 5px;
  /* ★★ THE INSET IS ALREADY INSIDE THE OCCLUSION — ADDING IT HERE COUNTED IT TWICE.
     `--nav-occlusion` is MEASURED by app.js off the nav's BORDER BOX, and that box contains the
     nav's own `padding-bottom: max(28px, env(safe-area-inset-bottom))` (shell.css §4.3). So
     `occlusion + env(...)` charges the home-indicator inset a second time: invisible at inset 0
     (measured `82px + 0 + 16 = 98px`, correct) and **+34px of pure surplus on the operator's
     phone** — which is the whole of "system/ didn't move at all" (2026-08-12, three photos).
     Same defect, same day, as the shared footer's `5rem + env(...)`: two boxes consuming one inset
     with two different arithmetics.
     ⚠ THE FALLBACK STILL CARRIES THE INSET, and that asymmetry is deliberate: `--nav-height` is a
     design ESTIMATE of the box, not a measurement of it, so nothing inside it has paid the inset
     yet. It is the first-frame value only — `--nav-occlusion` supersedes it as soon as app.js
     measures the real device. Collapsing the two would under-reserve for one frame on a notched
     phone; keeping them apart is what makes both cases correct. */
  --nav-fallback: calc(var(--nav-height) + max(28px, env(safe-area-inset-bottom, 0px)) - 28px);
  --nav-clearance: calc(var(--nav-occlusion, var(--nav-fallback)) + 16px);

  /* ONE CARD WIDTH, FOR EVERY SURFACE. The card column is declared once here and every card
     family consumes it; no surface states a width of its own. Measured at 390px before this
     token, the SAME app drew five different cards (366 · 366 · 350 · 342 · 302) because each
     surface added its own inset on top of the shell's. --shell-w scales the column with the
     viewport so a desktop is not a phone with margins; --card-peek is the carousel member. */
  --shell-w: min(100%, clamp(600px, 62vw, 900px));
  --card-w: 100%;
  --card-peek: clamp(264px, 78%, 520px);
}
