/* ROOFTOP QUEST — design tokens.
 *
 * Tokens and primitives only: the custom properties, the reset, the type scale,
 * the four things every screen is built out of (.btn .card .chip .pill) plus
 * .icon and .sr-only, and the shared keyframes. src/ui/hud.js and
 * src/ui/landing.js ship their own CSS on top of this.
 *
 * Nothing in here may name a class that only one of those two owns. If a rule
 * needs to know about a layout, it belongs in that module's stylesheet. The
 * test of a token is whether both screens could use it.
 *
 * NAMING — so you can guess a name instead of grepping for one:
 *   --c-*      colour: raw hues (--c-pink) and roles (--c-text, --c-surface)
 *   --sky-*    the three stops of the 3D sky, mirrored from the art palette
 *   --s-*      space, a 4px scale, --s-1 up to --s-9
 *   --r-*      corner radius, --r-xs up to --r-xl, plus --r-pill
 *   --fs-*     font size    --lh-*  line height    --tr-*  letter spacing
 *   --fw-*     font weight  --sh-*  shadow
 *   --dur-* / --ease-*      motion      --z-*  stacking order
 *
 * COLOUR, AND WHY IT IS NOT JUST A COPY OF THE ART PALETTE
 *
 * Six tokens are exact mirrors of src/art/palette.js and have to stay that way,
 * because the page paints them behind or beside the same colour in the 3D scene:
 *
 *   --sky-top --sky-mid --sky-horizon   PAL.skyTop / skyMid / skyHorizon
 *   --c-green --c-green-deep            PAL.brandGreen / brandGreenDark
 *   --c-cream                           PAL.cream
 *
 * Change one of those and change its twin. They are copied rather than imported
 * because CSS cannot read a JS module without a load-order dance and the HUD has
 * to paint on the first frame.
 *
 * The rest of the hues below deliberately do NOT match their 3D namesakes. The
 * scene has to stay legible against a hot pink sky, which forces those colours
 * dark; the UI sits on near-black glass, where the same colours would disappear.
 * So PAL.beam is a deep vermillion and --c-coral is bright, and both are right.
 * Do not "sync" them.
 *
 * Type is a heavy rounded SYSTEM stack. No @font-face, no @import, nothing is
 * fetched. The named families are only picked up if the machine already has
 * them and the stack falls through to the platform UI face if it does not — so
 * never assume a specific metric, and never centre anything by eye on one OS.
 *
 * Keyframes are prefixed ps- because keyframe names are global and a second
 * `fade-in` defined in a module stylesheet silently wins.
 */

:root {
  /* ------------------------------------------------------------ brand hues */
  --c-pink: #ff6bae;
  --c-pink-hot: #ff3d84;
  --c-cyan: #3fd8f2;
  --c-lilac: #a7b7f2;
  --c-grape: #b79cf0;
  --c-violet: #7d6ad6;
  --c-butter: #ffe07a;
  --c-coral: #ff5f7d;
  --c-tangerine: #ff8a3d;
  --c-mint: #9ce6b0;
  --c-cream: #fff3dd;

  /* The brand green. Lighter than any roof in shared/course.js on purpose:
   * those greens are tuned to sit on a pink sky and they go muddy the moment
   * you put them on a dark card. This one reads on both. */
  --c-green: #5cc06a;
  --c-green-deep: #2f7a3c;

  /* --------------------------------------------------------------- the sky */
  /* Mirrors PAL.skyTop / skyMid / skyHorizon. --sky-grad is the page fallback
   * behind the canvas: without it there is a white flash on every load, which
   * on a pink-and-cyan game is the most obvious frame in the whole session. */
  --sky-top: #3fd8f2;
  --sky-mid: #a7b7f2;
  --sky-horizon: #ff6bae;
  --sky-grad: linear-gradient(180deg, var(--sky-top) 0%, var(--sky-mid) 52%, var(--sky-horizon) 100%);

  /* ------------------------------------------------------------ ink + text */
  --c-ink: #1c1730;
  --c-ink-2: #120e20;
  --c-ink-3: #332a4e;
  --c-text: #fff6e6;
  --c-text-dim: rgba(255, 246, 230, 0.72);
  --c-text-mute: rgba(255, 246, 230, 0.46);
  --c-text-on-light: #1c1730;

  /* ------------------------------------------------------------- surfaces */
  /* Every card floats over a live 3D scene, so the fills are translucent ink
   * and the blur is what keeps text legible when a pink slab slides under it.
   * --c-surface-solid is for the few places that must not show the race
   * through them (results, modal panels). */
  --c-surface: rgba(28, 23, 48, 0.82);
  --c-surface-2: rgba(52, 43, 84, 0.7);
  --c-surface-3: rgba(255, 255, 255, 0.1);
  --c-surface-solid: #221c39;
  --c-line: rgba(255, 255, 255, 0.12);
  --c-line-strong: rgba(255, 255, 255, 0.24);
  --blur-card: blur(14px) saturate(1.15);

  /* ------------------------------------------------------------- semantic */
  --c-good: var(--c-green);
  --c-warn: var(--c-butter);
  --c-bad: var(--c-coral);
  --c-focus: var(--c-cyan);

  /* ----------------------------------------------------------------- type */
  --font: ui-rounded, "SF Pro Rounded", "Hiragino Maru Gothic ProN", "Nunito",
    "Varela Round", "Segoe UI Variable Display", "Segoe UI", system-ui,
    -apple-system, "Helvetica Neue", Arial, sans-serif;

  /* The big title only. Heavier and wider than --font where the machine has
   * something for it; "Arial Rounded MT Bold" is the reliable fallback and is
   * the reason the display face still looks rounded on a bare install. */
  --display: ui-rounded, "SF Pro Rounded", "Baloo 2", "Fredoka", "Nunito",
    "Arial Rounded MT Bold", "Segoe UI Variable Display", system-ui,
    -apple-system, Arial, sans-serif;

  /* Numbers that change every frame — the clock, the place, the percentage.
   * Anything using this must also set font-variant-numeric: tabular-nums, or
   * the readout jitters sideways sixty times a second. */
  --font-num: var(--font);

  --fs-3xs: 0.625rem;
  --fs-2xs: 0.6875rem;
  --fs-xs: 0.75rem;
  --fs-sm: 0.8125rem;
  --fs-md: 0.9375rem;
  --fs-lg: 1.125rem;
  --fs-xl: 1.5rem;
  --fs-2xl: 2rem;
  --fs-3xl: clamp(2.25rem, 5vw, 3.75rem);
  --fs-display: clamp(3rem, 10.5vw, 8.5rem);

  --lh-tight: 0.92;
  --lh-snug: 1.15;
  --lh-body: 1.5;

  --tr-title: -0.035em;
  --tr-tight: -0.015em;
  --tr-flat: 0;
  --tr-wide: 0.06em;
  --tr-caps: 0.12em;

  --fw-mid: 600;
  --fw-bold: 800;
  --fw-black: 900;

  /* ---------------------------------------------------------------- space */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 28px;
  --s-7: 36px;
  --s-8: 48px;
  --s-9: 64px;
  /* Distance from the viewport edge to anything pinned to a corner. One token
   * so the four corners of the HUD cannot drift apart. */
  --gutter: clamp(12px, 2.2vw, 26px);

  /* --------------------------------------------------------------- radius */
  --r-xs: 8px;
  --r-sm: 12px;
  --r-md: 16px;
  --r-lg: 22px;
  --r-xl: 30px;
  --r-pill: 999px;

  /* -------------------------------------------------------------- shadow */
  --sh-1: 0 2px 6px rgba(18, 14, 32, 0.22);
  --sh-2: 0 8px 20px rgba(18, 14, 32, 0.3);
  --sh-3: 0 18px 44px rgba(18, 14, 32, 0.38);
  /* The chunky lip under a button. This game's buttons are objects that get
   * pressed down, not rectangles that change colour. */
  --sh-lip: 0 4px 0 rgba(18, 14, 32, 0.34);
  --sh-lip-sm: 0 3px 0 rgba(18, 14, 32, 0.34);
  --sh-text: 0 2px 0 rgba(18, 14, 32, 0.5);
  --ring: 0 0 0 3px rgba(63, 216, 242, 0.55);

  /* -------------------------------------------------------------- motion */
  --dur-1: 110ms;
  --dur-2: 200ms;
  --dur-3: 380ms;
  --dur-4: 700ms;
  --ease-out: cubic-bezier(0.22, 0.68, 0.32, 1);
  --ease-in: cubic-bezier(0.5, 0, 0.9, 0.4);
  /* Overshoots. Everything that appears in this game arrives slightly too far
   * and settles back; that bounce is most of the tone. */
  --ease-pop: cubic-bezier(0.2, 1.5, 0.36, 1);

  /* --------------------------------------------------------------- layers */
  --z-scene: 0;
  --z-hud: 10;
  --z-overlay: 20;
  --z-panel: 30;
  --z-toast: 40;
  --z-modal: 50;

  /* Default size for an inline icon; override per component with --icon-size. */
  --icon-size: 1.15em;

  color-scheme: dark;
}

/* ==================================================================== reset */

*,
*::before,
*::after { box-sizing: border-box; }

html, body { height: 100%; }

html {
  -webkit-text-size-adjust: 100%;
  /* A rubber-band scroll on a full-screen canvas game reads as a broken frame. */
  overscroll-behavior: none;
}

body {
  margin: 0;
  background: var(--sky-grad) fixed;
  color: var(--c-text);
  font-family: var(--font);
  font-size: var(--fs-md);
  font-weight: var(--fw-mid);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-tight);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow: hidden;
  /* Dragging to look must not select the HUD text it passes over. Inputs get
   * it back below — take this away globally and the name field stops working. */
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
}

input, textarea, [contenteditable] {
  user-select: text;
  -webkit-user-select: text;
}

h1, h2, h3, h4, p, figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

button, input, select, textarea {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  background: none;
  border: 0;
  margin: 0;
}

button { cursor: pointer; }
button:disabled { cursor: default; }

a { color: inherit; text-decoration: none; }

canvas { display: block; }
svg { display: block; }
img { max-width: 100%; display: block; }

/* ⚠ The 3D canvas MUST be sized by CSS, and this rule is the only thing doing
 * it. A <canvas> with no CSS size lays itself out at its DRAWING BUFFER size,
 * and the renderer sets that buffer from the canvas's own client size — so
 * without this the two chase each other and the game ends up in a small box in
 * the top-left corner of a full-screen page. (setSize(w, h, false) is called
 * with updateStyle = false on purpose, precisely so the stylesheet stays in
 * charge; that is what makes this rule load bearing rather than decorative.) */
#stage {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  touch-action: none;   /* drag-to-look must not scroll the page under it */
}

/* Keyboard focus is visible; a mouse click never draws a ring. */
:focus { outline: none; }
:focus-visible { outline: none; box-shadow: var(--ring); }

/* ============================================================== type scale */

.t-display {
  font-family: var(--display);
  font-size: var(--fs-display);
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-title);
  text-transform: uppercase;
}

.t-title {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-black);
  line-height: var(--lh-snug);
  letter-spacing: var(--tr-title);
}

.t-head {
  font-size: var(--fs-lg);
  font-weight: var(--fw-black);
  line-height: var(--lh-snug);
  letter-spacing: var(--tr-tight);
}

.t-body {
  font-size: var(--fs-md);
  font-weight: var(--fw-mid);
  line-height: var(--lh-body);
}

/* Small all-caps labels. Caps at 11px need the extra tracking or they set as a
 * grey smear; that is what --tr-caps is for. */
.t-label {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-black);
  line-height: 1.2;
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  color: var(--c-text-dim);
}

.t-num {
  font-family: var(--font-num);
  font-weight: var(--fw-black);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tr-flat);
}

.t-dim { color: var(--c-text-dim); }
.t-mute { color: var(--c-text-mute); }

/* ================================================================= button */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-5);
  border-radius: var(--r-pill);
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  color: var(--c-text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-black);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  white-space: nowrap;
  box-shadow: var(--sh-lip-sm);
  transition:
    transform var(--dur-1) var(--ease-out),
    box-shadow var(--dur-1) var(--ease-out),
    background-color var(--dur-1) var(--ease-out),
    filter var(--dur-1) var(--ease-out);
}

.btn:hover { filter: brightness(1.12); }

/* Pressed means the lip is gone and the button has actually moved down by the
 * height of that lip. Any other press state looks flat next to it. */
.btn:active {
  transform: translateY(3px);
  box-shadow: none;
}

.btn:disabled {
  opacity: 0.45;
  filter: none;
  transform: none;
  box-shadow: var(--sh-lip-sm);
}

.btn--primary {
  background: var(--c-green);
  border-color: rgba(255, 255, 255, 0.28);
  color: var(--c-ink);
  box-shadow: var(--sh-lip), 0 10px 26px rgba(47, 122, 60, 0.35);
}
.btn--primary:active { box-shadow: 0 6px 16px rgba(47, 122, 60, 0.3); }

.btn--pink {
  background: var(--c-pink-hot);
  border-color: rgba(255, 255, 255, 0.28);
  color: var(--c-text);
  box-shadow: var(--sh-lip), 0 10px 26px rgba(255, 61, 132, 0.32);
}
.btn--pink:active { box-shadow: 0 6px 16px rgba(255, 61, 132, 0.28); }

.btn--ghost {
  background: transparent;
  border-color: var(--c-line-strong);
  box-shadow: none;
  color: var(--c-text-dim);
}
.btn--ghost:hover { color: var(--c-text); background: var(--c-surface-3); }
.btn--ghost:active { transform: translateY(1px); box-shadow: none; }

/* The round dark icon buttons. Square by construction so a 24px icon never
 * squashes the circle into an egg. */
.btn--round {
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 50%;
  --icon-size: 20px;
}

.btn--sm {
  padding: var(--s-2) var(--s-4);
  font-size: var(--fs-2xs);
  --icon-size: 16px;
}

.btn--lg {
  padding: var(--s-4) var(--s-8);
  font-size: var(--fs-lg);
  --icon-size: 22px;
}

.btn--block { width: 100%; }

/* Currently selected, in a segmented control or a swatch row. */
.btn.is-on {
  background: var(--c-cream);
  border-color: var(--c-cream);
  color: var(--c-text-on-light);
}

/* =================================================================== card */

.card {
  background: var(--c-surface);
  -webkit-backdrop-filter: var(--blur-card);
  backdrop-filter: var(--blur-card);
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  padding: var(--s-4) var(--s-5);
  color: var(--c-text);
  box-shadow: var(--sh-2);
}

.card--tight { padding: var(--s-3) var(--s-4); border-radius: var(--r-md); }
.card--roomy { padding: var(--s-6) var(--s-7); border-radius: var(--r-xl); }

/* Opaque, for anything the race must not show through. */
.card--solid {
  background: var(--c-surface-solid);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: var(--sh-3);
}

/* =================================================================== chip */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-pill);
  background: var(--c-surface-2);
  border: 1px solid var(--c-line);
  color: var(--c-text-dim);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-black);
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  --icon-size: 15px;
}

.chip--ghost { background: transparent; border-color: var(--c-line-strong); }
.chip--light { background: var(--c-cream); border-color: var(--c-cream); color: var(--c-text-on-light); }

/* A keycap. The bright side is the key itself, so the label beside it stays
 * dim — otherwise a row of legend chips shouts louder than the race. */
.chip--key { padding-left: var(--s-2); }
.chip--key > .chip__key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  padding: 0 var(--s-1);
  border-radius: var(--r-xs);
  background: var(--c-cream);
  color: var(--c-text-on-light);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-black);
  letter-spacing: var(--tr-flat);
  box-shadow: 0 2px 0 rgba(18, 14, 32, 0.45);
}

/* =================================================================== pill */

.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) var(--s-4);
  border-radius: var(--r-pill);
  background: var(--c-surface);
  -webkit-backdrop-filter: var(--blur-card);
  backdrop-filter: var(--blur-card);
  border: 1px solid var(--c-line);
  color: var(--c-text);
  font-size: var(--fs-md);
  font-weight: var(--fw-black);
  letter-spacing: var(--tr-tight);
  /* Live readouts sit in here. Fixed-width digits or the clock shuffles. */
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  box-shadow: var(--sh-2);
  --icon-size: 18px;
}

.pill--lg { padding: var(--s-3) var(--s-5); font-size: var(--fs-lg); --icon-size: 22px; }
.pill--sm { padding: var(--s-1) var(--s-3); font-size: var(--fs-xs); --icon-size: 15px; }
.pill--ghost { background: transparent; box-shadow: none; border-color: var(--c-line-strong); }
.pill--light { background: var(--c-cream); border-color: var(--c-cream); color: var(--c-text-on-light); }

/* A hairline between two halves of a pill — place, then clock. */
.pill__sep {
  width: 1px;
  align-self: stretch;
  margin: 2px 0;
  background: var(--c-line-strong);
}

/* ================================================================== icons */

/* src/ui/icons.js emits 24x24 SVG that inherits currentColor. Size it with
 * --icon-size on the parent, never with a width on the svg itself, or every
 * component ends up with its own magic number. */
.icon {
  display: block;
  flex: 0 0 auto;
  width: var(--icon-size);
  height: var(--icon-size);
  color: inherit;
}

.icon > svg,
svg.icon { width: 100%; height: 100%; display: block; }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ============================================================== keyframes
 *
 * Shared, prefixed, and the only ones any module should need. Reach for one of
 * these before writing a new @keyframes — a module-local `fade-in` overrides
 * everyone else's silently, because keyframe names are global. */

@keyframes ps-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes ps-fade-out { from { opacity: 1; } to { opacity: 0; } }

@keyframes ps-pop-in {
  0% { opacity: 0; transform: scale(0.82); }
  100% { opacity: 1; transform: scale(1); }
}

@keyframes ps-rise-in {
  0% { opacity: 0; transform: translateY(14px); }
  100% { opacity: 1; transform: translateY(0); }
}

@keyframes ps-drop-in {
  0% { opacity: 0; transform: translateY(-18px); }
  100% { opacity: 1; transform: translateY(0); }
}

@keyframes ps-pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.06); }
}

@keyframes ps-spin { to { transform: rotate(360deg); } }

/* For anything that should look like it is floating: a balloon, a card at rest. */
@keyframes ps-drift {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-6px); }
}

@keyframes ps-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-5px); }
  40% { transform: translateX(4px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(2px); }
}

/* A highlight sweeping across a surface. Drive it with background-position on
 * a gradient; do not animate a pseudo-element's `left`, which lays out. */
@keyframes ps-sheen {
  from { background-position: -140% 0; }
  to { background-position: 240% 0; }
}

@keyframes ps-blink { 0%, 55% { opacity: 1; } 56%, 100% { opacity: 0.28; } }

/* The countdown numeral: arrives too big, settles, leaves. */
@keyframes ps-count {
  0% { opacity: 0; transform: scale(1.8); }
  22% { opacity: 1; transform: scale(1); }
  78% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(0.86); }
}

/* Motion is decoration here, never information — every animated element is
 * still readable frozen, so this can turn all of it off outright. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}
