/* Party — local components · DS v2 (墨 + 朱笔).
   Step 6d (2026-08-05): token values (fonts, room colors, --seal, photo grade,
   --radius/--max, shadows, ::selection, .mark/.swash base) split out to
   tokens.css, generated verbatim from kelvin-chen/design/tokens/ds-v2.tokens.css
   — see DESIGN_SYSTEM.md §10. This file keeps only party-local components and
   the back-compat var() aliases below; every page links both files.

   2026-07-04: party is a toggleable 纸(light)/夜(dark) room, not fixed to
   night — set via [data-room] on <html>, same mechanism build/design.html's
   four rooms use. Every page's <html> tag now carries data-room="night" as a
   static default (so tokens.css's [data-room="night"] block always matches,
   even pre-JS): guest/host/index/guide overwrite it early from a saved choice
   or system preference before first paint; TV never overwrites it on purpose
   — see tv.html, its darkness is stage lighting, not a theme preference.
   Canonical spec + live code samples: kelvin-chen/design/DESIGN_SYSTEM.md,
   kelvin-chen/build/design.html — read those before adding anything not here. */

:root {
  /* Back-compat aliases: old call sites (var(--accent)/var(--green)/var(--red)/
     var(--serif)/var(--sans)) still resolve to something DS-correct instead of
     the retired warm-orange system, so nothing silently regresses to old hues
     if a call site got missed during migration. New/updated code should reach
     for --ink/--seal/--muted directly, not through these. */
  --serif: var(--grot);
  --sans: var(--grot);
  --accent: var(--ink);
  --accent-deep: var(--ink);
  --btn-ink: var(--bg);
  --green: var(--ink);
  --red: var(--seal);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--grot);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  position: relative;
}

/* faint paper grain, per DS §1 ("全站铺一层极淡颗粒纹理") */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  opacity: 0.055;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.6'/%3E%3C/svg%3E");
}

h1, h2, h3 { font-family: var(--grot); font-weight: 700; letter-spacing: -0.03em; }
h1 em, h2 em { color: var(--ink); font-style: normal; text-decoration: underline; text-underline-offset: 5px; text-decoration-thickness: 2px; }

.kicker {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Room toggle — light vs. dark, text label not an icon (no sun/moon glyph:
   emoji ban, DS §4). Shows the room you're currently in; tap switches it.
   Not present on tv.html on purpose — see styles.css header note. */
.room-toggle {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 6px 12px;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.room-toggle:hover { border-color: var(--ink); color: var(--ink); }

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 20px;
}

/* 朱笔 mark — inline 焕 + swash, adapts to --ink/--seal automatically (no
   fixed-color SVG asset — this app only has one room per page, but the
   pattern must still work if that ever changes). Copy this markup wherever a
   header/corner mark is needed; see DESIGN_SYSTEM.md §3 + build/design.html
   header for the source. Base .mark/.swash primitive lives in tokens.css —
   this is party's sizing/placement layer on top of it. */
/* Quiet by default (2026-07-04 — was reading as too prominent/too frequent at
   full size and full opacity). This is a signature, not a logo: small, faded,
   never a focal point. Bump size/opacity explicitly only for the rare moment
   that calls for it — don't let a page-specific override quietly re-inflate
   it back to a hero element. */
.brand-mark {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  font-family: var(--song);
  font-weight: 700;
  font-size: 14px;
  color: var(--ink);
  opacity: 0.4;
  pointer-events: none;
}
/* the swash — DS §3's one surviving mark gesture (2026-07-05 amendment retired the
   dot). Static, not self-drawing: this is a "header 常驻" placement (present on
   every scene), and DS §3 calls for those to be a still, already-drawn stroke —
   the self-draw-on-first-view animation is reserved for og/footer placements. */
.brand-mark .swash { display: block; width: 112%; height: 8px; overflow: visible; }
.brand-mark .swash path {
  fill: none;
  stroke: var(--seal);
  stroke-width: 2.4;
  vector-effect: non-scaling-stroke;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* tokens.css's .swash path (the self-drawing base rule) sets stroke-dasharray/
     -dashoffset for the og/footer variant — neutralize both here since this is the
     static "header 常驻" variant (DS §3), which must render as a solid, already-
     drawn stroke, not a dash pattern. */
  stroke-dasharray: none;
  stroke-dashoffset: 0;
}

.btn {
  display: inline-block;
  border: 1.5px solid var(--ink);
  border-radius: 999px;
  padding: 12px 20px;
  font: 500 16px var(--grot);
  letter-spacing: -0.01em;
  background: var(--ink);
  color: var(--bg);
  cursor: pointer;
  transition: background 0.3s ease, color 0.3s ease, transform 0.15s ease, letter-spacing 0.3s ease;
}
.btn:hover { background: transparent; color: var(--ink); letter-spacing: 0.02em; }
.btn:active { transform: scale(0.97); }
.btn.secondary { background: var(--panel2); color: var(--ink); border-color: var(--line); }
.btn.secondary:hover { background: var(--panel2); color: var(--ink); letter-spacing: normal; border-color: var(--ink); }
.btn.ghost { background: transparent; color: var(--ink); }
.btn.ghost:hover { background: var(--ink); color: var(--bg); }
.btn.danger { background: transparent; color: var(--seal); border-color: var(--seal); }
.btn.danger:hover { background: var(--seal); color: #fff; border-color: var(--seal); }
.btn:disabled { opacity: 0.45; cursor: default; }

input[type="text"], input[type="email"], textarea, select {
  width: 100%;
  border: 1.5px solid var(--line);
  border-radius: 14px;
  padding: 12px 14px;
  font: 16px var(--grot);
  background: transparent;
  color: var(--ink);
}
input[type="text"]::placeholder, input[type="email"]::placeholder, textarea::placeholder { color: var(--muted); opacity: 0.8; }
input[type="text"]:focus, input[type="email"]:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--ink);
}

/* Checkboxes/radios — tag-based so every instance across every page picks this up
   with no per-page markup change (DS §6: "checkbox 墨底白勾" / ink-filled + white
   check, no browser-default blue). */
input[type="checkbox"], input[type="radio"] {
  appearance: none;
  width: 18px;
  height: 18px;
  border: 1.5px solid var(--line);
  border-radius: 5px;
  margin: 0;
  display: inline-grid;
  place-items: center;
  vertical-align: middle;
  background: transparent;
  cursor: pointer;
  flex: 0 0 auto;
  transition: background 0.2s ease, border-color 0.2s ease;
}
input[type="radio"] { border-radius: 50%; }
input[type="checkbox"]:checked, input[type="radio"]:checked { border-color: var(--ink); }
input[type="checkbox"]:checked { background: var(--ink); }
input[type="checkbox"]:checked::after {
  content: "✓";
  color: var(--bg);
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
}
input[type="radio"]:checked::after {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--ink);
}

/* Keyboard focus (host page runs on laptops) — ink ring, never a colored glow */
button:focus-visible,
a:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* Photos — tag/class-based so every avatar or reaction photo across every page
   picks up the whisper grade with no per-page markup change (DS §4: components
   reference tokens, never hand-write a filter). Small list thumbnails don't need
   a scroll-driven reveal engine on top of this — the whisper grade is a complete
   resting state on its own; hover is enough bloom for a photo this size. */
.avatar-img, .reaction-photo {
  filter: var(--photo-rest);
  transition: filter 0.4s ease;
}
.avatar-img:hover, .reaction-photo:hover {
  filter: var(--photo-alive);
}

/* Result bars (guest + TV share the shape; TV scales it up). Ranking is
   communicated by length/position, never by hue — DS §6 party primitives. */
.bar-row { margin: 10px 0; }
.bar-label { display: flex; justify-content: space-between; font-size: 15px; margin-bottom: 4px; }
.bar-track { background: var(--panel2); border-radius: 999px; height: 14px; overflow: hidden; }
.bar-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--ink);
  transition: width 0.5s cubic-bezier(0.22, 1, 0.36, 1);
  min-width: 4px;
}

/* Live/currently-open signal — outlet ② (DS §1 "活信号"). Reserve for things
   that are actually still live/changing (an open block, a running countdown).
   A settled "winner"/"correct" after reveal is no longer live — use ink +
   weight for that instead, not this. */
.live-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; background: var(--seal); vertical-align: 1px; }
@media (prefers-reduced-motion: no-preference) {
  .live-dot { animation: pulse 2.2s ease-in-out infinite; }
  @keyframes pulse { 0%, 70%, 100% { transform: scale(1); opacity: 1; } 35% { transform: scale(1.45); opacity: .6; } }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; }
  /* One deliberate exception to the transition kill-switch: the phones-only attention flash
     (R-A4). When navigator.vibrate is unavailable — iOS Safari — that flash is the ONLY signal
     a guest gets that a new question just opened, so it cannot be removed. With transitions
     killed it snapped hard on and off, which is more jarring than the gentle fade guest.html
     asks for under reduced motion, not less. Honouring its transition is the accessible
     outcome here; the keyframe sweep is still suppressed by the `animation: none` above. */
  *:not(.attention-flash) { transition: none !important; }
}

/* ---------- leaderboard podium (R-G6) ----------
   Lives here rather than in guest.html because the markup comes from the SHARED renderer
   public/assets/block-render.js — which host.html's guest preview (R-H13) also renders. Styling
   it inside guest.html's <style> made that preview drift into an unstyled stacked list, which is
   precisely the drift the shared renderer was extracted to prevent. Anything block-render.js
   emits belongs here. Uses only design-system tokens, so both pages get the same result. */
.lb-podium { display: flex; align-items: flex-end; gap: 8px; margin-bottom: 10px; }
.lb-podium .lb-row.podium {
  flex: 1 1 0;
  min-width: 0;
  margin-bottom: 0;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: 6px;
  text-align: center;
  padding: 14px 8px 16px;
}
.lb-podium .lb-row.podium .lb-name { width: 100%; white-space: normal; overflow-wrap: break-word; }
.lb-podium .rank-1 { order: 2; min-height: 148px; background: color-mix(in srgb, var(--ink) 9%, var(--panel)); }
.lb-podium .rank-2 { order: 1; min-height: 122px; }
.lb-podium .rank-3 { order: 3; min-height: 104px; }
.lb-podium .rank-1 .lb-medal { font-size: 30px; }
.lb-podium .rank-1 .lb-name { font-size: 16px; font-weight: 800; }
.lb-podium .rank-1 .lb-score { font-size: 15px; font-weight: 700; color: var(--ink); }

/* ---------- correct-answer mark + winning row ----------
   Emitted by public/assets/block-render.js (quiz/poll bars, person tallies), so BOTH guest.html
   and host.html's guest preview render it — shared markup, shared CSS, same rule as the podium
   above. Ink + weight rather than --seal: the seal is reserved for live signals, and a settled
   correct answer is a fact, not a signal.
   guest.html withholds this appearance until its staged reveal reaches it (R-G5's ordering); the
   preview, which never animates, gets the settled look immediately. */
.bar-row .bar-label .correct-check { color: var(--ink); font-weight: 700; }
.bar-row.winner {
  border-radius: 10px;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  box-shadow: inset 0 0 0 1.5px var(--ink);
  /* The ring is drawn on the row's own box, and .bar-row carries no padding — so without this the
     stroke lands hard against the first glyph of the label and against the percentage on the right
     (visible on every winning poll/quiz row, guest and TV alike). Pad the row, then pull the same
     amount back out horizontally so the track still lines up with the non-winning rows above and
     below it rather than stepping inward.
     Vertical margin is deliberately NOT set here: tv.html scales .bar-row's margin in vh, and a px
     value at this specificity would silently override the TV's rhythm. */
  padding: 6px 10px;
  margin-left: -10px;
  margin-right: -10px;
}
