/* FG_UI_VERSION = '1.0.1' */
/*
 * fg-ui.css — the shared design layer for FairGame Labs.
 *
 * SOURCE OF TRUTH: the fairgame-ui repo. Every copy in a consumer app is
 * written by tools/sync.cjs. DO NOT EDIT A CONSUMER COPY — the next sync
 * silently reverts it. Same discipline as fv-engine.js/slip.js.
 *
 * WHY THIS EXISTS. Four apps shared a palette and a font pair and nothing
 * else. The bottom app switcher, the burger drawer and the header were
 * copy-pasted with "KEEP IN SYNC" comments, and had already drifted: three
 * different meanings for --border, two different values for --muted, four
 * different view-switching idioms, and a motion budget ranging from ten
 * keyframes to zero. There were no radius, spacing, shadow or motion tokens
 * at all — 316 literal border-radius declarations across the four files —
 * and prefers-reduced-motion was honoured in exactly one place.
 *
 * LOADING ORDER. Link this BEFORE an app's own inline <style>. Every existing
 * app rule then still wins on order, so adopting the package is additive and
 * a migration can proceed component by component.
 *
 * ⚠️ FRESHNESS. Load it with a versioned URL — `fg-ui.css?v=1.0.0`, written
 * by tools/sync.cjs. This is not belt-and-braces, it is the ONLY mechanism
 * that works. Measured 2026-08-12: the zone-level Browser Cache TTL overrides
 * the origin's Cache-Control on EVERY zone hostname (not just the apex, as
 * previously believed) — performance-lab.fairgamelabs.com and
 * alchemizer.fairgamelabs.com both return max-age=14400 for .js despite an
 * explicit `no-cache` in _headers. Only *.pages.dev honours it. HTML is never
 * cached that way, so a version in the URL is the one thing a stale cache
 * cannot defeat.
 */

:root {
  /* ── palette ────────────────────────────────────────────────────────────
   * Reconciled across the four apps. Two values are deliberate changes:
   *
   * --border: three apps used rgba(180,190,210,.08), the tracker an opaque
   *   #2c2e38. The alpha version wins because it reads consistently over
   *   --surface, --surface2 AND --surface3 — which the tracker's dense tables
   *   sit on. The tracker's stronger line is preserved as --border-strong for
   *   the table/row-separator rules that genuinely need it against --row-alt.
   *
   * --muted: three apps used #6f7480, the tracker #8b909c. Converged UP on
   *   the tracker's. #6f7480 on #14151a is ~3.6:1 — it FAILS WCAG AA for body
   *   text. #6f7480 survives as --muted2 for genuinely de-emphasised chrome
   *   (timestamps, unit suffixes) where the contrast floor does not apply.
   */
  --bg: #14151a;
  --surface: #1c1d24;
  --surface2: #26272f;
  --surface3: #2e2f38;

  --text: #eceef2;
  --muted: #8b909c;
  --muted2: #6f7480;
  --muted3: #4a4e58;

  --border: rgba(180, 190, 210, .08);
  --border2: rgba(180, 190, 210, .16);
  --border-strong: #2c2e38;

  --accent: #c8f060;
  --accent-dim: rgba(200, 240, 96, .12);
  --accent-border: rgba(200, 240, 96, .25);
  /* ⚠️ Use this, never `var(--accent)22`. A var() substitutes a WHOLE token,
   * so the trailing 22 stays a separate token and the declaration is dropped
   * as invalid — verified live, it computes to rgba(0,0,0,0). Several pill
   * "fill" rules have silently had no background for months because of it. */
  --accent-tint: rgba(200, 240, 96, .13);
  --on-accent: #111;

  --win: #34c97a;
  --loss: #f75c5c;
  --push: #f5c842;
  --pending: #7b82a0;

  --warn: #f0a040;
  --warn-dim: rgba(240, 160, 64, .12);
  --warn-border: rgba(240, 160, 64, .25);
  --danger: #f06060;
  --danger-dim: rgba(240, 96, 96, .1);
  --danger-border: rgba(240, 96, 96, .2);
  --yellow: #f0d040;
  --yellow-dim: rgba(240, 208, 64, .12);
  --yellow-border: rgba(240, 208, 64, .25);
  --purple: #b794f6;
  --purple-dim: rgba(183, 148, 246, .14);
  --purple-border: rgba(183, 148, 246, .32);
  --info: #5b9bd5;
  --info-dim: rgba(91, 155, 213, .14);
  --info-border: rgba(91, 155, 213, .32);

  --row-alt: #202128;
  --leg-pending: #7b82a0;
  --leg-void: #565b70;

  /* ── type ─────────────────────────────────────────────────────────────── */
  --sans: 'Archivo', system-ui, sans-serif;
  /* Historical name. It is Inter, not a monospace — the apps set
   * font-variant-numeric: tabular-nums globally to get aligned figures. */
  --mono: 'Inter', system-ui, sans-serif;

  /* ── radius ───────────────────────────────────────────────────────────── */
  --fg-r-1: 4px;
  --fg-r-2: 6px;
  --fg-r-3: 8px;
  --fg-r-4: 10px;
  --fg-r-5: 12px;
  --fg-r-6: 16px;
  --fg-r-pill: 999px;
  --fg-r-circle: 50%;

  /* ── space (4px base) ─────────────────────────────────────────────────── */
  --fg-s-1: 4px;
  --fg-s-2: 8px;
  --fg-s-3: 12px;
  --fg-s-4: 16px;
  --fg-s-5: 20px;
  --fg-s-6: 24px;
  --fg-s-7: 32px;
  --fg-s-8: 48px;

  /* ── elevation ────────────────────────────────────────────────────────────
   * Each value MATCHES an existing literal in the apps, so adopting a token is
   * a no-op render. That is what makes the shadow pass the safest migration
   * group to do wholesale. */
  --fg-sh-1: 0 1px 2px rgba(0, 0, 0, .25);
  --fg-sh-2: 0 4px 18px rgba(0, 0, 0, .22);   /* lab-loading toast */
  --fg-sh-3: 0 6px 24px rgba(0, 0, 0, .35);   /* default toast */
  --fg-sh-4: 0 8px 24px rgba(0, 0, 0, .4);    /* settings menu */
  --fg-focus: 0 0 0 3px rgba(200, 240, 96, .25);

  /* ── motion ───────────────────────────────────────────────────────────── */
  --fg-dur-1: 120ms;   /* tap / micro */
  --fg-dur-2: 200ms;   /* hover, toggle, opacity */
  --fg-dur-3: 250ms;   /* drawer — MUST stay .25s, matches shipped behaviour */
  --fg-dur-4: 400ms;   /* panel / view change */
  --fg-dur-5: 1400ms;  /* shimmer loop */
  --fg-ease-out: cubic-bezier(.2, .7, .3, 1);
  --fg-ease-in-out: cubic-bezier(.4, 0, .2, 1);
  --fg-ease-spring: cubic-bezier(.2, 1.2, .3, 1);  /* never on the drawer */

  /* ── layout / z ───────────────────────────────────────────────────────── */
  --fg-header-h: 56px;
  --fg-header-h-sm: 52px;
  --fg-switcher-h: 76px;
  --fg-view-bar-h: 56px;
  --fg-drawer-w: 260px;
  --fg-z-drawer: 99999;
  --fg-z-switcher: 999999;
  --fg-z-toast: 1000000;
}

/* iOS auto-inflates text by content width, so the SAME 13px switcher label
 * rendered at different sizes in each app. Pinned in all four already; kept
 * here so a consumer cannot lose it. */
html { -webkit-text-size-adjust: 100%; }

/* There is no consistent focus ring anywhere in the four apps. This is both an
 * accessibility gap and a visible cohesion gap. */
:focus-visible { outline: none; box-shadow: var(--fg-focus); }

/* ════════════════════════════════════════════════════════════════════════════
 * MOTION
 *
 * ⚠️ NEVER use animation-fill-mode: both on an ENTRY animation. A throttled or
 * never-completing animation then leaves the element parked at its 0% frame —
 * that is what left the tracker's sign-in gate permanently dim and locked users
 * out (tracker index.html :10653-10662). Entry animations here end at their
 * natural final state. If you need a persistent end state, set it in the BASE
 * rule and animate FROM the offset.
 * ══════════════════════════════════════════════════════════════════════════ */

@keyframes fg-shimmer { 0%, 100% { opacity: .3 } 50% { opacity: .7 } }
@keyframes fg-spin { to { transform: rotate(360deg) } }
@keyframes fg-fade-up { from { opacity: 0; transform: translateY(16px) } }
@keyframes fg-slide-in-left { from { opacity: 0; transform: translateX(-32px) } }
@keyframes fg-slide-in-right { from { opacity: 0; transform: translateX(32px) } }
@keyframes fg-count-in { from { opacity: 0; transform: scale(.8) } }
@keyframes fg-pulse {
  0% { box-shadow: 0 0 0 0 rgba(200, 240, 96, .35) }
  70% { box-shadow: 0 0 0 10px rgba(200, 240, 96, 0) }
  100% { box-shadow: 0 0 0 0 rgba(200, 240, 96, 0) }
}

/* Background orbs. front-door called these orb1/2/3 and the tracker fgOrb1/2/3
 * — the same animation under two names. One definition now. */
@keyframes fg-orb-a { 0%, 100% { transform: translate(0, 0) scale(1) } 50% { transform: translate(30px, -20px) scale(1.1) } }
@keyframes fg-orb-b { 0%, 100% { transform: translate(0, 0) scale(1) } 50% { transform: translate(-25px, 20px) scale(1.15) } }
@keyframes fg-orb-c { 0%, 100% { transform: translate(0, 0) scale(1) } 50% { transform: translate(20px, 25px) scale(.9) } }

/* ── lab-themed loaders ─────────────────────────────────────────────────── */
/* A meniscus falling down a burette. */
@keyframes fg-titrate { from { transform: translateY(-100%) } to { transform: translateY(0) } }
/* Bubbles rising out of a flask and fading. */
@keyframes fg-distill {
  0% { transform: translateY(0) scale(.6); opacity: 0 }
  30% { opacity: .9 }
  100% { transform: translateY(-14px) scale(1); opacity: 0 }
}
/* Electrons on offset elliptical paths. */
@keyframes fg-orbit { to { transform: rotate(360deg) } }
/* Sweeps a bell curve into existence — the confidence-interval motif already
 * present in the repo's own iconography. */
@keyframes fg-curve-draw { to { stroke-dashoffset: 0 } }

/* ⚠️ GLOBAL reduced-motion. Previously honoured in ONE scoped place across
 * four apps. An animation that cannot be stopped is an accessibility failure,
 * and for some users a vestibular one. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── loader components ──────────────────────────────────────────────────── */
.fg-loader-orbit {
  display: inline-block; position: relative;
  width: 1em; height: 1em; vertical-align: -.15em;
}
.fg-loader-orbit::before,
.fg-loader-orbit::after {
  content: ''; position: absolute; inset: 0;
  border: 1.5px solid transparent; border-top-color: currentColor;
  border-radius: var(--fg-r-circle);
  animation: fg-orbit .8s linear infinite;
}
.fg-loader-orbit::after {
  inset: 25%; border-top-color: transparent; border-bottom-color: currentColor;
  animation-duration: 1.2s; animation-direction: reverse;
}

/* Determinate. Drive with style="--fg-progress:.42". Replaces the ad-hoc
 * .fetch-progress-bar width transition in front-door and dashboard. */
.fg-loader-titration {
  position: relative; overflow: hidden;
  height: 4px; border-radius: var(--fg-r-pill);
  background: var(--surface3);
}
.fg-loader-titration::after {
  content: ''; position: absolute; inset: 0;
  transform-origin: left center;
  transform: scaleX(var(--fg-progress, 0));
  background: var(--accent);
  transition: transform var(--fg-dur-4) var(--fg-ease-out);
}

.fg-loader-curve { display: block; width: 120px; height: 48px; color: var(--accent) }
.fg-loader-curve path {
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-dasharray: 200; stroke-dashoffset: 200;
  animation: fg-curve-draw 1.6s var(--fg-ease-in-out) infinite;
}

/* ── skeletons ──────────────────────────────────────────────────────────── */
/* front-door and dashboard shared a .skeleton; the tracker and bet-lab had
 * none at all — bet-lab showed nothing until content arrived. */
.fg-skeleton { animation: fg-shimmer var(--fg-dur-5) ease-in-out infinite }
.fg-skel-bar { height: 11px; border-radius: var(--fg-r-1); background: var(--surface3) }
.fg-skel-row { display: flex; gap: var(--fg-s-3); padding: var(--fg-s-3) 0 }
.fg-skel-row > .fg-skel-bar { flex: 1 }
.fg-skel-card {
  height: 76px; border-radius: var(--fg-r-3);
  background: var(--surface2); border: 1px solid var(--border);
}

/* ── toast ──────────────────────────────────────────────────────────────── */
/* Extracted from the tracker's appToast, which was the only toast in the
 * product. bottom clears the 76px app switcher. */
.fg-toast {
  position: fixed; left: 50%; bottom: calc(var(--fg-switcher-h) + var(--fg-s-6));
  transform: translateX(-50%);
  z-index: var(--fg-z-toast);
  max-width: min(92vw, 420px);
  padding: var(--fg-s-3) var(--fg-s-4);
  border-radius: var(--fg-r-4);
  font: 500 13px/1.4 var(--mono);
  background: var(--surface2); color: var(--text);
  border: 1px solid var(--border2);
  box-shadow: var(--fg-sh-3);
  opacity: 0; pointer-events: none;
  transition: opacity var(--fg-dur-2) var(--fg-ease-out);
}
.fg-toast[data-open='1'] { opacity: 1 }
.fg-toast[data-variant='lab-loading'] {
  background: rgba(35, 38, 44, .9); color: #c5c9d0;
  border-color: rgba(255, 255, 255, .12);
  border-radius: var(--fg-r-4); font-size: 12px;
  box-shadow: var(--fg-sh-2);
}
.fg-toast[data-variant='win'] { border-color: var(--win); color: var(--win) }
.fg-toast[data-variant='error'] { border-color: var(--danger); color: var(--danger) }

/* ── empty state ────────────────────────────────────────────────────────── */
.fg-empty { text-align: center; padding: var(--fg-s-8) var(--fg-s-5); color: var(--muted); font-size: 13px }
.fg-empty .fg-empty-icon { font-size: 40px; line-height: 1; margin-bottom: var(--fg-s-3); opacity: .85 }

/* ════════════════════════════════════════════════════════════════════════════
 * NAVIGATION  (markup is injected by fg-nav.js)
 * ══════════════════════════════════════════════════════════════════════════ */

#fg-app-switcher {
  position: fixed;
  /* ⚠️ top:auto is load-bearing. A bare `nav {}` rule in the tracker's mobile
   * stylesheet sets a top offset, and it leaks onto this element because the
   * switcher IS a <nav>. Without this the bar jumps to mid-screen on phones. */
  top: auto; right: 0; bottom: 0; left: 0;
  height: var(--fg-switcher-h);
  display: flex; align-items: stretch;
  background: var(--surface);
  border-top: 1px solid var(--border);
  z-index: var(--fg-z-switcher);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
#fg-app-switcher a {
  flex: 1; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 4px;
  text-decoration: none; color: var(--muted);
  font: 600 13px/1 var(--mono);
  transition: color var(--fg-dur-2) var(--fg-ease-out);
}
#fg-app-switcher a.active { color: var(--accent) }
#fg-app-switcher img { width: 22px; height: 22px; object-fit: contain }
body { padding-bottom: var(--fg-switcher-h) }

/* ⚠️⚠️ THE DRAWER ANIMATES `left`, NOT `transform`. This is not an oversight
 * and must not be "optimised".
 *
 * In iOS WKWebView a transform-animated fixed-position drawer intermittently
 * rendered blank or snapped to its final position with no animation — the
 * compositor promotes the layer and then fails to paint it over the existing
 * fixed header/switcher stack. Animating `left` keeps it on the main thread,
 * which is slower in theory and correct in practice. This comment previously
 * lived in bet-lab index.html :119-130; it is canonical here now.
 *
 * If you are reading this while "modernising" the drawer: test on a real
 * iPhone, in a home-screen PWA, not in desktop Safari's responsive mode. */
#fg-menu-drawer {
  position: fixed; top: 0; bottom: 0;
  left: calc(-1 * var(--fg-drawer-w));
  width: var(--fg-drawer-w);
  background: var(--surface);
  border-right: 1px solid var(--border);
  z-index: var(--fg-z-drawer);
  transition: left var(--fg-dur-3) ease;
  display: flex; flex-direction: column;
  padding: var(--fg-s-4);
  overflow-y: auto;
}
#fg-menu-drawer.open { left: 0 }
#fg-menu-drawer .fg-drawer-title {
  font: 700 18px/1 var(--sans); color: var(--accent);
  letter-spacing: .5px; margin-bottom: var(--fg-s-5);
}
#fg-menu-drawer a {
  display: block; padding: var(--fg-s-3) var(--fg-s-2);
  color: var(--text); text-decoration: none;
  font: 500 14px/1 var(--mono); border-radius: var(--fg-r-2);
  transition: background var(--fg-dur-2) var(--fg-ease-out);
}
#fg-menu-drawer a:hover { background: var(--surface2) }
#fg-menu-drawer a.active { color: var(--accent) }

#fg-drawer-scrim {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, .5);
  z-index: calc(var(--fg-z-drawer) - 1);
  opacity: 0; pointer-events: none;
  transition: opacity var(--fg-dur-3) var(--fg-ease-out);
}
#fg-drawer-scrim.open { opacity: 1; pointer-events: auto }

.fg-burger-btn {
  width: 34px; height: 34px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: var(--on-accent);
  border: none; border-radius: var(--fg-r-2);
  font-size: 16px; cursor: pointer;
}

/* ── shared view switcher (tablist) ─────────────────────────────────────── */
/* bet-lab's setLabMode was the only accessible switcher of the four — correct
 * role/aria-selected/aria-controls wiring. This generalises it. */
.fg-tablist { display: flex; gap: var(--fg-s-2); flex-wrap: wrap }
.fg-tab {
  padding: var(--fg-s-2) var(--fg-s-4);
  border-radius: var(--fg-r-pill);
  border: 1px solid var(--border2);
  background: transparent; color: var(--muted);
  font: 600 13px/1 var(--mono); cursor: pointer;
  transition: color var(--fg-dur-2) var(--fg-ease-out),
              border-color var(--fg-dur-2) var(--fg-ease-out),
              background var(--fg-dur-2) var(--fg-ease-out);
}
.fg-tab[aria-selected='true'] {
  color: var(--accent); border-color: var(--accent-border);
  background: var(--accent-tint);
}

@media (max-width: 640px) {
  :root { --fg-header-h: var(--fg-header-h-sm); --fg-view-bar-h: 52px }
}
