/* Reconnect banner — fixed above everything, hidden by default */
.reconn-banner {
  position: fixed; top: 0; left: 0; right: 0; z-index: 9999;
  background: #f59e0b; color: #1b1530;
  font-family: var(--font); font-weight: 700; font-size: 14px;
  text-align: center; padding: 10px 16px;
  animation: reconnPulse 1.4s ease-in-out infinite;
}
@keyframes reconnPulse { 0%,100%{opacity:1} 50%{opacity:.7} }

/* Join/rejoin request banner — floats above whatever Game Night screen is active (like
   .reconn-banner) since a request can arrive mid-turn, mid-judge, anywhere. Host-only. */
.join-requests {
  position: fixed; top: calc(10px + env(safe-area-inset-top)); left: 12px; right: 12px; z-index: 9998;
  display: flex; flex-direction: column; gap: 8px;
}
/* [hidden] and an explicit `display` on the same element tie in specificity, and author CSS
   wins ties over the UA stylesheet — without this override .join-requests{display:flex} beats
   [hidden]'s display:none and the "hidden" banner stays visible. Same pattern as .overlay[hidden]. */
.join-requests[hidden] { display: none; }
.join-request-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  background: rgba(30, 27, 46, 0.92); backdrop-filter: blur(8px);
  border: 1px solid rgba(255,255,255,0.14); border-radius: 16px; padding: 10px 14px;
  box-shadow: 0 10px 22px -10px rgba(0,0,0,0.5);
  animation: cardIn 0.3s ease both;
}
.join-request-info { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; }
.join-request-name { color: #fff; font-weight: 600; font-size: 14px; }
.join-request-meta { color: rgba(255,255,255,0.6); font-size: 12px; }
.join-request-btn { padding: 8px 14px; font-size: 13px; margin-top: 0; width: auto; touch-action: manipulation; }

/* Curiosity teaser on the joiner's own waiting screen (#screen-net-pending) — a peek at the
   room's already-public state (roster/tally/round), see renderJoinPeek in app.js. */
.join-peek {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  margin-top: 4px; padding: 14px 18px;
  background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12);
  border-radius: 16px; max-width: 320px;
}
.join-peek[hidden] { display: none; }  /* see .join-requests[hidden] for why this override is required */
.join-peek-roster { font-size: 22px; line-height: 1.2; letter-spacing: 2px; }
.join-peek-line { color: rgba(255,255,255,0.7); font-size: 13px; margin: 0; text-align: center; }
.join-peek-status { color: rgba(255,255,255,0.9); font-weight: 600; }
.join-peek-leader[hidden] { display: none; }

:root {
  --bg-1: #1549C4;
  --bg-2: #0c35a0;
  --bg-glow: #1e5ce6;
  --ink: #1E1B2E;
  --cream: #FFF7ED;
  --card: #FFFFFF;
  --coral: #FF5E5B;
  --tangerine: #FF9F1C;
  --grape: #9D4EDD;
  --grape-deep: #7B2FF7;
  --mint: #2EC4B6;
  --muted: #6B6580;
  --gold: #FFE234;
  --line: rgba(30, 27, 46, 0.08);
  --glass-fill: rgba(255, 255, 255, 0.12);
  --glass-stroke: rgba(255, 255, 255, 0.18);
  --grad: linear-gradient(120deg, var(--gold), var(--tangerine));
  --grad-warm: linear-gradient(120deg, var(--gold), var(--tangerine));
  --grad-title: linear-gradient(120deg, var(--gold), #FFFFFF);
  --shadow: 0 18px 40px -12px rgba(30, 27, 46, 0.35);
  --e-primary: 0 10px 22px -8px rgba(255, 226, 52, 0.45);
  --e-lit: 0 14px 26px -6px rgba(255, 226, 52, 0.55);
  --radius: 26px;
  --font: "Fredoka", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --font-display: "Lilita One", "Fredoka", -apple-system, sans-serif;
}

* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; }
body {
  font-family: var(--font);
  background: var(--bg-1) url("bg-ambient.jpg") center top / cover no-repeat;
  color: var(--ink);
  overflow: hidden;
  display: flex;
  justify-content: center;
  overscroll-behavior: none;
  -webkit-text-size-adjust: 100%;
  -webkit-user-select: none;
  user-select: none;
}

#app {
  width: 100%;
  max-width: 480px;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}

/* ---------- Topbar ---------- */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: calc(16px + env(safe-area-inset-top)) 20px 10px;
  flex-shrink: 0;
}
.brand { background: none; border: none; display: flex; align-items: center; gap: 7px; cursor: pointer; }
.brand-emoji { font-size: 22px; }
.brand-name {
  font-family: var(--font-display); font-size: 24px; font-weight: 400; letter-spacing: 0;
  background: var(--grad-title);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  text-shadow: none;
}
.streak {
  display: flex; align-items: center; gap: 5px;
  background: var(--glass-fill);
  border: 1px solid var(--glass-stroke);
  backdrop-filter: blur(8px);
  color: #fff; font-family: var(--font-display); font-weight: 400; font-size: 15px;
  padding: 6px 13px; border-radius: 999px;
  box-shadow: none;
}
.streak .flame { filter: drop-shadow(0 0 6px rgba(255, 226, 52, 0.6)); }
.flame { font-size: 15px; }
.topbar-right { display: flex; align-items: center; gap: 10px; }
.lang-btn {
  background: rgba(255, 255, 255, 0.14); border: none; color: #fff;
  font-size: 17px; line-height: 1; width: 34px; height: 34px; border-radius: 999px;
  display: flex; align-items: center; justify-content: center; padding: 0; cursor: pointer;
}
.lang-btn svg { display: block; }
.lang-btn svg circle, .lang-btn svg path { fill: none; stroke: #fff; stroke-width: 1.8; }
#muteBtn.is-muted path#muteWave { display: none; }
#muteBtn.is-muted path#muteX { display: block !important; }
.lang-list { display: flex; flex-direction: column; gap: 8px; width: 100%; }
/* Picker card is white (var(--card)), so .btn-soft's white-on-white is invisible —
   force dark-on-light here, with coral for the active locale. */
.lang-list .lang-opt { width: 100%; background: #F3EFFA; color: var(--ink); font-weight: 600; }
.lang-list .lang-opt.active { background: var(--coral); color: #fff; }

/* ---------- Screens ---------- */
.screen {
  position: absolute;
  top: calc(64px + env(safe-area-inset-top));
  left: 0;
  right: 0;
  bottom: 0;
  padding: 8px 22px calc(28px + env(safe-area-inset-bottom));
  display: none;
  flex-direction: column;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  animation: screenIn 0.42s cubic-bezier(0.16, 1, 0.3, 1);
}
.screen.active { display: flex; }
@keyframes screenIn {
  from { opacity: 0; transform: translateY(14px) scale(0.99); }
  to   { opacity: 1; transform: none; }
}

/* ---------- Today ---------- */
.day-tag {
  align-self: center;
  background: rgba(255, 255, 255, 0.12);
  color: #fff; font-weight: 600; font-size: 13px;
  letter-spacing: 1.5px; text-transform: uppercase;
  padding: 6px 14px; border-radius: 999px; margin-bottom: 6px;
}
.day-tag.small { letter-spacing: 1px; font-size: 11px; padding: 5px 12px; margin: 0; align-self: auto; }
.grad {
  background: linear-gradient(120deg, var(--tangerine), var(--coral), var(--grape));
  -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
}
.social-proof { text-align: center; color: rgba(255, 255, 255, 0.55); font-size: 13px; min-height: 18px; }

/* Home screen — equal-weight modes */
.home-top { display: flex; flex-direction: column; align-items: center; }
.home-kicker { font-size: 13px; color: rgba(255,255,255,0.5); margin: 0 0 2px; text-align: center; }
.home-modes { display: flex; flex-direction: column; gap: 12px; margin-top: 16px; }
.home-card {
  display: flex; align-items: center; gap: 16px;
  width: 100%; padding: 20px 20px;
  background: rgba(255,255,255,0.1); border: 1.5px solid rgba(255,255,255,0.14);
  border-radius: 22px; cursor: pointer; touch-action: manipulation;
  font-family: var(--font); text-align: left;
}
.home-card-group { display: flex; flex-direction: column; }
.home-card--night { background: rgba(255,122,0,0.16); border-color: rgba(255,122,0,0.28); }
.home-card:active { background: rgba(255,255,255,0.17); }
.home-card--night:active { background: rgba(255,122,0,0.27); }
.home-card-icon { font-size: 32px; line-height: 1; flex-shrink: 0; }
.home-card-body { display: flex; flex-direction: column; gap: 3px; }
.home-card-title { font-size: 19px; font-weight: 700; color: #fff; line-height: 1.1; }
.home-card-sub { font-size: 13px; color: rgba(255,255,255,0.52); }
.home-card-proof { font-size: 12px; color: rgba(255,255,255,0.55); }
.home-card-proof:empty { display: none; }
.home-card-proof--muted { color: var(--tangerine); font-weight: 600; }
.home-foot { margin-top: auto; display: flex; flex-direction: column; align-items: center; gap: 4px; }

/* Home dashboard wrapper — transparent on phone so existing column layout is untouched */
.home-main { display: contents; }
.home-rail { display: none; }
/* Rail cards */
.rail-card {
  background: rgba(255,255,255,0.07); border: 1.5px solid rgba(255,255,255,0.12);
  border-radius: 22px; padding: 18px 20px;
}
.rail-card-title { font-size: 13px; font-weight: 700; letter-spacing: 0.8px; text-transform: uppercase; color: rgba(255,255,255,0.55); }
.rail-card-head {
  display: flex; align-items: baseline; justify-content: space-between; width: 100%;
  background: none; border: none; cursor: pointer; padding: 0; margin-bottom: 12px; font-family: var(--font);
}
.rail-see-all { font-size: 13px; font-weight: 600; color: var(--gold); }
.rail-stats { display: flex; gap: 10px; margin-top: 14px; }
.rail-stat { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px; }
.rs-num { font-family: var(--font-display); font-size: 30px; line-height: 1; color: #fff; }
.rs-label { font-size: 12px; color: rgba(255,255,255,0.55); white-space: nowrap; }
.rail-strip { justify-content: flex-start; margin-bottom: 12px; gap: 6px; }
.rail-strip .pf-emoji { font-size: 26px; }
.rail-stories { display: flex; flex-direction: column; gap: 10px; }
.rail-story {
  text-align: left; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.08);
  border-radius: 14px; padding: 12px 14px; cursor: pointer; font-family: var(--font);
  transition: background 0.15s, transform 0.1s;
}
.rail-story:active { transform: scale(0.985); background: rgba(255,255,255,0.1); }
.rail-story-text { color: rgba(255,255,255,0.92); font-size: 14px; line-height: 1.4; font-weight: 500; }
.rail-story-meta { display: flex; align-items: center; justify-content: space-between; margin-top: 8px; }
.rail-story-author { font-size: 12px; color: rgba(255,255,255,0.5); }
.rail-story-hearts { font-size: 12px; color: rgba(255,255,255,0.65); font-weight: 600; }
.home-foot-row { display: flex; gap: 8px; }
.compose-inspire {
  font-size: 13px; margin: -8px 0 12px; opacity: 0.7;
  max-height: 40px; overflow: hidden;
  transition: max-height 0.25s ease, opacity 0.2s ease, margin 0.25s ease;
}
.mode-toggle { display: flex; gap: 6px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.12); border-radius: 16px; padding: 5px; }
.mode-opt { flex: 1; background: none; border: none; color: rgba(255, 255, 255, 0.7); font-family: var(--font); font-weight: 600; font-size: 15px; padding: 11px; border-radius: 12px; cursor: pointer; transition: background 0.2s, color 0.2s; }
.mode-opt.active { background: #fff; color: var(--ink); }

/* Game Night host setting (emoji count) */
.ng-setting { margin: 4px auto 14px; max-width: 340px; }
.ng-setting-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.ng-setting-label { color: rgba(255, 255, 255, 0.85); font-weight: 600; font-size: 15px; }
/* A real setting the host can change deserves to look tappable, not like a neutral
   .mode-toggle blending into the row — gold ring + bigger targets make it read as
   "this is a live control," matching the weight of the label next to it. */
.ng-emoji-toggle {
  flex: 0 0 auto; background: rgba(255, 226, 52, 0.1);
  border: 2px solid var(--gold); border-radius: 20px; padding: 3px;
  box-shadow: 0 0 0 3px rgba(255, 226, 52, 0.14);
}
.ng-emoji-toggle .mode-opt { flex: 0 0 auto; min-width: 60px; padding: 12px 18px; font-size: 18px; font-weight: 700; }
.ng-emoji-toggle[hidden] { display: none; }  /* see .join-requests[hidden] for why this override is required */
/* AI Judge toggle — same tappable-control treatment as .ng-emoji-toggle above. */
.ng-aijudge-toggle {
  flex: 0 0 auto; background: rgba(255, 226, 52, 0.1);
  border: 2px solid var(--gold); border-radius: 20px; padding: 3px;
  box-shadow: 0 0 0 3px rgba(255, 226, 52, 0.14);
}
.ng-aijudge-toggle .mode-opt { flex: 0 0 auto; min-width: 52px; padding: 10px 16px; font-size: 15px; font-weight: 700; }
.ng-aijudge-toggle[hidden] { display: none; }  /* see .join-requests[hidden] for why this override is required */
/* Non-host readout of the host's chosen round length — plain text, deliberately NOT
   styled like .mode-toggle/.mode-opt so it doesn't invite a tap from a non-host. */
.ng-setting-readout { color: rgba(255, 255, 255, 0.6); font-weight: 600; font-size: 14px; }
/* Sample emoji strip under the setting — purely illustrative (not the real round's
   emojis, which are chosen once the game starts), just to get everyone thinking of
   a story while they wait. Updates live as the host flips between 3 and 4. */
.ng-setting-example { margin: 6px 0 0; font-size: 22px; letter-spacing: 4px; text-align: center; }
.ng-setting-time { margin: 4px 0 0; font-size: 13px; color: rgba(255, 255, 255, 0.55); font-weight: 600; text-align: center; }

/* ---------- Pick mode ---------- */
.pick-list { display: flex; flex-direction: column; gap: 12px; margin-top: 4px; }
.pick-card {
  text-align: left; background: var(--card); border: 2px solid transparent; border-radius: 18px;
  padding: 18px 20px; font-family: var(--font); font-weight: 500; font-size: 17px; color: var(--ink);
  line-height: 1.35; cursor: pointer; box-shadow: 0 10px 22px -14px rgba(0, 0, 0, 0.5);
  animation: cardIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; transition: transform 0.12s, border-color 0.2s, box-shadow 0.2s;
}
.pick-card:active { transform: scale(0.98); }
.pick-card.chosen { border-color: var(--coral); transform: scale(1.02); box-shadow: 0 14px 26px -10px var(--coral); }
#pickWriteOwn { margin-top: 14px; }

/* ---------- Build mode (word-bank / magnets) ---------- */
.build-sentence {
  min-height: 92px; background: rgba(255, 255, 255, 0.06); border: 1.5px dashed rgba(255, 255, 255, 0.22);
  border-radius: 18px; padding: 12px; display: flex; flex-wrap: wrap; gap: 8px; align-content: flex-start;
}
.build-sentence:empty::before { content: attr(data-empty); color: rgba(255, 255, 255, 0.4); font-weight: 500; font-size: 18px; }
.build-bank { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; max-height: 34vh; overflow-y: auto; padding: 2px; }
.word-chip {
  background: #fff; color: var(--ink); border: none; border-radius: 11px; padding: 9px 13px;
  font-family: var(--font); font-weight: 600; font-size: 15px; cursor: pointer;
  box-shadow: 0 3px 8px -4px rgba(0, 0, 0, 0.55); transition: transform 0.1s;
}
.word-chip:active { transform: scale(0.92); }
.word-chip.in-sentence { background: #FFF2EC; box-shadow: inset 0 0 0 1.5px var(--coral); }

/* Compose word bank — subtle, collapsed-by-default nudge for writer's block */
.wordbank { margin: 12px 0 2px; }
.wordbank-toggle {
  background: none; border: none; cursor: pointer; padding: 4px 2px;
  color: rgba(255, 255, 255, 0.5); font-family: var(--font); font-size: 13px; font-weight: 600;
}
.wordbank-toggle:active { opacity: 0.65; }
.wordbank-toggle.open { color: rgba(255, 255, 255, 0.75); }
.wordbank-chips { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 9px; animation: wbReveal 0.28s ease both; }
.wordbank-chips[hidden] { display: none; }  /* see .join-requests[hidden] for why this override is required */
@keyframes wbReveal { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.wb-chip {
  background: rgba(255, 255, 255, 0.1); color: rgba(255, 255, 255, 0.88); border: none; border-radius: 999px;
  padding: 7px 13px; font-family: var(--font); font-size: 14px; font-weight: 500;
  cursor: pointer; touch-action: manipulation; transition: transform 0.1s, background 0.15s;
}
.wb-chip:active { transform: scale(0.93); background: rgba(255, 255, 255, 0.2); }
.build-actions { display: flex; gap: 8px; margin-top: 12px; align-items: center; }
.build-actions .btn-ghost { flex: 0 0 auto; color: var(--muted); }
.build-actions .btn-primary { flex: 1; }
#buildWriteOwn { margin-top: 10px; }

/* ---------- Party (Pass & Play) ---------- */
.party-label { color: rgba(255, 255, 255, 0.7); font-size: 14px; font-weight: 600; margin: 16px 0 8px; }
.party-counts { display: flex; gap: 8px; }
.party-counts button {
  flex: 1; padding: 13px 0; border-radius: 13px; border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.1); color: #fff; font-family: var(--font); font-weight: 700; font-size: 17px; cursor: pointer;
}
.party-counts button.sel { background: var(--grad); border-color: transparent; color: var(--ink); }
.party-names { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.party-name-input {
  border: none; outline: none; background: #fff; color: var(--ink); border-radius: 13px;
  padding: 13px 16px; font-family: var(--font); font-weight: 600; font-size: 16px;
}
.party-center { margin: auto; text-align: center; display: flex; flex-direction: column; align-items: center; gap: 10px; }
.party-pass-emoji { font-size: 56px; }
.party-pass-name { color: #fff; font-size: 28px; font-weight: 700; line-height: 1.15; }
.party-pass-sub { color: rgba(255, 255, 255, 0.6); font-size: 15px; }
/* One-time notice when a judge's turn got auto-skipped (they disconnected mid-turnIntro) —
   otherwise the round jumping to a new judge with no writing phase looks like a silent bug. */
.ng-skip-notice {
  background: rgba(255, 226, 52, 0.14); border: 1px solid rgba(255, 226, 52, 0.35);
  color: #fff; font-size: 13px; font-weight: 600; padding: 8px 14px; border-radius: 999px;
}
.party-center .btn-lg { width: auto; min-width: 220px; margin-top: 18px; }
/* Deliberate dead space + dimming between the primary Start/Shuffle actions and quitting —
   a real confirm dialog now gates the tap either way (see netQuitConfirmOverlay), but a
   generous gutter means a stray or rushed tap lands on nothing rather than reaching this
   control at all. Full-opacity only once actually touched. */
#ngQuit, #screen-net-lobby #netLeaveBtn { margin-top: 32px; opacity: 0.55; transition: opacity 0.15s; }
#ngQuit:active, #netLeaveBtn:active { opacity: 1; }
.party-intro-strip { margin: 18px 0 8px; }
.party-intro-btn { width: auto; min-width: 200px; }
#ngRoundControls { display: flex; flex-direction: column; gap: 10px; align-items: center; width: 100%; }
/* Writing countdown */
.ng-timer {
  font-family: var(--font-display); font-weight: 400; font-size: 22px; color: var(--ink);
  background: var(--grad-warm); border: none; border-radius: 12px; padding: 5px 18px;
  box-shadow: none; letter-spacing: 0.5px; align-self: center;
}
/* Last 10s (warn: 10-6s, danger: <=5s) — bigger and more urgent-looking the less time is
   left, not just a color change, so it's readable at a glance without staring at it. */
.ng-timer.warn { background: #ff9800; color: #fff; font-size: 26px; animation: ngPulse 1s ease-in-out infinite; }
.ng-timer.danger { background: #ef4444; color: #fff; font-size: 32px; padding: 6px 22px; animation: ngPulse 0.5s ease-in-out infinite; }
@keyframes ngPulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.14); } }
/* Emoji reminder on the judging-wait screen (fixed size; .party-center won't give flex:1 a width) */
#ngWaitStrip { margin-top: 20px; }
#ngWaitStrip .pf-emoji { flex: 0 0 auto; width: 54px; height: 54px; font-size: 34px; }
/* Bigger writing box for connected play */
#ngInput {
  width: 100%; border: none; resize: none; outline: none; background: transparent;
  font-family: var(--font); font-weight: 500; color: var(--ink);
  min-height: 0; font-size: 23px; line-height: 1.45;
  -webkit-user-select: text; user-select: text;
}
#ngInput::placeholder { color: #C4BFD4; }
/* iOS keyboard fix + Game Night dark editor */
#screen-ng-input {
  overflow: hidden;
}
#screen-ng-input .party-input-head,
#screen-ng-input #ngInputStrip {
  flex-shrink: 0;
}
#screen-ng-input .editor {
  flex: none;
  height: min(200px, 35dvh);
  display: flex;
  flex-direction: column;
  background: #ffffff;
  border: 2px solid rgba(21,73,196,0.2);
  box-shadow: 0 4px 16px rgba(0,0,0,0.12);
}
#screen-ng-input #ngInput {
  flex: 1;
  min-height: 0;
  color: var(--ink);
}
#screen-ng-input #ngInput::placeholder { color: #b0b8d0; }
#screen-ng-input .editor-foot { border-top-color: rgba(21,73,196,0.1); }
#screen-ng-input .wordcount { color: #8896b0; }
#screen-ng-input #ngInputSubmit {
  flex-shrink: 0;
}
/* Keyboard-open layout: the button owns the bottom, the EDITOR yields.
   The old fixed editor height (min(200px, 35dvh)) is the remaining offender behind the
   "Lock-it-in is a 20px yellow sliver" bug — iOS never shrinks dvh for the software
   keyboard, and with this screen's overflow:hidden the bottom-pinned button was clipped
   past the fold with no way to scroll to it. Hit constantly in 2-player AI-judge games,
   where BOTH players write every turn. With the keyboard up: the editor turns flexible
   and absorbs the squeeze (never the button), the emoji strip compacts, the first-turn
   tip folds away, the bottom padding stops reserving home-bar space the keyboard already
   covers, and overflow returns to auto as a last-resort scroll path. */
body.kb-open #screen-ng-input {
  overflow-y: auto;
  padding-bottom: 10px;
}
body.kb-open #screen-ng-input .editor {
  flex: 1 1 auto;
  height: auto;
  min-height: 88px;
}
body.kb-open #screen-ng-input .onboard-tip { display: none; }
body.kb-open #screen-ng-input .filmstrip.sticky { margin-bottom: 8px; padding-top: 6px; padding-bottom: 4px; }
body.kb-open #screen-ng-input .filmstrip.sticky .pf-emoji { width: 44px; height: 44px; font-size: 28px; }
body.kb-open #screen-ng-input #ngInputSubmit { margin-top: 10px; }

/* ---------- Connect phones (lobby) ---------- */
/* First-time-only onboarding tips (dismissible, shown once ever per key — see
   showFirstTimeTip). Deliberately high-contrast and generously sized: these exist
   specifically for players who find the UI unfamiliar (e.g. first-time / older players). */
.onboard-tip {
  background: rgba(255, 226, 52, 0.14); border: 1.5px solid rgba(255, 226, 52, 0.4);
  border-radius: 18px; padding: 16px 18px; margin: 0 0 16px;
  display: flex; flex-direction: column; gap: 10px; align-items: flex-start;
  animation: cardIn 0.3s ease both;
}
.onboard-tip-text { color: #fff; font-size: 15px; font-weight: 600; line-height: 1.5; margin: 0; }
.onboard-tip-dismiss {
  align-self: flex-end; background: var(--gold); color: var(--ink); border: none;
  border-radius: 999px; padding: 10px 20px; font-family: var(--font); font-weight: 700;
  font-size: 14px; cursor: pointer; touch-action: manipulation;
}
.onboard-tip-dismiss:active { transform: scale(0.96); }
.onboard-tip[hidden] { display: none; }  /* see .join-requests[hidden] for why this override is required */
.field-label { display: block; color: rgba(255,255,255,0.75); font-size: 13px; font-weight: 600; margin: 0 0 6px; }

/* Cumulative daily count, not a live room snapshot — see PlotNet.bumpDailyGameCount. */
.daily-game-count { text-align: center; color: rgba(255,255,255,0.6); font-size: 13px; font-weight: 600; margin: 0 0 10px; }
/* "A friend of yours is hosting right now" — the practical zero-setup automatic-discovery path. */
.known-host-banner {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: space-between;
  background: var(--grad); border-radius: 16px; padding: 12px 16px; margin: 0 0 14px;
  box-shadow: var(--e-primary);
}
.known-host-text { color: var(--ink); font-weight: 700; font-size: 14px; flex: 1 1 auto; }
.known-host-btn { padding: 8px 16px; font-size: 13px; margin-top: 0; width: auto; background: var(--ink); color: #fff; }
.known-host-banner[hidden] { display: none; }  /* see .join-requests[hidden] for why this override is required */
#netName { width: 100%; margin-bottom: 12px; }
/* Avatar picker button + grid — avatars share the same 80-animal set room names come from
   (PlotNet.avatars), so a player's identity and a hosted room's default name are the same thing. */
.name-avatar-row { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.name-avatar-row #netName { width: auto; flex: 1 1 auto; margin-bottom: 0; }
.avatar-btn {
  flex: 0 0 auto; width: 52px; height: 52px; border-radius: 16px; border: none;
  background: var(--card); font-size: 26px; cursor: pointer; touch-action: manipulation;
  box-shadow: 0 8px 18px -10px rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center;
}
.avatar-btn:active { transform: scale(0.94); }
.avatar-grid {
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; width: 100%;
  max-height: 40vh; overflow-y: auto; padding: 2px;
}
.avatar-opt {
  aspect-ratio: 1; border: none; border-radius: 14px; background: #F3EFFA; font-size: 32px;
  cursor: pointer; touch-action: manipulation; display: flex; align-items: center; justify-content: center;
}
.avatar-opt.active { background: var(--grad); box-shadow: 0 0 0 2px var(--gold) inset; }
.avatar-opt:active { transform: scale(0.92); }
.net-or-divider { text-align: center; color: rgba(255,255,255,0.35); font-size: 13px; font-weight: 600; margin: 18px 0 10px; letter-spacing: 0.04em; }
.net-code-input {
  width: 100%; text-align: center; letter-spacing: 12px; text-transform: uppercase;
  border: none; outline: none; background: #fff; color: var(--ink); border-radius: 16px;
  padding: 18px; font-family: var(--font); font-weight: 700; font-size: 34px; margin: 8px 0 14px;
}
.net-code-big {
  text-align: center; letter-spacing: 10px; font-size: 56px; font-weight: 700; color: var(--ink);
  background: var(--grad); border-radius: 20px; padding: 18px 0; margin: 6px 0 12px;
}
.net-room-name {
  text-align: center; font-size: 30px; font-weight: 800; color: var(--ink);
  background: var(--grad); border-radius: 18px; padding: 14px 12px; margin: 4px 0 10px;
}
/* Lobby: settings grouped into one quiet card BELOW the players/start flow — they're
   secondary controls, and their always-on explainer text was drowning the primary
   "who's here → start" read of the screen. */
.lobby-settings {
  background: rgba(255, 255, 255, 0.06); border-radius: 18px;
  padding: 14px 16px 12px; margin: 14px 0 4px;
}
.lobby-settings .ng-setting { margin: 0 auto; max-width: none; }
.lobby-settings .ng-setting + .ng-setting { margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(255, 255, 255, 0.1); }
.lobby-settings .ng-setting-time { font-size: 12px; text-align: left; }
/* The sample-emoji strip ("📦 🐶 🚪") was pure decoration inside the settings card —
   dropped from the layout to keep the card to two tight rows. */
.lobby-settings .ng-setting-example { display: none; }
.lobby-packs-line { font-size: 12px; }
/* Head-count over the roster — the number that drives Start locking / CTA emphasis. */
.lobby-players-label {
  margin: 12px 2px 6px; font-size: 12px; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: rgba(255, 255, 255, 0.6);
}
.lobby-players-label:empty { display: none; }
/* 6+ players: two-column compact roster so eight full-width rows can't fill the whole
   screen and shove Start game below the fold. Names ellipsize; badges shrink to fit. */
#netPlayers.roster-compact { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
#netPlayers.roster-compact .party-score-row { font-size: 14px; padding: 10px 12px; gap: 6px; min-width: 0; }
#netPlayers.roster-compact .party-score-row > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
#netPlayers.roster-compact .host-badge, #netPlayers.roster-compact .away-badge { font-size: 9px; padding: 2px 6px; margin-left: 4px; }
/* Inline home for the docked chat bar while the lobby is active (see chatUiSync's
   reparenting) — full-width bar in the page flow instead of a floating corner pill.
   Doubled-up selector (.chat-dock.chat-dock--inline) is deliberate: the base .chat-dock
   rule (position:fixed etc.) lives LATER in this file, so a same-specificity override
   here would lose the cascade tie on source order. */
.lobby-chat-slot { margin-top: 10px; }
.chat-dock.chat-dock--inline {
  position: static; width: 100%; max-width: none;
  justify-content: flex-start; padding: 12px 16px;
  border-radius: 16px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
}
.chat-dock.chat-dock--inline .chat-dock-last { flex: 1; text-align: left; }
/* Floating-pill lane: while the docked pill floats (any non-lobby Game Night screen),
   the active screen reserves the bottom strip the pill lives in, so every screen's own
   bottom-pinned control (Start round, judge picks, See who won, Pass, Vote, Quit…) ends
   ABOVE the pill instead of colliding with it. kb-open's write-screen rule outranks this
   (id selector) — the pill is hidden entirely while typing anyway. */
body.chat-pill .screen.active { padding-bottom: calc(72px + env(safe-area-inset-bottom)); }
.lobby-qr {
  display: block; margin: 0 auto 12px; border-radius: 12px;
  width: 180px; height: 180px;
}
/* flex-shrink:0 is load-bearing, not decorative: .net-rooms sits inside .screen's flex
   column, and min-height (56px, sized for the empty/searching state) is smaller than the
   real content once 2+ rooms are found. Without flex-shrink:0, flexbox is free to squeeze
   this item down toward that 56px floor whenever the column runs out of vertical space —
   and since overflow stays visible, the room cards keep painting at full size regardless,
   so they spill straight into the room-name input and buttons below instead of the screen
   just scrolling (which is what .screen's own overflow-y:auto is there for). */
.net-rooms { display: flex; flex-direction: column; gap: 10px; margin: 12px 0 16px; min-height: 56px; flex-shrink: 0; }
/* Open rooms are the primary path (join > host — too many simultaneous hosts is confusing for
   a group), so these get a gold ring to visually outrank the (now de-emphasized) Host button. */
.net-room {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background-image: linear-gradient(var(--card), var(--card)), var(--grad);
  background-origin: border-box; background-clip: padding-box, border-box;
  border: 2px solid transparent; border-radius: 16px; padding: 14px 18px;
  font-family: var(--font); color: var(--ink); cursor: pointer; touch-action: manipulation;
  box-shadow: 0 10px 22px -14px rgba(255, 176, 32, 0.6);
}
.net-room:active { transform: scale(0.98); }
.net-room-emoji { font-size: 28px; }
.net-room-body { flex: 1; display: flex; flex-direction: column; }
.net-room-label { font-weight: 700; font-size: 19px; }
.net-room-count { font-size: 13px; color: var(--muted); font-weight: 600; }
.net-room-cta {
  flex: 0 0 auto; background: var(--grad); color: var(--ink); font-weight: 700; font-size: 13px;
  padding: 8px 14px; border-radius: 999px;
}
#screen-net-home .btn-lg, #screen-net-join .btn-lg, #screen-net-lobby .btn-lg { margin-top: 14px; }
#screen-net-home .btn-soft, #screen-net-lobby .btn-ghost { margin-top: 10px; }
.party-input-head {
  display: flex; justify-content: space-between; color: rgba(255, 255, 255, 0.8); font-weight: 600; font-size: 15px; margin-bottom: 12px;
  max-height: 40px; overflow: hidden; opacity: 1;
  transition: max-height 0.25s ease, opacity 0.2s ease, margin-bottom 0.25s ease;
}
/* Same phone-keyboard collapse as .compose-head above — frees room for the emoji strip
   while writing a Game Night turn. The countdown itself lives outside this element (see
   #ngInputTimer) specifically so it survives the collapse — players must always be able
   to see time remaining, even mid-keyboard. */
body.kb-open #screen-ng-input .party-input-head {
  max-height: 0; opacity: 0; margin-bottom: 0; pointer-events: none;
}
#ngInputTimer { margin: 0 0 10px; }
.party-judge-card {
  width: 100%; text-align: left; background: var(--card); border: none; border-radius: 18px;
  padding: 16px 18px; font-family: var(--font); font-weight: 500; font-size: 17px; color: var(--ink);
  line-height: 1.4; cursor: pointer; touch-action: manipulation; box-shadow: 0 10px 22px -14px rgba(0, 0, 0, 0.5);
  animation: cardIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.party-judge-card:active { transform: scale(0.98); }

/* Judge reveal cards */
.judge-card-hidden {
  position: relative; overflow: hidden;
  width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  background: rgba(255,255,255,0.05); border: 1.5px dashed rgba(255,255,255,0.22); border-radius: 18px;
  padding: 22px 18px; font-family: var(--font); cursor: pointer; touch-action: manipulation;
  animation: cardIn 0.4s cubic-bezier(0.16,1,0.3,1) both;
}
.judge-card-hidden::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg, transparent 30%, rgba(255,255,255,0.03) 42%, rgba(255,255,255,0.22) 50%, rgba(255,255,255,0.03) 58%, transparent 70%);
  transform: translateX(-100%);
  animation: judgeCardShimmer 2.4s ease-in-out infinite;
}
.judge-card-hidden:nth-of-type(2)::after { animation-delay: 0.3s; }
.judge-card-hidden:nth-of-type(3)::after { animation-delay: 0.6s; }
.judge-card-hidden:nth-of-type(4)::after { animation-delay: 0.9s; }
.judge-card-hidden:nth-of-type(5)::after { animation-delay: 1.2s; }
@keyframes judgeCardShimmer {
  0%, 15% { transform: translateX(-100%); }
  85%, 100% { transform: translateX(100%); }
}
.judge-card-hidden:active { transform: scale(0.97); background: rgba(255,255,255,0.09); }
.judge-card-hidden:active::after { animation-play-state: paused; }
.judge-card-mystery-icon { font-size: 22px; color: rgba(255,255,255,0.32); }
.judge-card-mystery-label { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.4); letter-spacing: 0.03em; }
.judge-card-revealed {
  width: 100%; background: var(--card); border-radius: 18px; padding: 16px 18px;
  box-shadow: 0 10px 22px -14px rgba(0,0,0,0.5);
  transform-origin: center; backface-visibility: hidden;
  animation: judgeFlipIn 0.5s cubic-bezier(0.34,1.4,0.5,1) both;
}
.judge-card-revealed.no-flip { animation: cardIn 0.25s ease both; }
@keyframes judgeFlipIn {
  0%   { opacity: 0; transform: perspective(1100px) rotateY(-82deg) scale(0.94); }
  55%  { opacity: 1; }
  100% { opacity: 1; transform: perspective(1100px) rotateY(0deg) scale(1); }
}
.judge-card-text { font-family: var(--font); font-weight: 500; font-size: 21px; color: var(--ink); line-height: 1.4; margin: 0 0 12px; }
.judge-card-foot { display: flex; align-items: center; justify-content: flex-end; }
.judge-card-revealed.selected { box-shadow: 0 0 0 3px var(--tangerine), 0 10px 22px -14px rgba(0,0,0,0.5); }

/* "Maxed the word limit" badge shown to the judge — playful jiggle so it pops. */
.judge-maxed-badge {
  font-size: 12px; font-weight: 700; color: #7a3b00;
  background: linear-gradient(135deg, #ffd76a, #ffb020);
  border-radius: 999px; padding: 4px 10px; white-space: nowrap;
  box-shadow: 0 4px 10px -4px rgba(255,150,0,0.7);
  transform-origin: center; animation: maxedPop 0.4s cubic-bezier(0.34,1.6,0.5,1) both, maxedWiggle 2.4s 0.6s ease-in-out infinite;
}
@keyframes maxedPop { 0% { opacity: 0; transform: scale(0.4) rotate(-12deg); } 100% { opacity: 1; transform: scale(1) rotate(-3deg); } }
@keyframes maxedWiggle { 0%,88%,100% { transform: rotate(-3deg); } 92% { transform: rotate(4deg) scale(1.06); } 96% { transform: rotate(-6deg); } }

/* Locked-in confirmation card (writer's own screen after submitting / auto-lock). */
.ng-locked { display: flex; flex-direction: column; align-items: center; gap: 14px; margin: 18px auto 4px; max-width: 340px; }
.ng-locked[hidden] { display: none; }  /* see .join-requests[hidden] for why this override is required */
.ng-locked-stamp {
  font-family: var(--font); font-weight: 800; font-size: 24px; color: #fff;
  background: linear-gradient(135deg, #48e07a, #16b45a); padding: 10px 20px;
  border-radius: 16px; box-shadow: 0 12px 26px -12px rgba(22,180,90,0.8);
  transform: rotate(-4deg);
}
.ng-locked-stamp.stamp-in { animation: stampSlam 0.6s cubic-bezier(0.3,1.5,0.5,1) both; }
@keyframes stampSlam {
  0%   { opacity: 0; transform: scale(2.6) rotate(-16deg); }
  55%  { opacity: 1; transform: scale(0.86) rotate(2deg); }
  75%  { transform: scale(1.08) rotate(-6deg); }
  100% { opacity: 1; transform: scale(1) rotate(-4deg); }
}
.ng-locked-card {
  width: 100%; background: var(--card); border-radius: 18px; padding: 16px 18px;
  box-shadow: 0 10px 22px -14px rgba(0,0,0,0.5); border: 2px dashed rgba(72,224,122,0.55);
}
.ng-locked-card.pop-in { animation: popCard 0.45s 0.12s cubic-bezier(0.34,1.5,0.5,1) both; }
@keyframes popCard { 0% { opacity: 0; transform: translateY(10px) scale(0.94); } 100% { opacity: 1; transform: none; } }
.ng-locked-label { font-size: 12px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: rgba(0,0,0,0.4); margin-bottom: 6px; }
.ng-locked-text { font-family: var(--font); font-weight: 500; font-size: 17px; color: var(--ink); line-height: 1.4; margin: 0; }

/* Round champion banner — animate the winner's name to all players. */
.ng-champion { text-align: center; margin: 6px auto 14px; }
.ng-champion-crown { font-size: 42px; line-height: 1; animation: crownBob 2.2s ease-in-out infinite; }
@keyframes crownBob { 0%,100% { transform: translateY(0) rotate(-5deg); } 50% { transform: translateY(-7px) rotate(5deg); } }
.ng-champion-name { font-family: var(--font); font-weight: 800; font-size: 32px; color: #fff; line-height: 1.12; text-shadow: 0 4px 20px rgba(0,0,0,0.35); word-break: break-word; }
.ng-champion-name.champ-in { animation: champIn 0.85s cubic-bezier(0.3,1.55,0.5,1) both; }
@keyframes champIn { 0% { opacity: 0; transform: scale(0.3) rotate(-12deg); } 55% { opacity: 1; transform: scale(1.16) rotate(4deg); } 100% { transform: scale(1) rotate(0); } }
.ng-champion-sub { font-weight: 700; font-size: 15px; color: #ffcf5a; margin-top: 5px; letter-spacing: 0.02em; }
/* Personal win, round-champion screen — bigger, gold, and a bouncier entrance + gentle
   glow pulse so YOUR win reads unmistakably differently from seeing someone else's name. */
.ng-champion-name.you-won { font-size: 38px; color: var(--gold); text-shadow: 0 4px 26px rgba(255, 226, 52, 0.65); }
.ng-champion-name.you-won.champ-in { animation: champInYou 0.95s cubic-bezier(0.3, 1.6, 0.5, 1) both, youWonGlow 1.8s ease-in-out 0.95s infinite; }
@keyframes champInYou { 0% { opacity: 0; transform: scale(0.22) rotate(-16deg); } 55% { opacity: 1; transform: scale(1.24) rotate(6deg); } 100% { transform: scale(1) rotate(0); } }
@keyframes youWonGlow { 0%, 100% { text-shadow: 0 4px 20px rgba(255, 226, 52, 0.5); } 50% { text-shadow: 0 4px 34px rgba(255, 226, 52, 0.95); } }

/* Play-again vote */
.ng-vote { margin: 16px auto 6px; max-width: 380px; }
.ng-vote-btns { display: flex; gap: 10px; }
.ng-vote-btns .btn { flex: 1; }
.ng-vote-btns .btn.chosen { outline: 3px solid #fff; outline-offset: 2px; transform: translateY(-1px); }
.ng-vote-btns .btn:disabled { opacity: 0.6; cursor: default; }
#ngVoteTally { margin-top: 10px; }
.party-score-row.leader { background: linear-gradient(135deg, rgba(255,215,106,0.22), rgba(255,176,32,0.12)); border-radius: 12px; }

/* Winner celebration canvas — absolute overlay over the score screen, pointer-events none
   so buttons underneath remain tappable. z-index sits above content but below topbar. */
.ng-celeb-canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; z-index: 5;
}
.ng-celeb-img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; object-position: 50% 92%;
  pointer-events: none; z-index: 6;
  animation: ng-celeb-fadein 0.8s ease-out;
}
.ng-celeb-img.ng-celeb-traverse {
  animation: ng-celeb-fadein 0.8s ease-out, ng-celeb-traverse 7s ease-in-out both;
}
@keyframes ng-celeb-fadein { from { opacity: 0; } to { opacity: 1; } }
@keyframes ng-celeb-traverse { from { transform: translateX(-65%); } to { transform: translateX(65%); } }
@media (prefers-reduced-motion: reduce) {
  .ng-celeb-canvas, .ng-celeb-img { display: none !important; }
  .heart-fx, .ng-score-confetti { display: none !important; }
  .judge-card-revealed, .judge-card-revealed.no-flip,
  .party-score-row.score-bumped, .party-score-pts.pts-bumped,
  .judge-maxed-badge, .ng-locked-stamp, .ng-locked-stamp.stamp-in, .ng-locked-card.pop-in,
  .ng-champion-crown, .ng-champion-name, .ng-champion-name.champ-in,
  .wordbank-chips { animation: none !important; }
}
.judge-pick-btn {
  display: block; width: 100%; margin-top: 12px;
  background: var(--tangerine); border: none; border-radius: 14px; padding: 13px 20px;
  font-family: var(--font); font-size: 16px; font-weight: 700; color: #fff; cursor: pointer; touch-action: manipulation;
  letter-spacing: 0.01em;
}
.judge-pick-btn:active { transform: scale(0.97); opacity: 0.85; }
.judge-pick-btn.selected { background: var(--ink); }

/* Duo mode's judge reactions (thumbs up/down/sideways) — replaces the pick-then-confirm
   flow above when there's only one submission to react to, not compare. Tapping one
   finalizes the turn immediately (see ngHostReact), so all three sit at equal visual
   weight — no "selected" state to show, unlike .judge-pick-btn. */
.judge-reactions { display: flex; gap: 8px; margin-top: 12px; }
.judge-reaction-btn {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px;
  background: var(--card-soft, rgba(0,0,0,0.04)); border: none; border-radius: 14px; padding: 12px 6px;
  font-family: var(--font); cursor: pointer; touch-action: manipulation;
}
.judge-reaction-btn:active { transform: scale(0.94); }
.judge-reaction-icon { font-size: 26px; line-height: 1; }
.judge-reaction-label { font-size: 12px; font-weight: 700; color: var(--ink); }
.judge-reaction-up { background: rgba(72,224,122,0.16); }
.judge-reaction-up:active { background: rgba(72,224,122,0.3); }
.judge-reaction-down { background: rgba(255,94,91,0.12); }
.judge-reaction-down:active { background: rgba(255,94,91,0.24); }
.judge-reaction-side { background: rgba(0,0,0,0.05); }
.judge-reaction-side:active { background: rgba(0,0,0,0.1); }
/* No dedicated "sideways thumb" glyph exists — reuse the thumbs-up icon, rotated. */
.judge-reaction-side .judge-reaction-icon { display: inline-block; transform: rotate(90deg); }

/* Turn-result screen: shows which reaction the duo judge gave, so it's never ambiguous
   why the celebration (sound/confetti/gold glow) did or didn't fire. See ngHostReact. */
.ng-reaction-badge {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin: 4px auto 10px; padding: 6px 14px; border-radius: 999px; width: fit-content;
  font-size: 13px; font-weight: 700; color: var(--ink);
  animation: popCard 0.35s cubic-bezier(0.34,1.5,0.5,1) both;
}
.ng-reaction-badge[hidden] { display: none; }  /* see .join-requests[hidden] for why this override is required */
.ng-reaction-badge-up { background: rgba(72,224,122,0.2); }
.ng-reaction-badge-down { background: rgba(255,94,91,0.16); }
.ng-reaction-badge-side { background: rgba(0,0,0,0.06); }
.ng-reaction-badge-icon { font-size: 16px; }
.ng-reaction-badge-side .ng-reaction-badge-icon { display: inline-block; transform: rotate(90deg); }

/* Countdown screen */
.ng-countdown-num {
  font-size: 96px; font-weight: 900; line-height: 1;
  color: var(--tangerine); margin: 24px 0 8px;
}
.ng-countdown-num.ng-countdown-pop { animation: ng-countdown-pop 0.55s ease-out; }
@keyframes ng-countdown-pop {
  0%   { transform: scale(1.5); opacity: 0.2; }
  60%  { transform: scale(0.92); }
  100% { transform: scale(1);   opacity: 1; }
}
.ng-countdown-label { font-size: 20px; color: var(--ink-light, #888); margin: 0; }

/* Player wait feed — revealed stories streaming in */
.wait-revealed-card {
  background: rgba(255,255,255,0.07); border-radius: 16px; padding: 14px 16px;
  animation: cardIn 0.4s cubic-bezier(0.16,1,0.3,1) both;
}
.wait-revealed-text { font-size: 19px; font-weight: 500; color: rgba(255,255,255,0.92); line-height: 1.45; margin: 0 0 8px; }
.wait-revealed-author { font-size: 12px; color: rgba(255,255,255,0.5); font-weight: 500; }
/* ---------- Card reaction bar (🙌💪😂🤭❤️🔥 — see ngReactionBarEl) ----------
   All six always visible, one tap away. Zero-count reactions sit dimmed with no number so
   the bar reads quiet until the table gets loud; your own picks get the gold tint. Default
   chrome is tuned for the dark translucent cards (wait feed / winner block); the white
   judge cards override below. */
.ng-react-bar {
  display: flex; gap: 2px; justify-content: space-between;
  margin-top: 10px; background: rgba(255,255,255,0.10); border-radius: 999px; padding: 3px 4px;
}
.ng-react-btn {
  flex: 1 1 0; display: inline-flex; align-items: center; justify-content: center; gap: 3px;
  border: none; background: none; border-radius: 999px; padding: 6px 2px; min-height: 34px;
  font-family: var(--font); font-size: 17px; line-height: 1; cursor: pointer;
  opacity: 0.5; transition: transform 0.12s, opacity 0.2s, background 0.2s;
  touch-action: manipulation;
}
.ng-react-btn:active { transform: scale(1.2); }
.ng-react-btn.has-count { opacity: 1; }
.ng-react-btn.mine { background: rgba(255,226,52,0.22); opacity: 1; }
.ng-react-count { font-size: 12px; font-weight: 700; color: rgba(255,255,255,0.85); font-variant-numeric: tabular-nums; min-width: 8px; text-align: left; }
.judge-card-revealed .ng-react-bar { background: rgba(21,73,196,0.08); }
.judge-card-revealed .ng-react-count { color: #6B6580; }
.judge-card-revealed .ng-react-btn.mine { background: rgba(255,159,28,0.28); }
/* Floating reaction emojis — spawned per newly-landed reaction (see ngSpawnReactFloater) */
#ngReactFloatLayer { position: fixed; inset: 0; pointer-events: none; z-index: 9990; overflow: hidden; }
.ng-react-floater {
  position: absolute; bottom: 96px; font-size: 34px;
  animation: ngFloatUp 2s ease-out forwards; will-change: transform, opacity;
}
@keyframes ngFloatUp {
  0%   { transform: translateY(0) scale(0.6); opacity: 0; }
  12%  { opacity: 1; transform: translateY(-24px) scale(1.15); }
  100% { transform: translateY(-46vh) scale(1); opacity: 0; }
}
/* ---------- Story Album (see renderAlbum) ---------- */
.ng-album-list { display: flex; flex-direction: column; gap: 12px; padding-bottom: 14px; }
.ng-album-round {
  font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: rgba(255,255,255,0.6); margin-top: 10px;
}
.ng-album-turn { display: flex; flex-direction: column; gap: 10px; }
.ng-album-turn .filmstrip.mini { margin: 0; }
.ng-album-turn .filmstrip.mini .pf-emoji { font-size: 24px; aspect-ratio: auto; height: 42px; }
.ng-album-card { position: relative; animation: none; }
.ng-album-card.ng-album-winner { box-shadow: inset 0 0 0 2px rgba(255,226,52,0.55); }
.ng-album-crown {
  position: absolute; top: -9px; right: 12px; background: var(--grad); color: var(--ink);
  font-size: 11px; font-weight: 700; border-radius: 999px; padding: 2px 9px; white-space: nowrap;
}
.ng-album-chips { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.ng-album-chip {
  background: rgba(255,226,52,0.16); color: var(--gold); border-radius: 999px;
  padding: 2px 9px; font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.ng-album-quip { text-align: left; color: rgba(255,255,255,0.7); margin: 0; }
/* ---------- Table chat (docked pill ↔ bottom sheet — see chatUiSync) ---------- */
/* display:flex below would silently defeat the [hidden] attribute (author rules beat the
   UA stylesheet) — same guard the overlay pattern uses. */
.chat-dock[hidden], .chat-panel[hidden] { display: none; }
/* z-index sits just BELOW .overlay (50) — chat is a persistent convenience, not a modal, so
   any real dialog (quit-confirm, avatar/language picker, pack upsell, share sheet…) must
   visually and functionally cover it rather than float underneath a translucent backdrop. */
.chat-dock {
  position: fixed; right: 12px; bottom: calc(10px + env(safe-area-inset-bottom)); z-index: 45;
  display: flex; align-items: center; gap: 7px; max-width: 60vw;
  background: #fff; color: var(--ink); border: none; border-radius: 999px;
  padding: 9px 13px; font-family: var(--font); font-weight: 600; font-size: 14px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); cursor: pointer;
}
.chat-dock-last { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--muted); }
/* While typing (keyboard up) even the docked pill is in the way of the shrunken write
   screen — hide it entirely; it returns the moment the keyboard drops. */
body.kb-open .chat-dock { display: none; }
.chat-unread {
  background: var(--coral); color: #fff; font-size: 11px; font-weight: 700;
  border-radius: 999px; min-width: 18px; height: 18px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}
.chat-unread[hidden] { display: none; }  /* see .join-requests[hidden] for why this override is required */
.chat-panel {
  position: fixed; left: 10px; right: 10px; bottom: calc(10px + env(safe-area-inset-bottom)); z-index: 45;
  background: #fff; color: var(--ink); border-radius: 22px; padding: 10px 14px 12px;
  box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.35);
  display: flex; flex-direction: column; gap: 8px;
  max-width: 460px; margin: 0 auto;
}
.chat-panel-head {
  display: flex; justify-content: space-between; align-items: center;
  font-size: 13px; font-weight: 700; color: var(--muted);
}
.chat-dock-btn {
  background: #f4ede0; border: none; border-radius: 999px; padding: 4px 12px;
  font-family: var(--font); font-weight: 700; font-size: 12px; color: var(--ink); cursor: pointer;
}
.chat-log { overflow-y: auto; display: flex; flex-direction: column; gap: 5px; max-height: 128px; min-height: 44px; -webkit-overflow-scrolling: touch; }
.chat-empty { margin: auto; color: var(--muted); font-size: 13px; }
.chat-msg { display: flex; align-items: baseline; gap: 7px; font-size: 14px; line-height: 1.35; }
.chat-msg-who { font-weight: 700; font-size: 12px; color: var(--muted); flex-shrink: 0; }
.chat-msg-mine .chat-msg-who { color: var(--grape); }
.chat-msg-body { font-weight: 600; }
.chat-tray-row { display: flex; gap: 5px; justify-content: center; flex-wrap: wrap; }
.chat-tray-row + .chat-tray-row { margin-top: 5px; }
.chat-tray-row button {
  border: none; cursor: pointer; border-radius: 999px; font-family: var(--font); font-weight: 700;
  touch-action: manipulation; transition: transform 0.12s;
}
.chat-tray-row button:active { transform: scale(1.15); }
.chat-tray-emoji { background: #f4ede0; font-size: 19px; padding: 6px 10px; }
.chat-tray-phrase { background: #f1eefa; color: var(--grape); font-size: 13px; padding: 7px 12px; }
/* Stacked-but-not-sent-yet preview (see ngChatQueue) — chips for each buffered tap plus a
   shrinking bar showing how long is left before the whole stack auto-sends as one message. */
.chat-pending-row[hidden] { display: none; }
.chat-pending-row {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 2px 2px 4px;
}
.chat-pending-chip {
  background: #fff3d6; border: 1px solid #f2d98a; border-radius: 999px;
  padding: 3px 10px; font-size: 13px; font-weight: 700; color: #7a5c00;
  animation: chatChipIn 0.18s ease-out both;
}
@keyframes chatChipIn { from { transform: scale(0.6); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.chat-pending-bar {
  flex: 1 1 28px; min-width: 28px; height: 4px; border-radius: 3px; align-self: center;
  background: var(--grape); transform-origin: left;
  animation-name: chatPendingShrink; animation-timing-function: linear; animation-fill-mode: forwards;
}
@keyframes chatPendingShrink { from { transform: scaleX(1); } to { transform: scaleX(0); } }
@media (prefers-reduced-motion: reduce) {
  .chat-pending-chip { animation: none; }
  .chat-pending-bar { animation: none; opacity: 0.4; }
}
/* Expanded sheet overlays the screen bottom — give the active screen enough scroll room
   that nothing (Start game, Leave room…) is permanently buried under it. */
body.chat-open .screen.active { padding-bottom: calc(330px + env(safe-area-inset-bottom)); }
.party-scoreboard { display: flex; flex-direction: column; gap: 8px; margin: 14px 0 18px; }
.party-score-row {
  display: flex; justify-content: space-between; align-items: center;
  background: rgba(255, 255, 255, 0.08); border-radius: 14px; padding: 13px 18px; color: #fff; font-weight: 600; font-size: 16px;
}
/* Lobby roster (avatar + name, no points yet) reads better a bit larger than the
   in-game scoreboard rows, which pack a points value too — scoped to #netPlayers
   only so the in-game scoreboard's sizing is untouched. */
#netPlayers.party-scoreboard .party-score-row { font-size: 19px; padding: 15px 18px; }
.party-score-pts { color: var(--gold); font-family: var(--font-display); font-weight: 400; font-size: 20px; text-shadow: none; display: inline-block; }
.host-badge {
  font-size: 11px; font-weight: 700; color: var(--gold); background: rgba(255, 226, 52, 0.14);
  border-radius: 999px; padding: 3px 9px; margin-left: 8px; white-space: nowrap;
}
.party-score-row.away { opacity: 0.55; }
.away-badge {
  font-size: 11px; font-weight: 700; color: rgba(255,255,255,0.7); background: rgba(255,255,255,0.1);
  border-radius: 999px; padding: 3px 9px; margin-left: 8px; white-space: nowrap;
}
/* Pulse on the row + points of whoever just scored */
.party-score-row.score-bumped { animation: scoreBump 0.6s cubic-bezier(0.34,1.56,0.64,1); }
@keyframes scoreBump {
  0%   { transform: scale(1); box-shadow: 0 0 0 0 rgba(255,226,52,0); }
  35%  { transform: scale(1.04); box-shadow: 0 0 0 2px var(--gold) inset; }
  100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255,226,52,0); }
}
.party-score-pts.pts-bumped { animation: ptsBump 0.6s cubic-bezier(0.34,1.56,0.64,1); }
@keyframes ptsBump { 0% { transform: scale(1); } 40% { transform: scale(1.5); } 100% { transform: scale(1); } }

/* ---------- Filmstrip ---------- */
.filmstrip { display: flex; justify-content: center; gap: 10px; flex-wrap: nowrap; }
.filmstrip.big { gap: 6px; margin: 18px 0 6px; }
.pf-emoji {
  display: flex; align-items: center; justify-content: center;
  background: #ffffff;
  border: none;
  border-radius: 18px;
  box-shadow: 0 8px 18px -6px rgba(0,0,0,0.40);
  opacity: 0; transform: translateY(18px) scale(0.6) rotate(-8deg);
  animation: popIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
.filmstrip.big .pf-emoji { width: 60px; height: 60px; font-size: 40px; }
/* Genuinely sticky (the class name always promised this, but nothing ever pinned it) — pinned
   to the top of its scrolling screen so the keyboard opening (which scrolls the focused field
   into view) can never carry the emoji strip off the top with it. The gradient fade avoids a
   hard-edged banner look as text scrolls up behind it. */
.filmstrip.sticky {
  gap: 8px; margin: 4px -22px 14px;
  position: sticky; top: 0; z-index: 5;
  padding: 10px 22px 8px;
  background: linear-gradient(to bottom, var(--bg-1) 72%, rgba(21, 73, 196, 0));
}
.filmstrip.sticky .pf-emoji { width: 58px; height: 58px; font-size: 36px; }
.filmstrip.mini { gap: 8px; margin: 14px 0 2px; }
.filmstrip.mini .pf-emoji { flex: 1 1 0; width: auto; height: auto; aspect-ratio: 1 / 1; font-size: 40px; border-radius: 18px; }
.filmstrip.play .pf-emoji { width: 56px; height: 56px; font-size: 36px; transition: transform 0.25s, box-shadow 0.25s; }
.filmstrip.play .pf-emoji.lit {
  transform: translateY(-8px) scale(1.18) !important;
  box-shadow: var(--e-lit);
  background: #fff;
}
@keyframes popIn { to { opacity: 1; transform: none; } }
.pf-emoji:nth-child(1) { animation-delay: 0.05s; }
.pf-emoji:nth-child(2) { animation-delay: 0.13s; }
.pf-emoji:nth-child(3) { animation-delay: 0.21s; }
.pf-emoji:nth-child(4) { animation-delay: 0.29s; }
.pf-emoji:nth-child(5) { animation-delay: 0.37s; }
/* Judge/wait strips re-use the same emojis shown earlier — skip the pop-in delay */
#ngJudgeStrip .pf-emoji,
#ngWaitStrip .pf-emoji { animation-delay: 0s !important; animation-duration: 0.15s !important; }

/* ---------- Buttons ---------- */
.btn {
  border: none; border-radius: 999px; font-family: var(--font);
  font-weight: 600; font-size: 16px; cursor: pointer;
  padding: 14px 22px; transition: transform 0.12s, box-shadow 0.2s, opacity 0.2s;
}
.btn:active { transform: scale(0.96); }
.btn-lg { width: 100%; padding: 17px; font-size: 17px; margin-top: auto; }
.btn-primary {
  background: var(--grad);
  /* --grad is a light gold→tangerine gradient — white text reads poorly against it
     (worst on the gold end). Dark ink text is legible across the whole gradient.
     This is every .btn-primary app-wide (Submit story, Share, Unlock, Host a game…),
     not just the pack sheet — same button style everywhere, same fix everywhere. */
  color: var(--ink); font-family: var(--font); letter-spacing: 0;
  box-shadow: var(--e-primary);
}
.btn-primary:active { transform: scale(0.96); }
.btn-primary:disabled { opacity: 0.4; cursor: not-allowed; }
/* Looks disabled but stays tappable (unlike the disabled attribute) so the tap can be
   answered with a shake — the lobby Start button below the 3-player minimum. */
.btn-locked { opacity: 0.4; filter: grayscale(0.5); box-shadow: none; cursor: not-allowed; }
@keyframes sm-shake {
  10%, 90% { transform: translateX(-2px); }
  20%, 80% { transform: translateX(4px); }
  30%, 50%, 70% { transform: translateX(-6px); }
  40%, 60% { transform: translateX(6px); }
}
.shake { animation: sm-shake 0.45s ease both; }
@keyframes sm-hint-flash {
  0% { transform: scale(1); }
  30% { transform: scale(1.08); color: #fff; }
  100% { transform: scale(1); }
}
.hint-flash { animation: sm-hint-flash 0.6s ease; }
/* Under the player minimum the lobby hint carries the one fact the host needs — make it
   read like a headline, not a footnote. */
.hint-under-min { font-size: 17px; font-weight: 700; color: #fff; }
/* Big transient response to a tap that can't be honored (e.g. Start game under the
   minimum) — unmissable even if the user never looks at the hint line. */
.sm-toast {
  position: fixed; left: 50%; top: 16%; transform: translateX(-50%) scale(0.92);
  z-index: 4000; background: #fff; color: var(--ink);
  font-family: var(--font); font-weight: 700; font-size: 19px; line-height: 1.35;
  text-align: center; padding: 16px 24px; border-radius: 18px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
  max-width: min(520px, 86vw);
  opacity: 0; pointer-events: none;
  transition: opacity 0.2s, transform 0.2s;
}
.sm-toast.show { opacity: 1; transform: translateX(-50%) scale(1); }
.btn-soft { background: rgba(255, 255, 255, 0.14); color: #fff; }
.btn-ghost { background: none; border: none; color: rgba(255, 255, 255, 0.7); font-family: var(--font); font-weight: 500; font-size: 15px; cursor: pointer; padding: 12px; }
.btn-ghost:active { color: #fff; }

/* ---------- Compose ---------- */
.compose-head {
  display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;
  max-height: 60px; overflow: hidden; opacity: 1;
  transition: max-height 0.25s ease, opacity 0.2s ease, margin-bottom 0.25s ease;
}
/* Phone keyboard open: the Back/day-tag header can't compete with the emoji strip above
   the textarea and the word-bank suggestions below it for the little space that's left —
   collapse it out of the layout (not just visually hidden) so both stay in view while
   typing. Reset on iPad (see 768px breakpoint) where there's room for everything. */
body.kb-open #screen-compose .compose-head,
body.kb-open #screen-build .compose-head {
  max-height: 0; opacity: 0; margin-bottom: 0; pointer-events: none;
}
/* The header collapse above wasn't enough on phones: the prompt text, order hint, and
   "Yesterday's stories" link between the emoji strip and the editor still pushed Submit
   below the fold once the keyboard ate ~40% of the screen. Fold those away too — the
   emoji strip itself stays (see the comment above), everything else is disposable once
   you're actually typing. */
body.kb-open #screen-compose .order-hint,
body.kb-open #screen-compose .compose-prompt,
body.kb-open #screen-compose .compose-inspire,
/* Game Night's write screen (#screen-ng-input) shares the same .order-hint text right
   above the editor, with no compose-prompt/compose-inspire equivalent — but its
   .party-input-head collapse alone still wasn't enough: on a tight viewport (e.g. an SE
   with the keyboard up) Lock-it-in was measured shrinking from its normal 54.5px to a
   near-unusable 20.5px. Same fix, same reason. */
body.kb-open #screen-ng-input .order-hint {
  max-height: 0; opacity: 0; margin: 0; padding: 0; pointer-events: none;
}
/* Game Night HOME with the keyboard up (typing your name, or a room name to join): the
   iOS keyboard eats nearly the bottom half of a phone, so fold away everything that isn't
   the input pair and its action button — tips, live counters, the found-rooms list. The
   fields plus Join/Host stay reachable without fighting the keyboard for space. */
body.kb-open #screen-net-home .onboard-tip,
body.kb-open #screen-net-home .daily-game-count,
body.kb-open #screen-net-home .known-host-banner,
body.kb-open #screen-net-home #netHomeSub,
body.kb-open #screen-net-home #netRooms,
body.kb-open #screen-net-home #netPacksHint { display: none; }
/* max-height:0 alone doesn't fully collapse these two — .compose-prompt's base rule sets
   min-height:24px (which wins over max-height in a min/max conflict), and .compose-inspire
   inherits 12px top/bottom padding from .btn-ghost (padding still renders even with 0
   content height). Zero both out here so the collapse is actually 0px, not ~24px. */
body.kb-open #screen-compose .compose-prompt { min-height: 0; }
/* Back button on the Today's Story flow (compose/pick/build) and Game Night home —
   the shared .btn-ghost (faint, no background) reads as too low-contrast to notice
   on these screens specifically; give it a visible pill here without touching
   .btn-ghost's other uses elsewhere (stats/vault headers, overlay/feed footers). */
#screen-compose .compose-head .btn-ghost,
#screen-pick .compose-head .btn-ghost,
#screen-build .compose-head .btn-ghost,
#screen-net-home .compose-head .btn-ghost,
#screen-feed .feed-head .btn-ghost {
  color: #fff; font-weight: 700; background: rgba(255, 255, 255, 0.14);
  border-radius: 999px; padding: 8px 16px 8px 12px;
}
#screen-compose .compose-head .btn-ghost:active,
#screen-pick .compose-head .btn-ghost:active,
#screen-build .compose-head .btn-ghost:active,
#screen-net-home .compose-head .btn-ghost:active,
#screen-feed .feed-head .btn-ghost:active {
  background: rgba(255, 255, 255, 0.24);
}
.compose-prompt {
  text-align: center; color: rgba(255, 255, 255, 0.85); font-size: 18px; font-weight: 500;
  margin: 8px 0 16px; min-height: 24px; max-height: 80px; overflow: hidden;
  transition: max-height 0.25s ease, opacity 0.2s ease, margin 0.25s ease;
}
/* Persistent (not dismissible — order confusion can recur every session, unlike a one-time
   tip) clarification that the emojis don't need to appear in the story in strip order. */
.order-hint {
  text-align: center; color: rgba(255,255,255,0.5); font-size: 13px; font-weight: 600;
  margin: 6px 0 2px; max-height: 40px; overflow: hidden;
  transition: max-height 0.25s ease, opacity 0.2s ease, margin 0.25s ease;
}
.editor { background: var(--card); border-radius: var(--radius); padding: 18px; box-shadow: var(--shadow); }
#storyInput {
  width: 100%; border: none; resize: none; outline: none;
  font-family: var(--font); font-size: 21px; font-weight: 500;
  color: var(--ink); line-height: 1.4; background: transparent;
  -webkit-user-select: text; user-select: text;
}
#storyInput::placeholder { color: #C4BFD4; }
.editor-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 12px; border-top: 1px solid var(--line); padding-top: 12px; }
.wordcount { font-size: 13px; color: var(--muted); font-weight: 500; }
.wordcount.over { color: var(--coral); }
.dice { background: #F3EFFA; border: none; width: 38px; height: 38px; border-radius: 12px; font-size: 18px; cursor: pointer; }
.dice:active { transform: scale(0.9) rotate(20deg); }
.micro { text-align: center; color: rgba(255, 255, 255, 0.45); font-size: 12px; margin-top: 14px; font-weight: 400; }
.micro.center { color: var(--muted); }

/* ---------- Playback ---------- */
#screen-playback { justify-content: center; }
/* Absolutely positioned (not part of the centered flex flow) so Back doesn't shift
   the vertically-centered story reveal below it. */
.playback-head { position: absolute; top: 8px; left: 22px; }
#screen-playback .playback-head .btn-ghost {
  color: #fff; font-weight: 700; background: rgba(255, 255, 255, 0.14);
  border-radius: 999px; padding: 8px 16px 8px 12px;
}
#screen-playback .playback-head .btn-ghost:active { background: rgba(255, 255, 255, 0.24); }
.playback-stage { position: relative; padding: 20px 0 8px; }
.playhead {
  height: 3px; border-radius: 3px; background: var(--grad-warm);
  width: 0%; margin: 14px auto 0; box-shadow: 0 0 12px var(--coral);
  transition: width 0.2s linear;
}
.play-story {
  text-align: center; color: #fff; font-size: 26px; font-weight: 600;
  line-height: 1.35; margin: 26px auto 0; max-width: 360px; min-height: 90px;
  letter-spacing: -0.3px;
}
.play-story .w { opacity: 0; display: inline-block; transform: translateY(8px); animation: wordIn 0.3s forwards; margin-right: 0.28em; }
@keyframes wordIn { to { opacity: 1; transform: none; } }
.play-actions { display: flex; flex-direction: column; gap: 4px; margin-top: 30px; opacity: 0; transition: opacity 0.5s; }
.play-actions.show { opacity: 1; }

/* ---------- Feed ---------- */
.feed-head .btn-ghost { margin: -12px 0 4px -12px; }
.feed-head h2 { color: #fff; font-size: 24px; font-weight: 700; }
.feed-sub { color: rgba(255, 255, 255, 0.6); font-size: 14px; margin-top: 2px; }
.tabs { display: flex; gap: 8px; margin: 16px 0 6px; }
.tab { flex: 1; background: rgba(255, 255, 255, 0.1); border: none; color: rgba(255, 255, 255, 0.7); font-family: var(--font); font-weight: 600; font-size: 14px; padding: 10px; border-radius: 14px; cursor: pointer; }
.tab.active { background: #fff; color: var(--ink); }
.feed-list { display: flex; flex-direction: column; gap: 12px; padding: 12px 0 16px; }
.story-card {
  background: var(--card); border-radius: 20px; padding: 16px 18px;
  display: flex; align-items: flex-start; gap: 12px; box-shadow: 0 10px 22px -14px rgba(0, 0, 0, 0.5);
  animation: cardIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.story-card.mine { background: linear-gradient(120deg, #fff, #FFF2EC); border: 2px solid var(--coral); }
@keyframes cardIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.sc-body { flex: 1; min-width: 0; }
.sc-text { font-size: 16px; font-weight: 500; color: var(--ink); line-height: 1.4; }
.sc-meta { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.sc-author { font-size: 12px; color: var(--muted); font-weight: 500; }
.you-badge { background: var(--grad); color: var(--ink); font-size: 10px; font-weight: 700; padding: 3px 8px; border-radius: 999px; letter-spacing: 0.5px; }
/* Deliberately muted/neutral (not the celebratory .you-badge gradient) — this marks
   house content with a baked-in heart count, not an achievement. */
.you-badge.example { background: rgba(0, 0, 0, 0.1); color: var(--muted); }
.rank-badge { font-size: 12px; color: var(--tangerine); font-weight: 700; }
.heart-btn {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  background: none; border: none; cursor: pointer; padding: 2px; min-width: 44px;
  position: relative;
}
/* Heart-tap burst: mini-hearts radiating out + a floating "+1" */
.heart-fx { position: absolute; left: 50%; top: 10px; width: 0; height: 0; pointer-events: none; z-index: 6; }
.heart-fx .hp { position: absolute; left: 0; top: 0; font-size: 11px; will-change: transform, opacity;
  animation: heartParticle 0.7s cubic-bezier(0.22,1,0.36,1) forwards; }
@keyframes heartParticle {
  0%   { transform: translate(-50%,-50%) scale(0.4); opacity: 0; }
  18%  { opacity: 1; }
  100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(1); opacity: 0; }
}
.heart-fx .plus1 { position: absolute; left: 0; top: 0; font-size: 13px; font-weight: 800;
  color: var(--coral); white-space: nowrap; will-change: transform, opacity;
  animation: heartPlus1 0.8s ease-out forwards; }
@keyframes heartPlus1 {
  0%   { transform: translate(-50%,-50%) scale(0.5); opacity: 0; }
  25%  { opacity: 1; }
  100% { transform: translate(-50%,-170%) scale(1); opacity: 0; }
}
.heart-ico { font-size: 22px; filter: grayscale(1) opacity(0.55); transition: transform 0.2s, filter 0.2s; }
.heart-btn.hearted .heart-ico { filter: none; transform: scale(1.15); animation: pop 0.32s; }
@keyframes pop { 0%{transform:scale(0.8);} 50%{transform:scale(1.35);} 100%{transform:scale(1.15);} }
.heart-num { font-size: 12px; color: var(--muted); font-weight: 600; }
.sc-actions { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.report-btn {
  background: none; border: none; cursor: pointer; padding: 2px 4px;
  font-size: 14px; opacity: 0.35; transition: opacity 0.2s;
}
.report-btn:hover { opacity: 0.7; }
.report-btn:disabled { font-size: 10px; opacity: 0.5; cursor: default; white-space: nowrap; }
.edit-btn {
  background: none; border: none; cursor: pointer; padding: 2px 4px;
  font-size: 14px; opacity: 0.5; transition: opacity 0.2s;
}
.edit-btn:hover { opacity: 0.9; }
.sc-label { font-size: 11px; font-weight: 700; color: var(--coral); letter-spacing: 0.3px; margin-bottom: 4px; text-transform: uppercase; }
/* Nested inside .home-card-group, directly under the Daily Story tile it belongs
   to — was a full-width card floating above both tiles, which read as if it applied
   to Game Night too. */
.my-today-card { margin: 10px 0 0; }
.feed-foot { display: flex; flex-direction: column; gap: 2px; padding-bottom: 10px; }
.feed-foot .btn-primary { width: 100%; }

/* ---------- Stats ---------- */
.stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 14px 0; }
.stat-card { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.12); border-radius: 20px; padding: 20px; text-align: center; }
.stat-num { font-family: var(--font-display); font-size: 42px; font-weight: 400; background: var(--grad-title); -webkit-background-clip: text; background-clip: text; color: transparent; line-height: 1; }
.stat-label { font-size: 13px; color: rgba(255, 255, 255, 0.65); margin-top: 8px; font-weight: 500; }
.best-block { background: var(--card); border-radius: 20px; padding: 18px; margin-bottom: 16px; box-shadow: var(--shadow); }
.best-block .bb-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; font-weight: 600; }
.best-block .bb-text { font-size: 18px; font-weight: 600; color: var(--ink); margin-top: 8px; line-height: 1.35; }
.best-block .bb-hearts { margin-top: 10px; font-size: 14px; color: var(--coral); font-weight: 600; }
.best-block.empty { text-align: center; color: var(--muted); }

/* Game Night winner reveal — sits directly on the dark screen background (like
   .play-story already does), NOT inside a .best-block light card, or the white
   text would be invisible. */
.ng-win-reveal { margin-bottom: 16px; }
.ng-win-author { text-align: center; color: rgba(255,255,255,0.55); font-size: 14px; font-weight: 600; margin-top: 6px; }
.ng-win-tie-block { margin-bottom: 20px; }
.ng-win-tie-text { text-align: center; color: #fff; font-size: 20px; font-weight: 600; line-height: 1.4; margin: 0; }

/* ---------- Overlay ---------- */
.overlay { position: fixed; inset: 0; background: rgba(15, 12, 28, 0.82); backdrop-filter: blur(6px); display: flex; align-items: center; justify-content: center; z-index: 50; padding: max(24px, env(safe-area-inset-top)) 24px max(24px, env(safe-area-inset-bottom)); animation: screenIn 0.3s; }
.overlay[hidden] { display: none; }
.overlay-card { width: 100%; max-width: 360px; display: flex; flex-direction: column; gap: 14px; }
#shareImg { width: 100%; border-radius: 22px; box-shadow: var(--shadow); }
.overlay-actions { display: flex; gap: 8px; }
.overlay-actions .btn { flex: 1; }
.overlay-actions .btn-ghost { flex: 0 0 auto; }

/* ---------- Practice banner ---------- */
.practice-banner {
  position: absolute; top: calc(54px + env(safe-area-inset-top)); left: 50%; transform: translateX(-50%);
  z-index: 40; background: var(--grad-warm); color: #1E1B2E;
  font-weight: 700; font-size: 11px; letter-spacing: 0.4px;
  padding: 6px 14px; border-radius: 999px; white-space: nowrap; pointer-events: none;
  box-shadow: 0 6px 16px -6px rgba(0, 0, 0, 0.45);
}
.practice-banner[hidden] { display: none; }

/* ---------- Vault ---------- */
.vault-title { color: #fff; font-size: 25px; font-weight: 700; margin-top: 6px; }
/* Personal win, turn-result banner — same gold/bigger/glow treatment as the round-champion
   "you-won" state, plus its own pop-in so it reads distinctly from other players' plain title. */
.vault-title.you-won { font-size: 34px; color: var(--gold); text-shadow: 0 4px 24px rgba(255, 226, 52, 0.6); animation: youWonTitleIn 0.7s cubic-bezier(0.3, 1.6, 0.5, 1) both, youWonGlow 1.8s ease-in-out 0.7s infinite; }
@keyframes youWonTitleIn { 0% { opacity: 0; transform: scale(0.4) rotate(-10deg); } 60% { opacity: 1; transform: scale(1.14) rotate(3deg); } 100% { transform: scale(1) rotate(0); } }
.vault-sub { color: rgba(255, 255, 255, 0.6); font-size: 14px; margin: 6px 0 16px; line-height: 1.45; }
.vault-list { display: flex; flex-direction: column; gap: 10px; padding-bottom: 18px; }
.vault-card {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 18px; padding: 14px 16px; cursor: pointer; font-family: var(--font);
  transition: transform 0.12s, background 0.2s; text-align: left;
  animation: cardIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.vault-card:active { transform: scale(0.98); }
.vault-left { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.vault-emojis { display: flex; gap: 6px; font-size: 24px; }
.vault-tone { color: rgba(255, 255, 255, 0.5); font-size: 12px; text-transform: capitalize; }
.vault-play {
  color: var(--tangerine); font-weight: 700; font-size: 14px; flex-shrink: 0;
  background: none; border: none; font-family: var(--font); cursor: pointer; padding: 4px 0;
}
/* Vault's two-action split: "browse" (read-only, real past-day stories) vs "play" (write
   your own practice round) — separate buttons, not one card-wide tap target, so both are
   discoverable at a glance instead of one hidden behind the other. */
.vault-actions { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex-shrink: 0; }
.vault-browse {
  color: rgba(255, 255, 255, 0.6); font-weight: 600; font-size: 12px;
  background: none; border: none; font-family: var(--font); cursor: pointer; padding: 4px 0;
}
.vault-browse:active { color: #fff; }
.vault-play:active { color: var(--tangerine); opacity: 0.7; }
/* Store-specific card lift — scoped to the Packs screen so the Vault's identical
   .vault-card elsewhere is untouched. Small, real depth cue, not a redesign. */
#screen-packs .vault-card { box-shadow: 0 8px 20px -14px rgba(0, 0, 0, 0.5); }
.pack-section-label { color: rgba(255, 255, 255, 0.55); font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; margin: 4px 2px 8px; }
.pack-bundle-card { position: relative; overflow: hidden; border: 2px solid transparent; background-image: linear-gradient(rgba(255,255,255,0.08), rgba(255,255,255,0.08)), var(--grad); background-origin: border-box; background-clip: padding-box, border-box; }
/* .vault-tone/.vault-play default to colors tuned for the dark .vault-card background —
   invisible/low-contrast on this card's light gold gradient. Same root cause as the
   .btn-primary fix: text color chosen for one background reused on a lighter one. */
.pack-bundle-card .vault-tone { color: var(--ink); opacity: 0.75; }
.pack-bundle-card .vault-play { color: var(--ink); }
/* Diagonal light sweep across the bundle card — the one "special offer" surface in the
   store, so it's the only card that gets this treatment (restraint = it still reads as
   special). Loops with a long pause, not a constant distraction. */
.pack-shimmer {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg, transparent 30%, rgba(255, 255, 255, 0.55) 50%, transparent 70%);
  transform: translateX(-120%);
  animation: packShimmerSweep 3.6s ease-in-out infinite;
}
@keyframes packShimmerSweep {
  0%, 55% { transform: translateX(-120%); }
  75%, 100% { transform: translateX(120%); }
}
@media (prefers-reduced-motion: reduce) {
  .pack-shimmer { display: none; }
}
.pack-save-badge {
  position: absolute; top: 10px; right: 10px; z-index: 1;
  background: var(--ink); color: #fff; font-size: 11px; font-weight: 800;
  letter-spacing: 0.3px; padding: 4px 9px; border-radius: 999px;
  box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.4);
}
.pack-price-pill { color: var(--ink); font-weight: 800; font-size: 14px; flex-shrink: 0; background: rgba(255, 255, 255, 0.85); padding: 5px 12px; border-radius: 999px; }
.pack-price-pill--bundle { background: rgba(255, 255, 255, 0.92); font-size: 16px; }
.pack-owned { color: var(--mint); font-weight: 700; font-size: 14px; flex-shrink: 0; }
.pack-round-badge { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; margin: 6px 0; }
.pack-round-badge:empty, .pack-round-badge[hidden] { display: none; }
.pack-round-tag { font-family: var(--font); font-size: 12px; font-weight: 600; color: var(--on-dark-70, rgba(255,255,255,0.7)); background: var(--glass-fill, rgba(255,255,255,0.08)); border: 1px solid var(--glass-stroke, rgba(255,255,255,0.14)); border-radius: 999px; padding: 3px 10px; cursor: pointer; }
.pack-nudge-card { width: 100%; margin: 10px 0; text-align: left; }
.pack-nudge-card .vault-tone { text-transform: none; }
.pack-free-banner { background: var(--glass-fill); border: 1px solid var(--glass-stroke); border-radius: 18px; padding: 12px 16px; margin-bottom: 12px; }
.pack-free-label { color: rgba(255, 255, 255, 0.85); font-size: 13px; font-weight: 700; margin-bottom: 8px; }
.pack-free-categories { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.pack-free-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 600; color: rgba(255, 255, 255, 0.75); background: rgba(255, 255, 255, 0.08); border-radius: 999px; padding: 4px 10px; }
.pack-free-chip b { color: var(--mint); font-weight: 700; }
.pack-free-combos { font-size: 12px; color: rgba(255, 255, 255, 0.6); }
.pack-free-explainer { font-size: 12px; color: rgba(255, 255, 255, 0.65); line-height: 1.4; margin-bottom: 10px; }

/* ---------- Upsell sheet ---------- */
.overlay-card.sheet { text-align: center; gap: 12px; background: var(--card); border-radius: 26px; padding: 28px 24px; }
.sheet-emoji { font-size: 44px; }
.pack-upsell-emojis { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 8px; font-size: 34px; line-height: 1; }
.pack-upsell-more { font-size: 15px; font-weight: 700; color: var(--muted); align-self: center; }
.pack-upsell-words { color: var(--muted); font-size: 13px; font-style: italic; }
.overlay-card.sheet p.pack-upsell-alreadyfree { display: inline-block; align-self: center; font-size: 12px; font-weight: 600; color: var(--mint); background: rgba(46, 196, 182, 0.12); border-radius: 999px; padding: 5px 14px; margin: 0 auto; line-height: 1.3; }
.overlay-card.sheet h3 { color: var(--ink); font-size: 21px; font-weight: 700; }
.overlay-card.sheet p { color: var(--muted); font-size: 14px; line-height: 1.5; }
.overlay-card.sheet .btn-primary { width: 100%; }
/* .btn-ghost defaults to near-white text for the dark plum background — invisible on
   this white sheet card. Every dismiss/close button inside a sheet needs this override
   (Vault upsell, Pack upsell, avatar/language pickers), not just one call site. */
.overlay-card.sheet .btn-ghost { color: var(--muted); }
.overlay-card.sheet .btn-ghost:active { color: var(--ink); }
/* ---------- Facilitator harness ---------- */
/* mode toggle is built for the dark screen — make it legible on the white sheet */
.overlay-card.sheet .mode-toggle { background: #F3EFFA; border-color: transparent; }
.overlay-card.sheet .mode-opt { color: var(--muted); }
.overlay-card.sheet .mode-opt.active { background: #fff; color: var(--ink); box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.25); }

/* ---------- Feed footer row ---------- */
.feed-foot-row { display: flex; gap: 8px; }
.feed-foot-row .btn-ghost { flex: 1; }
.you-badge.practice { background: var(--grad-warm); color: #1E1B2E; }

/* ---------- Mode pick (first launch) ---------- */
#screen-mode-pick { justify-content: center; }
.mode-pick-wrap {
  display: flex; flex-direction: column; align-items: center;
  gap: 10px; padding: 0 4px;
}
.mode-pick-logo {
  display: flex; align-items: center; gap: 8px; margin-bottom: 6px;
}
.mode-pick-logo .brand-emoji { font-size: 30px; }
.mode-pick-logo .brand-name { font-size: 32px; background: var(--grad-title); -webkit-background-clip: text; background-clip: text; color: transparent; text-shadow: none; }
.mode-pick-tag {
  color: rgba(255, 255, 255, 0.6); font-size: 16px; font-weight: 400;
  text-align: center; margin-bottom: 10px;
}
.mode-pick-cards { display: flex; flex-direction: column; gap: 12px; width: 100%; }
.mode-pick-card {
  display: flex; align-items: center; gap: 16px;
  background: rgba(255, 255, 255, 0.09);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 22px; padding: 22px 20px;
  font-family: var(--font); cursor: pointer; text-align: left;
  transition: background 0.18s, border-color 0.18s, transform 0.12s;
}
.mode-pick-card:active { transform: scale(0.98); background: rgba(255, 255, 255, 0.15); }
.mpc-icon { font-size: 36px; flex-shrink: 0; }
.mpc-body { flex: 1; min-width: 0; }
.mpc-name { color: #fff; font-weight: 700; font-size: 18px; line-height: 1.2; }
.mpc-sub { color: rgba(255, 255, 255, 0.55); font-size: 13px; font-weight: 400; margin-top: 4px; line-height: 1.4; }
.mpc-arrow { color: rgba(255, 255, 255, 0.4); font-size: 22px; font-weight: 400; flex-shrink: 0; }

/* ---------- Today screen mode switching ---------- */
#screen-today.mode-solo .room-only { display: none; }
#screen-today.mode-living-room .solo-only { display: none; }
.room-only { display: flex; flex-direction: column; gap: 12px; }
.switch-mode-btn {
  font-size: 13px; color: rgba(255, 255, 255, 0.35);
  padding: 8px; margin-top: 4px;
}
.switch-mode-btn:active { color: rgba(255, 255, 255, 0.7); }

/* ---------- Confetti ---------- */
.confetti { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.confetti span { position: absolute; top: -12px; width: 10px; height: 14px; border-radius: 2px; animation: fall linear forwards; }
@keyframes fall { to { transform: translateY(110vh) rotate(720deg); opacity: 0; } }

/* ===================================================================
   iPad Layout Enhancements
   768px  → tablet portrait: wider container, side-by-side home cards,
            2×2 pick grid, scaled filmstrip, larger touch targets
   1024px → landscape / large iPad: two-panel compose & build,
            2-col feed & judge, vault grid
   =================================================================== */

/* Wrapper defaults — transparent columns on mobile */
.ipad-compose-main,
.ipad-context,
.ipad-editor,
.ipad-build-main,
.ipad-build-left,
.ipad-build-right { display: flex; flex-direction: column; }

/* ---- 768px — tablet portrait ------------------------------------ */
@media (min-width: 768px) {
  #app { max-width: 680px; }

  /* iPad has room for the header even with the keyboard up — undo the phone-only
     kb-open collapse from the Compose section above. */
  body.kb-open #screen-compose .compose-head,
  body.kb-open #screen-build .compose-head,
  body.kb-open #screen-ng-input .party-input-head {
    max-height: none; opacity: 1; margin-bottom: 8px; pointer-events: auto;
  }
  body.kb-open #screen-ng-input .party-input-head { margin-bottom: 12px; }
  body.kb-open #screen-compose .order-hint,
  body.kb-open #screen-compose .compose-prompt,
  body.kb-open #screen-compose .compose-inspire,
  body.kb-open #screen-ng-input .order-hint {
    max-height: none; opacity: 1; pointer-events: auto;
  }
  body.kb-open #screen-compose .compose-prompt { margin: 8px 0 16px; }
  body.kb-open #screen-compose .order-hint,
  body.kb-open #screen-ng-input .order-hint { margin: 6px 0 2px; }
  body.kb-open #screen-compose .compose-inspire { margin: -8px 0 12px; }
  /* iPad also has room for the write screen's normal layout with the keyboard up —
     undo the phone-only squeeze (flexible editor / compact strip / hidden tip). */
  body.kb-open #screen-ng-input .editor { flex: none; height: min(200px, 35dvh); }
  body.kb-open #screen-ng-input .onboard-tip { display: block; }
  body.kb-open #screen-ng-input .filmstrip.sticky .pf-emoji { width: 58px; height: 58px; font-size: 36px; }
  /* Same story for the Game Night home clutter-fold — phones only. */
  body.kb-open #screen-net-home .onboard-tip,
  body.kb-open #screen-net-home .daily-game-count,
  body.kb-open #screen-net-home .known-host-banner,
  body.kb-open #screen-net-home #netHomeSub,
  body.kb-open #screen-net-home #netRooms,
  body.kb-open #screen-net-home #netPacksHint { display: revert; }

  /* Topbar */
  .topbar { padding-left: 28px; padding-right: 28px; padding-bottom: 14px; }
  .brand-emoji { font-size: 26px; }
  .brand-name  { font-size: 28px; }
  .streak      { font-size: 17px; padding: 8px 16px; }

  /* Screens */
  .screen {
    top: calc(70px + env(safe-area-inset-top));
    padding-left: 32px;
    padding-right: 32px;
    padding-bottom: calc(40px + env(safe-area-inset-bottom));
  }

  /* Filmstrip */
  .filmstrip.big .pf-emoji    { width: 80px; height: 80px; font-size: 52px; border-radius: 20px; }
  .filmstrip.sticky .pf-emoji { width: 68px; height: 68px; font-size: 44px; }
  .filmstrip.mini .pf-emoji   { font-size: 48px; }
  .filmstrip.play .pf-emoji   { width: 68px; height: 68px; font-size: 44px; }
  #ngWaitStrip .pf-emoji      { width: 68px !important; height: 68px !important; font-size: 44px !important; }

  /* Today */
  .home-kicker   { font-size: 15px; }
  .home-modes    { flex-direction: row; gap: 16px; margin-top: 24px; }
  .home-card     { flex: 1; flex-direction: column; align-items: flex-start; gap: 12px; padding: 26px 22px; min-height: 140px; }
  .home-card-group { flex: 1; }
  .home-card-group .home-card { flex: none; width: 100%; }
  .home-card-icon  { font-size: 40px; }
  .home-card-title { font-size: 22px; }
  .home-card-sub   { font-size: 14px; }

  /* Home rail — portrait tablet: full-width row below the modes, fills the vertical void.
     align-items:flex-start so the compact "run" card keeps its natural height next to
     the taller yesterday card instead of stretching with empty space. */
  .home-rail { display: flex; flex-direction: row; align-items: flex-start; gap: 18px; margin-top: 26px; }
  .rail-run        { flex: 0 0 248px; }
  .rail-yesterday  { flex: 1; min-width: 0; }
  .rail-story-text { font-size: 15px; }
  /* push the home foot to sit just under the rail rather than the screen bottom */
  .home-foot { margin-top: 22px; }

  /* Buttons */
  .btn        { font-size: 17px; padding: 16px 26px; }
  .btn-lg     { padding: 20px; font-size: 18px; border-radius: 18px; }
  .btn-ghost  { font-size: 16px; padding: 14px; }
  .word-chip  { font-size: 17px; padding: 12px 16px; border-radius: 13px; }

  /* Compose */
  .compose-prompt { font-size: 21px; margin: 14px 0 20px; }
  #storyInput     { font-size: 24px; line-height: 1.5; }
  .editor         { padding: 24px; border-radius: 28px; }
  .editor-foot    { margin-top: 16px; padding-top: 16px; }
  .dice           { width: 48px; height: 48px; font-size: 22px; border-radius: 14px; }
  .wordcount      { font-size: 15px; }

  /* Pick — 2×2 grid */
  .pick-list { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
  .pick-card { font-size: 18px; padding: 22px; }

  /* Build */
  .build-sentence { min-height: 120px; padding: 16px; }
  .build-bank     { max-height: 44vh; }

  /* Playback */
  .play-story   { font-size: 30px; max-width: 520px; }
  .filmstrip.play .pf-emoji.lit { transform: translateY(-10px) scale(1.22) !important; }

  /* Feed */
  .feed-head h2 { font-size: 28px; }
  .sc-text      { font-size: 17px; }
  .heart-ico    { font-size: 26px; }
  .heart-num    { font-size: 14px; }

  /* Stats */
  .stat-num          { font-size: 54px; }
  .stat-label        { font-size: 14px; }
  .best-block .bb-text { font-size: 20px; }

  /* Game Night */
  .ng-countdown-num  { font-size: 140px; }
  .party-pass-name   { font-size: 34px; }
  .party-pass-sub    { font-size: 17px; }
  .net-room-name     { font-size: 48px; padding: 28px 16px; }
  #ngInput           { font-size: 26px; }
  #screen-ng-input .editor { height: min(280px, 42dvh); }
  .judge-card-text   { font-size: 19px; }
  .party-judge-card  { font-size: 19px; padding: 20px 22px; }
  .judge-card-hidden { padding: 30px 22px; }
  .ng-timer          { font-size: 26px; padding: 6px 20px; }
  .party-score-row   { font-size: 18px; padding: 16px 22px; }
  .party-score-pts   { font-size: 24px; }
  .net-room          { padding: 20px 22px; }
  .net-room-label    { font-size: 22px; }
  .lobby-qr          { width: 220px; height: 220px; }
  .judge-pick-btn    { font-size: 18px; padding: 16px 22px; border-radius: 16px; }

  /* Game Night centered screens — give the hero presence and keep short content
     from floating in a void on big iPad screens. */
  .party-center        { max-width: 620px; gap: 14px; }
  .party-intro-strip   { margin: 24px 0 12px; }
  .oracle-emojis       { font-size: 3.8rem; gap: 0.8rem; }
  .oracle-roster       { gap: 10px; max-width: 460px; }
  .rtile               { font-size: 1rem; padding: 8px 16px 8px 13px; }
  .oracle-msg          { font-size: 1.35rem; min-height: 2rem; }
  .oracle-track        { width: min(380px, 56vw); height: 9px; }
  .oracle-count        { font-size: 0.95rem; }
  /* Scoreboard — center the result block vertically instead of pinning the CTA low */
  #screen-ng-score     { justify-content: center; }
  #ngScoreTitle, #ngWinStory, #ngScoreboard,
  #screen-ng-score .btn-lg, #ngScoreWait {
    width: 100%; max-width: 600px; margin-left: auto; margin-right: auto;
  }
  /* cancel the base .btn-lg margin-top:auto so justify-content:center can group everything */
  #screen-ng-score .btn-lg { margin-top: 10px; }
  #ngScoreTitle        { font-size: 30px; margin-bottom: 8px; }

  /* Overlays */
  .overlay-card          { max-width: 500px; }
  .overlay-card.sheet    { padding: 36px 32px; }
  .overlay-card.sheet h3 { font-size: 24px; }

  /* Vault */
  .vault-title  { font-size: 28px; }
  .vault-list   { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
  .vault-emojis { font-size: 28px; }
}

/* ---- 1024px — landscape / large iPad ---------------------------- */
@media (min-width: 1024px) {
  #app { max-width: 1080px; }

  .topbar { padding-left: 36px; padding-right: 36px; }
  .screen { padding-left: 40px; padding-right: 40px; }

  /* Today — two-column dashboard: hero on the left, live rail on the right.
     Fills the landscape dead-zone with your run + yesterday's best stories. */
  #screen-today {
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 48px;
    overflow-y: auto;
  }
  .home-main {
    display: flex; flex-direction: column;
    flex: 1 1 0; max-width: 520px;
  }
  .home-main .home-foot { margin-top: 30px; }
  .home-rail {
    display: flex; flex-direction: column;
    flex: 1 1 0; max-width: 440px; gap: 18px; margin-top: 0;
  }
  .rail-run { flex: 0 0 auto; }
  .rail-run .rail-stats { margin-top: 14px; }
  .rail-card { padding: 22px 24px; }
  .rs-num { font-size: 34px; }
  .rail-story-text { font-size: 15px; }

  /* Today — richer home cards */
  .home-card { min-height: 160px; padding: 30px 26px; }
  .home-card-icon  { font-size: 44px; }
  .home-card-title { font-size: 24px; }

  /* Compose — two-panel */
  #screen-compose { overflow-y: hidden; }
  .ipad-compose-main {
    flex-direction: row;
    gap: 36px;
    flex: 1;
    min-height: 0;
    align-items: flex-start;
  }
  .ipad-context {
    width: 320px;
    flex-shrink: 0;
    align-items: center;
    padding-top: 8px;
    position: sticky;
    top: 0;
  }
  .ipad-context .compose-prompt { text-align: center; }
  /* Scale down filmstrip to fit 320px panel (5×56+4×8=312) */
  .ipad-context .pf-emoji {
    width: 56px !important;
    height: 56px !important;
    font-size: 36px !important;
  }
  .ipad-editor {
    flex: 1;
    min-width: 0;
    overflow-y: auto;
  }
  /* Cancel btn-lg auto margin in two-panel — button should follow editor naturally */
  .ipad-editor .btn-lg { margin-top: 16px; }

  /* Build — two-panel */
  #screen-build { overflow-y: hidden; }
  .ipad-build-main {
    flex-direction: row;
    gap: 28px;
    flex: 1;
    min-height: 0;
    overflow: hidden;
  }
  .ipad-build-left {
    width: 340px;
    flex-shrink: 0;
    gap: 10px;
    overflow-y: auto;
  }
  /* Scale filmstrip in build left panel (5×56+4×8=312 fits 340) */
  .ipad-build-left .pf-emoji {
    width: 56px !important;
    height: 56px !important;
    font-size: 36px !important;
  }
  .ipad-build-right {
    flex: 1;
    min-width: 0;
    overflow: hidden;
  }
  .ipad-build-right .build-bank {
    max-height: none;
    height: 100%;
    overflow-y: auto;
  }

  /* Pick — bigger cards in 2×2 */
  .pick-card { font-size: 19px; padding: 24px; }

  /* Feed — 2-col story grid */
  .feed-list {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
    align-items: start;
  }
  .feed-foot {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 12px;
    align-items: center;
  }
  .feed-foot .btn-primary,
  .feed-foot .btn-soft { flex: 1; margin-top: 0; }
  .feed-foot-row       { width: 100%; justify-content: center; }

  /* Game Night — judge cards in 2-col grid */
  #ngJudgeList {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
    align-items: start;
  }

  /* Vault — 3-col */
  .vault-list { grid-template-columns: 1fr 1fr 1fr; }

  /* Stats grid — wider cards */
  .stat-grid { grid-template-columns: 1fr 1fr 1fr 1fr; }
}

/* ── Judge Oracle (wait screen, collect phase) ─────────────────────────── */
#ngJudgeOracle {
  display: flex; flex-direction: column; align-items: center;
  gap: 0.5rem; margin-top: 0.5rem; width: 100%;
}
.oracle-emojis {
  display: flex; gap: 0.5rem; line-height: 1;
  font-size: 2.6rem;
}
/* Fidget toy — tappable emojis that pop & spin (something to do while waiting) */
.fidget-emoji {
  cursor: pointer; touch-action: manipulation; user-select: none;
  display: inline-block; transition: transform 0.12s ease;
  animation: fidget-idle 2.6s ease-in-out infinite;
}
.fidget-emoji:nth-child(2) { animation-delay: 0.3s; }
.fidget-emoji:nth-child(3) { animation-delay: 0.6s; }
.fidget-emoji:nth-child(4) { animation-delay: 0.9s; }
.fidget-emoji:active { transform: scale(0.85); }
.fidget-emoji.fidget-pop { animation: fidget-pop 0.5s cubic-bezier(.22,1,.36,1); }
@keyframes fidget-idle {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-5px); }
}
@keyframes fidget-pop {
  0%   { transform: scale(1) rotate(0); }
  45%  { transform: scale(1.55) rotate(var(--spin, 300deg)); }
  100% { transform: scale(1) rotate(0); }
}
/* Live "the room" tiles — who's still writing vs locked in */
.oracle-roster {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 8px; margin: 0.2rem 0; max-width: 360px;
}
.rtile {
  display: flex; align-items: center; gap: 6px;
  background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.14);
  border-radius: 999px; padding: 6px 13px 6px 11px;
  font-family: var(--font); font-weight: 600; font-size: 0.9rem; color: rgba(255,255,255,0.7);
  transition: background 0.25s, color 0.25s, border-color 0.25s;
}
.rtile-ico { font-size: 0.95rem; }
.rtile.locked {
  background: rgba(46,196,182,0.18); border-color: rgba(46,196,182,0.5); color: #fff;
}
.rtile.rtile-pop { animation: rtile-pop 0.5s cubic-bezier(.22,1,.36,1); }
@keyframes rtile-pop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.16); }
  100% { transform: scale(1); }
}
.oracle-msg {
  font-size: 1.05rem; font-style: italic; font-family: var(--font);
  color: var(--grape); min-height: 1.6rem; text-align: center;
  transition: opacity 0.35s ease;
  padding: 0 1.5rem;
}
.oracle-msg.oracle-fade { opacity: 0; }
.oracle-track {
  width: min(280px, 72vw); height: 7px;
  background: rgba(255,255,255,0.18); border-radius: 99px;
  overflow: hidden; margin-top: 0.4rem;
}
.oracle-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--grape-deep), var(--coral));
  border-radius: 99px; width: 0%;
  transition: width 0.6s cubic-bezier(.22,1,.36,1);
}
.oracle-count {
  font-size: 0.82rem; opacity: 0.55; font-family: var(--font);
  letter-spacing: 0.02em;
}
@keyframes oracle-ping {
  0%   { transform: scale(1);    opacity: 0.55; }
  40%  { transform: scale(1.18); opacity: 1;    color: var(--coral); }
  100% { transform: scale(1);    opacity: 0.55; }
}
.oracle-count.oracle-ping { animation: oracle-ping 0.55s ease; }

/* ---------- Mascot sprites ---------- */
.mascot-sprite {
  position: absolute;
  pointer-events: none;
  z-index: 0;
  user-select: none;
}

@keyframes mascot-bob {
  0%, 100% { transform: translateY(0) rotate(-2deg); }
  50%      { transform: translateY(-8px) rotate(2deg); }
}

/* Oracle (waiting-to-judge): thinking character, upper-right of the timer/title.
   Static sitting pose only — no blink swap, no lobby/header appearance. */
.mascot-thinking-wrap {
  position: absolute;
  top: 6px;
  right: 4px;
  width: 108px;
  height: 148px;
  pointer-events: none;
  z-index: 0;
  animation: mascot-bob 2.6s ease-in-out infinite;
}
.mascot-thinking-wrap img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* Score: celebration character, top-left corner, pops in with the confetti */
.mascot-celebrate {
  width: 118px;
  left: 4px;
  top: 4px;
  opacity: 0;
  transform: scale(0.7) translateY(15px);
}
.screen.active .mascot-celebrate {
  animation: mascot-pop-in 0.45s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
@keyframes mascot-pop-in {
  0%   { opacity: 0; transform: scale(0.7) translateY(15px); }
  60%  { opacity: 1; transform: scale(1.06) translateY(-3px); }
  100% { opacity: 1; transform: scale(1) translateY(0); }
}

/* ---------- AI Judge mascot — inline SVG "robot host" badge, deliberately built from shapes
   (not an emoji) so it reads as its own custom character, distinct from the human judge's
   sprite-thinking.png. Reused at full size on the judging/reveal screen and mini size on the
   winner-reveal screen (see index.html's #screen-ng-ai-judging and #ngAiQuipWrap). ---------- */
.ai-judge-mascot {
  width: 84px; height: 84px;
  margin: 0 auto 6px;
  animation: ai-judge-pulse 1.7s ease-in-out infinite;
}
.ai-judge-mascot-svg { width: 100%; height: 100%; overflow: visible; }
.ai-judge-grad-a { stop-color: var(--grape-deep); }
.ai-judge-grad-b { stop-color: var(--mint); }
.ai-judge-face { fill: url(#aiJudgeGrad); }
.ai-judge-eye { fill: var(--cream); }
.ai-judge-mouth { fill: none; stroke: var(--cream); stroke-width: 4.5; stroke-linecap: round; }
.ai-judge-antenna { stroke: var(--gold); stroke-width: 3.5; stroke-linecap: round; }
.ai-judge-antenna-dot { fill: var(--gold); animation: ai-judge-blink 1.7s ease-in-out infinite; }
@keyframes ai-judge-pulse {
  0%, 100% { transform: scale(1) translateY(0); }
  50%      { transform: scale(1.05) translateY(-3px); }
}
@keyframes ai-judge-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}
/* Mini size for the winner-reveal screen, sitting inline with the quip line instead of the
   full "thinking" hero spot — no need to re-pulse once the pick is already in. */
.ai-judge-mascot-mini {
  width: 34px; height: 34px;
  margin: 0; flex: 0 0 auto;
  animation: none;
}
.ai-judge-quip-wrap {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin: 2px 0 4px;
  /* #screen-ng-score always pops in .mascot-celebrate (position:absolute, top-left, z-index:0)
     behind whatever's in normal flow — without a stacking context of our own here, this row
     (which sits right under the title, in that same top-left zone) would render BEHIND that
     mascot's artwork instead of over it. */
  position: relative; z-index: 1;
}
.ai-judge-quip-wrap[hidden] { display: none; }  /* see .join-requests[hidden] for why this override is required */

/* Per-submission AI reaction line inside the staged-reveal feed cards — appears in TWO
   different backgrounds (the light .judge-card-revealed for the turn owner's tap-to-reveal
   view, and the dark .wait-revealed-card for everyone else's read-only feed), so it carries
   its own opaque background rather than relying on inherited text color for contrast — a
   real speech bubble, not just an italic caption. Sized to match/exceed .judge-card-text
   (21px) — this is the AI's voice reacting out loud, it should read at least as big as the
   story it's reacting to, not smaller. */
.ai-judge-comment {
  position: relative;
  font-size: 21px; font-weight: 700; line-height: 1.4;
  color: #fff; background: var(--grape-deep);
  border-radius: 16px; padding: 12px 16px;
  margin: 18px 0 0;
  box-shadow: 0 8px 18px -10px rgba(123,47,247,0.6);
}
/* Speech-bubble tail, pointing up at what it's reacting to. */
.ai-judge-comment::before {
  content: ""; position: absolute; top: -8px; left: 22px;
  width: 0; height: 0;
  border-left: 9px solid transparent; border-right: 9px solid transparent;
  border-bottom: 9px solid var(--grape-deep);
}
.ai-judge-card { border-left: 3px solid var(--grape-deep); }
