/* ---------- Tokens ---------- */
:root {
  /* Surfaces */
  --bg: #000;
  --bg-elev: #141821;
  --bg-elev-2: #1a2030;
  /* Board surface sits behind the colour swatches. Kept distinct from
     --bg-elev-2 so the axis label boxes (which still use --bg-elev-2)
     stand out as lighter chrome against the darker board. */
  --surface-board: #0c1018;
  --surface-quiet: #11151d;
  --surface-glass: rgba(20, 24, 33, 0.72);

  /* Hairlines */
  --border: rgba(255, 255, 255, 0.07);
  --border-strong: rgba(255, 255, 255, 0.12);

  /* Type */
  --text: #ecf0f7;
  --muted: #8b95a7;
  --muted-soft: #5d6678;

  /* Accents */
  --accent: #4dd9c0;
  --accent-soft: rgba(77, 217, 192, 0.16);
  --danger: #ff5970;
  --success: #4dd9c0;

  /* Whimsy palette — drives the title, blob, tab gradient, chip dots, and
     the rainbow seam above the footer. Six stops on a single gradient keep
     each transition gradual rather than banded. */
  --c-pink:   #ff6bb5;
  --c-purple: #b06bff;
  --c-yellow: #ffd76b;
  --c-orange: #ff9d4d;
  --c-mint:   #4dd9c0;
  --c-blue:   #6aa9ff;

  --grad-rainbow: linear-gradient(
    90deg,
    var(--c-yellow)  0%,
    var(--c-orange) 18%,
    var(--c-pink)   38%,
    var(--c-purple) 58%,
    var(--c-blue)   78%,
    var(--c-mint)  100%
  );
  --grad-tab: linear-gradient(135deg, var(--c-pink), var(--c-purple) 55%, var(--c-blue));

  /* Geometry */
  --radius-sm: 10px;
  --radius: 14px;
  --radius-lg: 20px;
  --radius-xl: 26px;
  --radius-pill: 999px;
  --gap: clamp(6px, 1.2vw, 12px);

  /* Elevation */
  --shadow-soft: 0 8px 24px rgba(0, 0, 0, 0.35);
  --shadow-pop: 0 16px 40px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.3);
  --shadow-inset: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  --ring: 0 0 0 1px rgba(255, 255, 255, 0.04) inset;

  /* Motion */
  --ease-out: cubic-bezier(0.2, 0.7, 0.2, 1);
  --ease-spring: cubic-bezier(0.2, 1.5, 0.4, 1);

  /* Photo height ceiling — phones stay generous, desktop tightens below. */
  --photo-max-h: 480px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: geometricPrecision;
  font-feature-settings: "ss01", "cv11";
  font-variant-ligatures: contextual;
}

/* Subtle grain over the page — keeps large flat black areas from looking
   plastic. Painted with a tiny SVG noise so we don't ship an asset. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  opacity: 0.04;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='0.7'/></svg>");
}
body > * { position: relative; z-index: 1; }

img {
  image-rendering: -webkit-optimize-contrast;
  image-rendering: high-quality;
}

body {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

/* ---------- Top bar ---------- */
.topbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 18px;
  padding: 14px clamp(16px, 3vw, 32px);
  position: sticky;
  top: 0;
  z-index: 20;
  /* Solid page bg on its own layer — blends with the body at rest,
     occludes content slipping behind it on scroll. */
  background: var(--bg);
}

.title {
  margin: 0;
  font-family: "Cormorant Garamond", "Iowan Old Style", Georgia, "Times New Roman", serif;
  font-size: clamp(26px, 3.4vw, 38px);
  font-weight: 700;
  letter-spacing: 0.005em;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
}

/* Brand blob: a metaball-style lava lamp painted as five SVG circles inside a
   gooey filter. JS (js/blob.js) drifts four of the circles with non-repeating
   sine sums and periodically pulls the fifth one outward like a lava-lamp
   drip. The fill cycles through the rainbow on a separate CSS timer. */
.title-blob {
  width: 1.6em;
  height: 1.6em;
  flex: none;
  display: block;
  /* Let the drop circle escape the viewBox edge during its peak without being
     clipped to the box. */
  overflow: visible;
}

.title-blob-shape {
  fill: #ff6bb5;
  animation: blob-color 16s linear infinite;
}

/* Solid colour fades from one stop to the next around the full
   rainbow, then loops smoothly back to the starting hue. */
@keyframes blob-color {
  0%   { fill: #ff6bb5; }
  16%  { fill: #b06bff; }
  33%  { fill: #6aa9ff; }
  50%  { fill: #4dd9c0; }
  66%  { fill: #ffd76b; }
  83%  { fill: #ff9d4d; }
  100% { fill: #ff6bb5; }
}

/* Title wordmark — creamy off-white base with the faintest pastel gradient
   washed over the letterforms. The cream layer wins most of the visual
   weight; the rainbow sits on top at low opacity for a hint of colour only
   where it brushes against a stroke. */
.title-text {
  /* Cream base with a rainbow tint that ramps in across the word:
     "col" 0%, "ora" 1-5%, "tion" 6-10%. The gradient stops align to
     the section boundaries so the rainbow stays absent on "col",
     hints in on "ora", and reaches ~10% by the end of "tion". */
  background:
    linear-gradient(
      90deg,
      rgba(255, 107, 181, 0.00)  0%,
      rgba(255, 107, 181, 0.00) 30%,
      rgba(176, 107, 255, 0.03) 45%,
      rgba(106, 169, 255, 0.05) 60%,
      rgba(77,  217, 192, 0.08) 80%,
      rgba(255, 215, 107, 0.10) 100%
    ),
    linear-gradient(#f6efdc, #f6efdc);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  text-shadow: none;
  /* Bulk up each glyph by a fraction of an em so the wordmark reads as a
     heavier weight than Cormorant's max 700. paint-order ensures the cream
     stroke sits behind the gradient-clipped fill, so it only widens the
     outline rather than overpainting the rainbow tint. */
  -webkit-text-stroke: 0.025em #f6efdc;
  paint-order: stroke fill;
}
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .title-text {
    background: none;
    -webkit-text-fill-color: currentColor;
    color: #f6efdc;
  }
}

@media (prefers-reduced-motion: reduce) {
  .title-blob-shape { animation: none; }
}

/* ---------- Scoreboard chips ---------- */
.scoreboard {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}

.chip {
  display: inline-flex;
  align-items: center;
  font-size: clamp(11px, 1.4vw, 13px);
  font-weight: 700;
  letter-spacing: 0.01em;
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  border: 0;
  white-space: nowrap;
}

#round-chip    { background: #dcc4f3; color: #5a1cc0; }
#streak-chip   { background: #f8c4d8; color: #b01a5e; }
#best-chip     { background: #f8e3a1; color: #7a5500; }
.chip--guesses { background: #b3e8d7; color: #0a6a58; }
.chip--skips   { background: #fbc9a3; color: #a64d0a; }

.chip--skips.chip--depleted {
  background: #ced3dd;
  color: #5a626e;
}

/* ---------- Tabs ---------- */
.tabs[hidden],
.tab[hidden] {
  /* `display: inline-grid` / `inline-flex` below outrank the UA `[hidden]`
     rule, so honour the attribute explicitly when JS hides an unavailable
     tab (e.g. items.json failed to load). */
  display: none;
}

.tabs {
  position: relative;
  display: inline-grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
  padding: 4px;
  margin: clamp(18px, 3vw, 28px) auto 0;
  background: var(--bg-elev);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-soft);
  align-self: center;
  width: max-content;
  max-width: calc(100% - 24px);
  isolation: isolate;
}

/* Sliding fill — half the track wide, translates between the two slots as
   the active mode changes. Anchored at top/left with calc()s that account
   for the 4px container padding so the pill fits cleanly inside the track. */
.tabs::before {
  content: "";
  position: absolute;
  top: 4px;
  left: 4px;
  width: calc(50% - 4px);
  height: calc(100% - 8px);
  background: var(--text);
  border-radius: var(--radius-pill);
  transform: translateX(0);
  transition: transform 320ms var(--ease-out);
  z-index: 0;
  pointer-events: none;
}

.tabs[data-active="grid"]::before { transform: translateX(100%); }

@media (prefers-reduced-motion: reduce) {
  .tabs::before { transition: none; }
}

.tab {
  appearance: none;
  background: transparent;
  color: var(--text);
  border: 0;
  border-radius: var(--radius-pill);
  padding: 8px 22px;
  font: inherit;
  font-weight: 800;
  font-size: clamp(13px, 1.7vw, 15px);
  cursor: pointer;
  letter-spacing: 0.01em;
  position: relative;
  z-index: 1;
  min-height: 38px;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: color 240ms var(--ease-out);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.tab:hover { color: var(--text); }

.tab--active,
.tab--active:hover,
.tab--active:focus,
.tab--active:focus-visible {
  color: #0e1116;
  background: transparent;
  transform: none;
}

.tab:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

/* ---------- Stage ---------- */
.stage {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(12px, 2.2vw, 22px);
  padding: clamp(14px, 2.8vw, 28px) clamp(14px, 3vw, 28px);
  width: 100%;
  max-width: 760px;
  margin: 0 auto;
  transition: opacity 200ms var(--ease-out);
}

.stage--switching { opacity: 0; }

/* End-of-day layout: collapse the stage to just the share card and the three
   share actions, centered. Anything else (photo card, title, board, quad,
   countdown, primary actions) is removed from layout entirely so there's no
   empty 4:3 slot or leftover gap above the share card. The `!important`
   defeats the desktop grid layout defined further down. The .status row
   stays present-but-empty so the shared-view caption can still render —
   when its text is empty it collapses to nothing via `:empty` below. */
.stage--finished {
  display: flex !important;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: clamp(14px, 2vw, 22px);
}
.stage--finished > :not(.share):not(.share-actions):not(.status) {
  display: none !important;
}
.stage--finished > .status:empty {
  display: none !important;
}

@media (prefers-reduced-motion: reduce) {
  .stage { transition: none; }
  .stage--switching { opacity: 1; }
}

/* ---------- Photo card ---------- */
.card {
  width: 100%;
  display: flex;
  justify-content: center;
}

.photo {
  margin: 0;
  position: relative;
  /* Pick the smaller of the column width and the width that would make the
     photo exactly --photo-max-h tall at 4:3. The aspect-ratio rule then
     derives height = width × 3/4, which is guaranteed ≤ --photo-max-h. */
  width: min(100%, calc(var(--photo-max-h) * 4 / 3));
  aspect-ratio: 4 / 3;
  /* Dark background instead of white. The rounded-corner clip on the
     <img> antialiases against this colour, and PNG/WebP source images
     with transparent corners reveal it directly — a white fill produced
     a visible bright sliver in each inner corner. */
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  /* No drop shadow under the photo — the rounded frame sits flat on the
     dark canvas. Keep only the inner top hairline for a faint bevel that
     defines the rounded edge against the page. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
  /* `none` would block vertical page scrolling that starts on the photo;
     `pan-y` previously left iOS double-tap-to-zoom enabled, which could
     defeat the grayscale-until-revealed mechanic. `pan-y pinch-zoom` keeps
     scroll working but suppresses the implicit double-tap-zoom gesture. */
  touch-action: pan-y pinch-zoom;
  user-select: none;
  -webkit-user-select: none;
  will-change: transform;
  transition: box-shadow 240ms var(--ease-out);
}

.photo.revealed {
  /* Reveal keeps the same flat frame — no drop shadow, just a faint
     hairline ring so the moment of reveal still feels intentional. */
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.06),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

.photo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1) contrast(1.05);
  image-rendering: -webkit-optimize-contrast;
  /* Block the iOS Safari long-press callout / drag preview that would
     otherwise reveal the source image in full color, defeating the
     grayscale-until-revealed mechanic. */
  -webkit-touch-callout: none;
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
}

/* Transition only on the reveal direction. Without this scoping, removing
   `revealed` between rounds animates color → grayscale over 600ms, which
   briefly shows the next photo (or lingering old photo) in color. */
.photo.revealed img {
  filter: grayscale(0) contrast(1);
  transition: filter 600ms var(--ease-out);
}

/* Character title / round prompt placed between photo and grid. Reserved
   height keeps the layout from jumping when the name appears on reveal.
   Outfit Regular 400 reads as the calm caption font across both prompt
   ("What color is X?") and reveal states. */
.character-title {
  margin: 0;
  width: 100%;
  text-align: center;
  font-family: "Outfit", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 400;
  font-size: clamp(22px, 4.2vw, 36px);
  letter-spacing: -0.015em;
  line-height: 1.15;
  color: var(--text);
  min-height: 1.2em;
  text-rendering: geometricPrecision;
  text-wrap: balance;
}

/* ---------- Board ---------- */
.board {
  display: grid;
  grid-template-columns: 28px 1fr;
  grid-template-rows: 28px 1fr;
  width: 100%;
  gap: var(--gap);
}

/* `display: grid` above has the same specificity as the UA `[hidden]`
   rule, so we need an explicit override to honor the `hidden` attribute
   when the round is using the quad board instead. */
.board[hidden],
.quad[hidden] {
  display: none;
}

.board-corner { grid-row: 1; grid-column: 1; }
.col-headers {
  grid-row: 1; grid-column: 2;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--gap);
}
.row-headers {
  grid-row: 2; grid-column: 1;
  display: grid;
  grid-template-rows: repeat(4, 1fr);
  gap: var(--gap);
}

/* Dark-theme-friendly axis headers. Light text on a subtle gradient slab so
   they read as part of the board instead of pure-white stickers floating on
   top of it. */
.hdr {
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.01)),
    var(--bg-elev-2);
  color: var(--text);
  border-radius: 7px;
  font-weight: 800;
  font-size: clamp(11px, 1.6vw, 14px);
  border: 1px solid var(--border-strong);
  box-shadow: var(--ring), 0 1px 0 rgba(0, 0, 0, 0.25);
  min-height: 22px;
  transition: background 220ms var(--ease-out), color 220ms var(--ease-out), border-color 220ms var(--ease-out), box-shadow 220ms var(--ease-out);
}

/* Negative hint: this row/column is NOT the answer. Surfaced after the
   second wrong guess — muted red with a strikethrough so it reads as
   "ruled out" rather than "is the answer". Narrows 16 candidates to 12
   without ever pinpointing the exact cell. */
.hdr--hint-negative {
  background: linear-gradient(180deg, rgba(60, 22, 24, 0.95), rgba(40, 14, 16, 0.95));
  color: rgba(255, 170, 170, 0.55);
  border-color: rgba(190, 70, 75, 0.55);
  text-decoration: line-through;
  text-decoration-thickness: 2px;
  text-decoration-color: rgba(230, 110, 115, 0.85);
  font-weight: 900;
  box-shadow:
    0 0 0 1px rgba(190, 70, 75, 0.35),
    0 0 10px rgba(190, 70, 75, 0.35);
}

.grid {
  grid-row: 2; grid-column: 2;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(4, 1fr);
  gap: var(--gap);
  padding: var(--gap);
  /* Darker than the page so the swatches read crisply, and darker than the
     header labels so the axis chrome separates visually. */
  background: var(--surface-board);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--ring), 0 2px 10px rgba(0, 0, 0, 0.25);
}

.cell {
  appearance: none;
  /* Color swatches are the game — no border, no inset shadow, no gloss.
     The grid gap separates them; any tint here would distort the answer. */
  border: 0;
  border-radius: 8px;
  aspect-ratio: 1 / 1;
  cursor: pointer;
  position: relative;
  padding: 0;
  touch-action: manipulation;
  transition:
    transform 120ms var(--ease-out),
    box-shadow 180ms var(--ease-out),
    filter 180ms var(--ease-out);
  -webkit-tap-highlight-color: transparent;
}

/* ---------- Quad board (4 distinct color choices, 1 guess) ---------- */
.quad {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-template-rows: repeat(2, 1fr);
  gap: clamp(10px, 1.8vw, 16px);
  padding: clamp(12px, 1.8vw, 18px);
  width: 100%;
  background: var(--surface-board);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--ring), 0 2px 10px rgba(0, 0, 0, 0.25);
}

.quad-cell {
  border-radius: 14px;
}

@media (hover: hover) {
  .cell:hover {
    transform: scale(1.04);
    /* Outer ring + drop only — no inset shadow that would tint the swatch. */
    box-shadow:
      0 0 0 2px rgba(255, 255, 255, 0.55),
      0 6px 18px rgba(0, 0, 0, 0.45);
    z-index: 1;
  }
}

.cell:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 2px;
  z-index: 2;
}

.cell:active { transform: scale(0.96); }

/* Armed: finger is down on this swatch but we haven't committed yet. Lift
   on the same swatch to lock in; drag off, scroll, or hold too long to
   cancel. Stronger ring than :active so the player can see which swatch
   is about to be selected even while their finger is occluding it. */
.cell--armed {
  transform: scale(0.96);
  box-shadow:
    0 0 0 2px rgba(255, 255, 255, 0.75),
    0 6px 16px rgba(0, 0, 0, 0.45);
  z-index: 2;
}

/* Wrong: dim with X */
.cell--wrong {
  cursor: default;
}
.cell--wrong::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(45deg, transparent calc(50% - 2px), #fff calc(50% - 2px), #fff calc(50% + 2px), transparent calc(50% + 2px)),
    linear-gradient(-45deg, transparent calc(50% - 2px), #fff calc(50% - 2px), #fff calc(50% + 2px), transparent calc(50% + 2px)),
    rgba(0, 0, 0, 0.55);
  border-radius: 7px;
}

/* Correct: glow + check */
.cell--correct {
  cursor: default;
  box-shadow:
    0 0 0 3px #fff,
    0 0 0 6px var(--success),
    0 8px 22px rgba(77, 217, 192, 0.45),
    0 6px 18px rgba(0, 0, 0, 0.5);
  z-index: 3;
  animation: pop 420ms var(--ease-spring);
}
.cell--correct::after {
  content: '✓';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: clamp(20px, 4vw, 30px);
  font-weight: 900;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.7);
}

.cell--dim {
  opacity: 0.5;
  filter: saturate(0.7);
  cursor: default;
}

/* ---------- Share card ---------- */
.share {
  width: 100%;
  display: flex;
  justify-content: center;
}

.share-card {
  width: 100%;
  max-width: 540px;
  height: auto;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-pop);
  background: var(--bg);
}

.countdown {
  margin: 0;
  text-align: center;
  font-size: clamp(12px, 1.6vw, 14px);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  min-height: 1.4em;
}

/* ---------- Status / actions ---------- */
.status {
  text-align: center;
  font-family: "Outfit", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-weight: 300;
  font-size: clamp(13px, 1.8vw, 16px);
  color: var(--muted);
  min-height: 1.4em;
  line-height: 1.45;
}

.actions {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
  /* Pull the action row up so Skip lands close to the grid/photo rather
     than floating below the reserved-height status + countdown rows. */
  margin-top: clamp(-22px, -2vw, -10px);
}

.btn {
  appearance: none;
  border: 1px solid var(--border-strong);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01)),
    var(--bg-elev);
  color: var(--text);
  font-weight: 700;
  padding: 12px 24px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  font-size: 15px;
  min-height: 48px;
  letter-spacing: 0.01em;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  box-shadow: var(--ring), 0 1px 0 rgba(0, 0, 0, 0.25);
  transition:
    transform 140ms var(--ease-out),
    box-shadow 220ms var(--ease-out),
    background 220ms var(--ease-out),
    color 220ms var(--ease-out),
    border-color 220ms var(--ease-out);
}

.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn--primary {
  background: var(--grad-tab);
  color: #0e1116;
  border-color: transparent;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.18);
  box-shadow:
    0 10px 26px rgba(176, 107, 255, 0.40),
    0 2px 6px rgba(255, 107, 181, 0.25),
    inset 0 0 0 1px rgba(255, 255, 255, 0.20);
}
.btn--primary:hover {
  box-shadow:
    0 14px 32px rgba(176, 107, 255, 0.50),
    0 2px 6px rgba(255, 107, 181, 0.28),
    inset 0 0 0 1px rgba(255, 255, 255, 0.24);
}

.btn--ghost {
  background: transparent;
  color: var(--muted);
  border-color: var(--border);
  box-shadow: none;
}
.btn--ghost:hover { color: var(--text); border-color: var(--border-strong); }

/* Skip button: flat amber pill — solid fill with no drop shadow or inner
   bevel, sized slightly larger than the default pill so players can find
   it without it competing with the primary "Next" gradient. */
#skip-btn.btn,
#skip-btn.btn--ghost {
  background: #ffb24d;
  color: #2a1700;
  border-color: transparent;
  text-shadow: none;
  font-weight: 800;
  padding: 14px 30px;
  min-height: 52px;
  font-size: 16px;
  box-shadow: none;
}
#skip-btn.btn:hover {
  color: #2a1700;
  background: #ffbf66;
  box-shadow: none;
}
#skip-btn.btn:active {
  background: #f5a236;
  box-shadow: none;
}

.btn:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

.btn[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

/* Three-up share row that appears when the daily run finishes. Stacks on
   narrow phones, sits in a single row on tablets and up. */
.share-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  width: 100%;
}
.share-actions[hidden] { display: none; }
.share-actions .btn { flex: 1 1 140px; min-width: 140px; max-width: 220px; }

/* ---------- Footer ---------- */
.bottombar {
  text-align: center;
  padding: 18px 16px calc(18px + env(safe-area-inset-bottom));
  color: var(--muted);
  font-size: 12px;
  letter-spacing: 0.02em;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  position: relative;
  margin-top: clamp(20px, 4vw, 36px);
}

.btn--hard-reset {
  font-size: 11px;
  padding: 6px 12px;
  min-height: 0;
  opacity: 0.6;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.btn--hard-reset:hover { opacity: 1; }
.btn--hard-reset-armed {
  opacity: 1;
  color: #2a1700;
  background: #ffb24d;
  border-color: transparent;
}

/* A thin rainbow accent above the footer ties the chrome together. */
.bottombar::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: -1px;
  height: 2px;
  background: var(--grad-rainbow);
  opacity: 0.55;
  pointer-events: none;
}

/* ---------- Animations ---------- */
@keyframes pop {
  0%   { transform: scale(0.85); }
  60%  { transform: scale(1.18); }
  100% { transform: scale(1); }
}

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

.shake { animation: shake 360ms ease; }

/* ---------- Spinner + inline retry ---------- */
.spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  margin-right: 8px;
  vertical-align: -2px;
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 800ms linear infinite;
}

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

.btn--inline {
  padding: 4px 12px;
  min-height: 0;
  font-size: inherit;
  vertical-align: baseline;
}

.status-detail {
  margin-top: 6px;
  font-size: 12px;
  opacity: 0.7;
}

/* ---------- Toast ---------- */
.toast {
  position: fixed;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom) + 24px);
  transform: translate(-50%, 12px);
  max-width: min(90vw, 420px);
  padding: 12px 20px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01)),
    var(--bg-elev);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  box-shadow: var(--shadow-pop), var(--ring);
  opacity: 0;
  pointer-events: none;
  transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out);
  z-index: 50;
}

.toast--visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; }
  .toast { transition: opacity 200ms ease; transform: translate(-50%, 0); }
}

/* ---------- Breakpoints ---------- */
/* Narrow phones: tighten topbar padding + chip size so the scoreboard wraps
   gracefully under the title without looking cramped. */
@media (max-width: 480px) {
  .topbar {
    padding: 12px 14px;
    gap: 8px 10px;
  }
  .scoreboard { width: 100%; }
  .chip {
    padding: 5px 10px;
    font-size: 11px;
  }
  .tab { padding: 10px 18px; min-height: 44px; }
  .character-title { font-size: clamp(20px, 6vw, 26px); }
  .btn { padding: 12px 20px; min-height: 48px; }
  .actions { gap: 10px; }
  .share-actions .btn { flex: 1 1 100%; min-width: 0; max-width: none; }
}

/* Phones (anything below the desktop two-column threshold): bound the
   photo height so the colour board can fit within the same viewport,
   removing the scroll that previously sat between photo and grid. The
   inner `min()` reserves ~440px of vertical room for the title, board,
   gaps, and stage padding so the photo yields space on shorter phones
   instead of crowding the swatches off-screen; the outer `max()` keeps
   the photo from collapsing to nothing in landscape orientation, where
   it's better to allow a small scroll than show a sliver. dvh units
   stay stable across iOS Safari's address-bar collapse. */
@media (max-width: 759px) {
  :root {
    --photo-max-h: max(180px, min(40dvh, 100dvh - 440px));
  }
}

/* Slightly larger axis headers on roomy phones / tablets. */
@media (min-width: 700px) {
  .board { grid-template-columns: 34px 1fr; grid-template-rows: 34px 1fr; }
  .hdr { font-size: 14px; min-height: 26px; }
}

/* Tablet + desktop: photo on the left, title + colour board on the right.
   Two columns so all three pieces (4:3 image, subtitle, colour options)
   stay visible together without scrolling. The columns collapse back to a
   single stack on phones, where the linear flow already works. */
@media (min-width: 760px) {
  :root {
    /* Bound to the smaller of "more than half the viewport" and 480px so
       the photo never dominates a tall desktop monitor. */
    --photo-max-h: min(56dvh, 480px);
  }
  .stage {
    max-width: 1120px;
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    grid-template-areas:
      "photo     title"
      "photo     board"
      "status    status"
      "share     share"
      "countdown countdown"
      "actions   actions"
      "shareacts shareacts";
    column-gap: clamp(22px, 3.4vw, 44px);
    row-gap: clamp(10px, 1.6vw, 18px);
    align-items: start;
  }
  /* Bottom-align the photo and the colour board so their lower edges meet
     across the column gap — looks like one cohesive widget instead of two
     stacked strips with mismatched baselines. The title floats up to the
     start of its row above the board. */
  .card            { grid-area: photo; align-self: end; }
  /* Keep the prompt centered above the colour grid on desktop too —
     it reads as the question to the column it sits above, not as a
     left-anchored caption next to the photo. */
  .character-title { grid-area: title; align-self: end; text-align: center; }
  .board           { grid-area: board; align-self: end; max-width: 100%; }
  .quad            { grid-area: board; align-self: end; max-width: 100%; }
  .status          { grid-area: status; }
  .share           { grid-area: share; }
  .countdown       { grid-area: countdown; }
  .actions         { grid-area: actions; }
  .share-actions   { grid-area: shareacts; }
}

/* Wide desktop polish — slightly larger title and a touch more space between
   the column-pair and the action row below. */
@media (min-width: 1024px) {
  .character-title { font-size: clamp(26px, 2.4vw, 38px); }
  .stage { padding-block: clamp(20px, 2.5vw, 36px); }
}
