/* site/app/tokens.css — the design system, as values.
 *
 * DESIGN.md is the reasoning; this file is the implementation. If the two ever disagree,
 * DESIGN.md is right and this file has drifted.
 *
 * Load order: this file first, then a screen's own CSS. Nothing else defines a colour, a
 * type size, an easing curve or a duration — a hex code written into a screen is a bug,
 * because it will not follow the palette into dark mode.
 */

/* ═══ 1 · COLOUR ═══════════════════════════════════════════════════════════
 * The brand palette. Navy and cream do the talking; orange and chartreuse are used the way
 * a coach uses a whistle — sparingly, and only to call attention.
 *
 * ⚠ THE FULL LIGHT PALETTE IS DEFINED ON BARE :root. Dark mode REDEFINES tokens; it never
 *   introduces a colour that has no light-mode value. A token whose only definition sits
 *   inside a media query is invisible in the other theme.
 */
:root {
  /* Brand constants — these five do not change between themes. They are the brand. */
  --navy:       #001F3E;
  --cream:      #F6F8ED;
  --orange:     #F58E03;
  --chartreuse: #DBE64C;
  --steel:      #7C8078;

  /* Tints, derived. Kept few on purpose: a palette with nine greys is a palette nobody
     can hold in their head, and every extra one is a decision at 200 call sites. */
  --navy-90:    #0A2A4A;
  --navy-70:    #2B4665;
  --cream-dim:  #E8EBDD;
  --steel-20:   rgba(124, 128, 120, 0.20);
  --steel-40:   rgba(124, 128, 120, 0.40);

  /* ── Semantic roles. Screens use THESE, never the brand constants above. ── */
  --bg:            var(--cream);
  --bg-raised:     #FFFFFF;
  --bg-sunken:     var(--cream-dim);
  --ink:           var(--navy);
  --ink-secondary: #40566E;
  --ink-tertiary:  var(--steel);
  --rule:          var(--steel-20);

  /* ⚠ ONE ACTION COLOUR. Orange is the thing you are meant to press, and nothing else.
     Two orange things on a screen means one of them is wrong. */
  --action:        var(--orange);
  --action-ink:    #2A1800;
  --action-press:  #D97C02;

  /* ⚠ CHARTREUSE IS ENERGY, NOT STATUS. It marks a live rally, a ball, a thing happening
     now. It is never "success" — it reads as a highlighter, not as a tick. */
  --live:          var(--chartreuse);
  --live-ink:      var(--navy);

  --ok:            #1B7F4B;
  --warn:          #9A6300;
  --danger:        #A32020;

  /* Translucent chrome. See DESIGN.md §5. */
  --chrome:        rgba(246, 248, 237, 0.72);
  --chrome-blur:   blur(20px) saturate(180%);
  --chrome-edge:   rgba(255, 255, 255, 0.55);

  --shadow-1: 0 1px 2px rgba(0, 31, 62, 0.06), 0 1px 3px rgba(0, 31, 62, 0.08);
  --shadow-2: 0 4px 12px rgba(0, 31, 62, 0.10), 0 2px 4px rgba(0, 31, 62, 0.06);
  --shadow-3: 0 12px 32px rgba(0, 31, 62, 0.16), 0 4px 8px rgba(0, 31, 62, 0.08);

  color-scheme: light dark;
}

/* Dark is the natural expression here, not a chore: navy is already the foundation, and the
 * app is used courtside at night. Guarded with :not([data-theme="light"]) so an explicit
 * user choice still wins in both directions. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:            var(--navy);
    --bg-raised:     var(--navy-90);
    --bg-sunken:     #001830;
    --ink:           var(--cream);
    --ink-secondary: #A9B8C6;
    --ink-tertiary:  #7C8B99;
    --rule:          rgba(246, 248, 237, 0.14);

    --action-ink:    #2A1800;
    --chrome:        rgba(0, 31, 62, 0.72);
    --chrome-edge:   rgba(255, 255, 255, 0.10);

    --ok:            #4ADE80;
    --warn:          #FBBF24;
    --danger:        #F87171;

    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.30);
    --shadow-2: 0 4px 12px rgba(0, 0, 0, 0.38);
    --shadow-3: 0 12px 32px rgba(0, 0, 0, 0.48);
  }
}

:root[data-theme="dark"] {
  --bg: var(--navy);            --bg-raised: var(--navy-90);   --bg-sunken: #001830;
  --ink: var(--cream);          --ink-secondary: #A9B8C6;      --ink-tertiary: #7C8B99;
  --rule: rgba(246, 248, 237, 0.14);
  --chrome: rgba(0, 31, 62, 0.72);  --chrome-edge: rgba(255, 255, 255, 0.10);
  --ok: #4ADE80;  --warn: #FBBF24;  --danger: #F87171;
  --shadow-1: 0 1px 2px rgba(0,0,0,.30);
  --shadow-2: 0 4px 12px rgba(0,0,0,.38);
  --shadow-3: 0 12px 32px rgba(0,0,0,.48);
}

/* ═══ 2 · TYPE ═════════════════════════════════════════════════════════════
 * ⚠ SELF-HOSTED, NOT A FONT CDN. site/_headers has no font-src for fonts.googleapis.com,
 *   deliberately: a third-party font host watches every player load every page. Drop the
 *   WOFF2 files under /assets/ with content-hashed names so they ride the immutable cache
 *   rule, and add the @font-face blocks here.
 */
:root {
  --font-display: "Fraunces", Georgia, "Times New Roman", serif;
  --font-ui: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, "SF Mono", Menlo, monospace;

  /* ⚠ TRACKING IS SIZE-SPECIFIC. One letter-spacing for every size is wrong somewhere:
     large text reads too loose as it grows, small text too tight. Leading moves the other
     way — tight on display, generous on body. */
  --display-size: clamp(2.5rem, 8vw, 4rem);
  --display-lh: 1.05;   --display-track: -0.022em;
  --h1-size: 2.5rem;    --h1-lh: 1.1;   --h1-track: -0.018em;
  --h2-size: 1.75rem;   --h2-lh: 1.2;   --h2-track: -0.012em;
  --h3-size: 1.25rem;   --h3-lh: 1.3;   --h3-track: -0.006em;
  --body-size: 1.0625rem; /* 17px — the iOS reading size; the brand guide asks for 16–17 */
  --body-lh: 1.5;       --body-track: 0;
  --caption-size: 0.8125rem; --caption-lh: 1.4; --caption-track: 0.01em;
}

/* ═══ 3 · SPACE ════════════════════════════════════════════════════════════
 * In rem, never px, so the user's text-size setting expands the layout WITH the text
 * instead of breaking it.
 */
:root {
  --s-1: 0.25rem;  --s-2: 0.5rem;   --s-3: 0.75rem;  --s-4: 1rem;
  --s-5: 1.5rem;   --s-6: 2rem;     --s-7: 3rem;     --s-8: 4rem;

  --r-sm: 8px;  --r-md: 12px;  --r-lg: 16px;  --r-xl: 22px;  --r-full: 999px;

  /* ⚠ 44px is the floor, not a target. A control smaller than this is missed by a thumb
     on a phone held one-handed at a venue — which is every use of this app. */
  --tap-min: 44px;

  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
}

/* ═══ 4 · MOTION ═══════════════════════════════════════════════════════════
 * ⚠ NEVER `ease-in` ON A UI ELEMENT. It delays the initial movement — the exact moment the
 *   user is watching most closely — so a 300ms ease-in dropdown FEELS slower than a 300ms
 *   ease-out one at the identical duration.
 *
 * ⚠ The built-in CSS easings are too weak; these are the stronger variants. One family for
 *   the whole app: two visual languages are paid for by the reader on every screen.
 */
:root {
  --ease-out:    cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);

  --t-press:    120ms;   /* button feedback  */
  --t-tooltip:  160ms;   /* tooltips, chips  */
  --t-menu:     200ms;   /* dropdowns        */
  --t-sheet:    320ms;   /* sheets, modals   */
  --t-exit:     160ms;   /* ⚠ exit is always faster than enter */

  /* Spring parameters for JS-driven, gesture-owned motion. Apple's damping/response, not
     mass/stiffness/damping — two numbers a designer can reason about.
     ⚠ Default to damping 1.0 (no overshoot). Bounce ONLY when the gesture itself carried
       momentum: overshoot on a menu that merely faded in feels wrong; overshoot on a card
       you flicked feels right. */
  --spring-damping: 1.0;    --spring-response: 0.4;
  --spring-sheet-damping: 0.8;  --spring-sheet-response: 0.3;
}

/* ═══ 5 · BASE ═════════════════════════════════════════════════════════════ */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* Momentum scrolling, and no rubber-band on the document itself — the app supplies its
     own boundaries so the page does not bounce behind a sheet. */
  overscroll-behavior-y: none;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--body-size);
  line-height: var(--body-lh);
  letter-spacing: var(--body-track);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3 { font-family: var(--font-display); font-weight: 600; margin: 0; }
h1 { font-size: var(--h1-size); line-height: var(--h1-lh); letter-spacing: var(--h1-track); }
h2 { font-size: var(--h2-size); line-height: var(--h2-lh); letter-spacing: var(--h2-track); }
h3 { font-family: var(--font-ui); font-size: var(--h3-size); line-height: var(--h3-lh);
     letter-spacing: var(--h3-track); }

.display {
  font-family: var(--font-display); font-weight: 600;
  font-size: var(--display-size); line-height: var(--display-lh);
  letter-spacing: var(--display-track); font-optical-sizing: auto;
}

/* Kickers and quotes. Serif italic at SMALL sizes only — never a serif italic paragraph. */
.kicker {
  font-family: var(--font-display); font-style: italic; font-size: var(--caption-size);
  letter-spacing: var(--caption-track); color: var(--ink-secondary);
}

img, svg, video { max-width: 100%; height: auto; display: block; }

/* ⚠ The focus ring is never removed, only restyled. It is the only affordance a keyboard
   or switch-control user has, and this app has a public sign-up — assume every input
   method arrives. */
:focus-visible {
  outline: 2px solid var(--action);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* ═══ 6 · PRESSABLES ═══════════════════════════════════════════════════════
 * ⚠ FEEDBACK ON POINTER-DOWN, NOT ON RELEASE. Waiting for click to show a response feels
 *   dead; the moment lag appears, the sense of directness falls off a cliff.
 */
.pressable {
  touch-action: manipulation;              /* removes the legacy ~300ms tap delay */
  -webkit-tap-highlight-color: transparent; /* we draw our own, instantly */
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  transition: transform var(--t-press) var(--ease-out),
              background-color var(--t-press) var(--ease-out);
}
.pressable:active { transform: scale(0.97); }

/* ⚠ Hover is gated. A touch device fires hover on tap, so an ungated :hover leaves a
   button looking stuck in a hover state after the finger lifts. */
@media (hover: hover) and (pointer: fine) {
  .pressable:hover { background-color: var(--bg-sunken); }
}

.btn-primary {
  background: var(--action); color: var(--action-ink);
  font-family: var(--font-ui); font-weight: 600; font-size: var(--body-size);
  border: 0; border-radius: var(--r-full);
  padding: var(--s-3) var(--s-5);
}
.btn-primary:active { background: var(--action-press); }

/* ═══ 7 · CHROME ═══════════════════════════════════════════════════════════
 * A floating translucent layer with content scrolling underneath — not an opaque strip that
 * eats a fixed band of a phone screen.
 */
.chrome {
  background: var(--chrome);
  backdrop-filter: var(--chrome-blur);
  -webkit-backdrop-filter: var(--chrome-blur);
  border-bottom: 1px solid var(--chrome-edge);
  padding-top: var(--safe-top);
}
.chrome-bottom {
  background: var(--chrome);
  backdrop-filter: var(--chrome-blur);
  -webkit-backdrop-filter: var(--chrome-blur);
  border-top: 1px solid var(--chrome-edge);
  padding-bottom: var(--safe-bottom);
}

/* ⚠ NEVER STACK A LIGHT TRANSLUCENT SURFACE ON ANOTHER — legibility collapses. A sheet on
   top of translucent chrome gets a solid ground. */
.sheet { background: var(--bg-raised); box-shadow: var(--shadow-3);
         border-radius: var(--r-xl) var(--r-xl) 0 0; }

/* ═══ 8 · ENTRANCES ════════════════════════════════════════════════════════
 * ⚠ NOTHING ENTERS FROM scale(0). Nothing in the real world appears from nothing; an
 *   element that grows from zero reads as arriving out of nowhere. 0.95 and up.
 * ⚠ Transitions, not @keyframes: a transition can be interrupted and retargeted mid-flight,
 *   a keyframe restarts from zero. Everything here can be triggered rapidly.
 */
.enter {
  opacity: 1; transform: scale(1) translateY(0);
  transition: opacity var(--t-menu) var(--ease-out),
              transform var(--t-menu) var(--ease-out);
}
@starting-style { .enter { opacity: 0; transform: scale(0.96) translateY(4px); } }

/* Popovers scale from their TRIGGER, not their centre — the spatial relationship between
   the button and what it opened is the whole point. Modals are the exception and stay
   centred, because they are anchored to nothing. */
.popover { transform-origin: var(--pop-origin, center); }

/* ═══ 9 · ACCESSIBILITY ════════════════════════════════════════════════════
 * Reduced motion is a GENTLER equivalent, not nothing. Opacity and colour changes that aid
 * comprehension stay; movement, parallax and overshoot go.
 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 160ms !important;
    scroll-behavior: auto !important;
  }
  .pressable:active { transform: none; background-color: var(--bg-sunken); }
  .enter { transition-property: opacity; }
  @starting-style { .enter { opacity: 0; transform: none; } }
}

@media (prefers-reduced-transparency: reduce) {
  .chrome, .chrome-bottom {
    background: var(--bg);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

@media (prefers-contrast: more) {
  :root { --rule: var(--ink); --ink-secondary: var(--ink); }
  .chrome, .chrome-bottom { background: var(--bg); backdrop-filter: none; }
  .btn-primary { outline: 2px solid var(--ink); }
}
