/* Cerebos Braai Master — base shell styles. UI module may extend below the marker.
   Build v5. */
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; overflow: hidden; background: #111C4E; }
body { font-family: 'Trebuchet MS', 'Segoe UI', system-ui, sans-serif; touch-action: manipulation; }
#app { position: fixed; inset: 0; }
#game { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
#overlay { position: absolute; inset: 0; pointer-events: none; }
#overlay > * { pointer-events: auto; }

/* Pack block face: Anton (OFL, self-hosted in assets - no external request
   at play time), the condensed heavy grotesque matching the label's BRAAI
   SALT lettering. The brand-chip canvases pick it up via document.fonts;
   until it lands they draw the vector fallback letterforms in js/ui.js. */
/* TTF on purpose, not woff2: the Google woff2 subset "loaded" but silently
   rendered fallback glyphs in testing (measureText identical to Times);
   the ttf renders correctly. 130 KB, loads async behind the vector
   fallback, so nothing blocks or flashes. */
@font-face {
  font-family: 'PackBlock';
  src: url('../assets/anton-latin.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ===== UI MODULE STYLES BELOW (owned by ui builder) ===== */

:root {
  --navy: #1B2A6B;
  --navy-deep: #111C4E;
  --brown: #6B4A2F;
  --cream: #F7F2E7;
  --gold: #F2A93B;
  --gold-deep: #D97E22;
  --green: #1F6B3A;
}

.hidden { display: none !important; }

/* ---------- screens ---------- */
.scr {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  /* NOT `center`: a centred flex item taller than the scroll port has its TOP
     half pushed to a negative offset that no scrollbar can ever reach (the pack
     shot lost its cap and the scoring key lost its last rows at 1240x742, where
     none of the max-height queries below apply). `safe center` centres exactly
     as before while the content fits and falls back to start-aligned (fully
     scrollable) the moment it does not. The plain `center` above it is the
     fallback for engines that do not parse the `safe` keyword. */
  justify-content: center;
  justify-content: safe center;
  flex-direction: column;
  padding: max(16px, env(safe-area-inset-top))
           max(16px, env(safe-area-inset-right))
           max(16px, env(safe-area-inset-bottom))
           max(16px, env(safe-area-inset-left));
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
/* auto margins keep tall content scrollable instead of clipping when centred */
.scr > .start-inner, .scr > .card { margin-top: auto; margin-bottom: auto; }
/* The painted savanna is the strongest asset in the build, so the start
   screen scrim is now barely there — legibility is bought locally by
   .title-block and the lockup's own contrast, not by dimming the art. */
.scr-start { background: radial-gradient(120% 90% at 50% 20%, rgba(17,28,78,0.05), rgba(17,28,78,0.35)); }
.scr-reg, .scr-over { background: rgba(12, 17, 46, 0.72); backdrop-filter: blur(3px); }

/* ---------- start screen ---------- */
.start-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: 100%;
  max-width: 420px;
  text-align: center;
  animation: rise-in 0.5s ease-out both;
}
/* Pack shot: the client signs off on pack accuracy, so the hero product is
   presented upright and still — no bob, no tilt — with only a grounding
   shadow. Do not re-add transform animations here. */
.bottle-wrap { line-height: 0; filter: drop-shadow(0 12px 20px rgba(0,0,0,0.5)); }
.bottle-cv { display: block; }
/* height is capped in vh so the cap of the pack is never sliced off the top
   of a short viewport (measured clipping at 560px tall before this) */
.bottle-img {
  display: block;
  height: clamp(170px, 26vh, 260px);
  width: auto;
  user-select: none;
  -webkit-user-drag: none;
}
/* Brand lockup: the client's name, at the size the client's name deserves,
   directly under the pack shot. The chip holds a canvas of the pack's own
   label art (script logo + BRAAI SALT blocks, painted by ui.js) — client
   change 31 Aug: real logo, pack lettering, no "400g". The canvas keeps its
   intrinsic aspect: size it here by width only, height stays auto. */
.brandlock {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  margin-top: 2px;
  padding: 8px 18px 7px;
  border-radius: 14px;
  /* WHITE, not cream: the designer lockup art ships on an opaque white
     ground, and the pack's own label area behind the lockup is white */
  background: #FFFFFF;
  box-shadow: 0 6px 18px rgba(0,0,0,0.45), inset 0 0 0 2px rgba(27,42,107,0.18);
}
.bl-cv {
  display: block;
  width: 96px;
  height: auto;
}
.kicker {
  color: var(--cream);
  font-size: 10px;
  letter-spacing: 0.34em;
  font-weight: 700;
  opacity: 0.8;
  text-shadow: 0 1px 3px rgba(0,0,0,0.6);
}
/* narrow local scrim: keeps the title readable without washing the sunset */
.title-block {
  width: 100%;
  padding: 8px 14px 10px;
  background: linear-gradient(180deg,
    rgba(9,14,38,0) 0%, rgba(9,14,38,0.46) 26%,
    rgba(9,14,38,0.46) 74%, rgba(9,14,38,0) 100%);
}
.game-title {
  color: #fff;
  font-size: clamp(44px, 12vw, 64px);
  font-weight: 900;
  line-height: 0.9;
  letter-spacing: 0.02em;
  text-shadow: 0 3px 0 var(--navy-deep), 0 6px 20px rgba(0,0,0,0.75);
}
.game-title span {
  display: block;
  color: var(--gold);
  font-size: 0.62em;
  letter-spacing: 0.24em;
  text-shadow: 0 3px 0 #7A4A10, 0 8px 22px rgba(0,0,0,0.55);
}
.tagline {
  color: var(--cream);
  font-size: 15px;
  font-style: italic;
  text-shadow: 0 1px 3px rgba(0,0,0,0.7);
}

/* ---------- buttons ---------- */
.btn-primary {
  min-height: 58px;
  min-width: 220px;
  padding: 14px 44px;
  border: 0;
  border-radius: 16px;
  background: linear-gradient(180deg, #FFC155, var(--gold) 45%, var(--gold-deep));
  color: var(--navy-deep);
  font-family: inherit;
  font-size: 22px;
  font-weight: 900;
  letter-spacing: 0.12em;
  cursor: pointer;
  box-shadow: 0 5px 0 #8A5210, 0 12px 24px rgba(0,0,0,0.4);
  transition: transform 0.08s ease, box-shadow 0.08s ease;
  touch-action: manipulation;
}
.btn-primary:active {
  transform: translateY(4px);
  box-shadow: 0 1px 0 #8A5210, 0 6px 12px rgba(0,0,0,0.4);
}
.btn-play { margin-top: 6px; animation: btn-pulse 1.8s ease-in-out infinite; }
@keyframes btn-pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.045); }
}

/* ---------- how to play ----------
   The new loop in the order it is played: SALT -> FLIP -> BASTE -> SERVE
   (client feedback v3 A). SALT is card one and a full-width hero card — it is
   now the first tap on every meat, so it must be the biggest thing under the
   PLAY button. The other three ride a single row below it and still fit a
   360px phone. */
.howto {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
  width: 100%;
}
.howto-row { display: flex; gap: 6px; justify-content: center; }
.howto-step {
  position: relative;
  flex: 1 1 0;
  max-width: 104px;
  background: rgba(17, 28, 78, 0.65);
  border: 1px solid rgba(242, 169, 59, 0.35);
  border-radius: 14px;
  padding: 8px 4px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}
.howto-step canvas { width: 46px !important; height: 46px !important; }
/* the hero SALT card: wide, gold-ringed, pack shot at full size */
.howto-step.hero {
  flex: 1 1 auto;
  max-width: none;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  padding: 8px 12px 8px 10px;
  text-align: left;
  border: 2px solid rgba(242, 169, 59, 0.9);
  background: linear-gradient(100deg, rgba(17,28,78,0.93), rgba(35,54,130,0.80));
  box-shadow: 0 0 0 3px rgba(242,169,59,0.18), 0 10px 22px rgba(0,0,0,0.45);
}
.howto-step.hero canvas { flex: 0 0 auto; width: 104px !important; height: 64px !important; }
/* FLIP is a second hero card (client feedback v6 C) and it ANIMATES — the
   cook ring sweeping into its green zone. The gold ring is a shade cooler than
   the SALT card's so the two heroes read as a sequence, not a tie. */
.howto-step.hero.flip {
  border-color: rgba(123, 224, 143, 0.85);
  box-shadow: 0 0 0 3px rgba(47, 155, 78, 0.18), 0 10px 22px rgba(0,0,0,0.45);
}
.howto-step.hero.flip .hs-title { color: #7BE08F; }
.howto-step.hero.flip .hs-body b { color: #7BE08F; }
/* BASTE + SERVE compress into one compact row so the extra hero card above
   costs no height: icon left, two short lines right, ~50px tall instead of
   ~93px. This is what keeps the whole start column scroll-free at 360x640. */
.howto-step.mini {
  flex-direction: row;
  align-items: center;
  text-align: left;
  gap: 7px;
  padding: 6px 8px;
  max-width: none;
}
.howto-step.mini canvas { flex: 0 0 auto; width: 40px !important; height: 40px !important; }
.howto-step.mini .hs-n { top: -7px; left: -5px; }
/* step number: a gold coin on the card corner, so it costs no card height */
.hs-n {
  position: absolute;
  top: -8px;
  left: -6px;
  width: 19px;
  height: 19px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--gold);
  color: var(--navy-deep);
  font-size: 11px;
  font-weight: 900;
  box-shadow: 0 2px 6px rgba(0,0,0,0.45);
}
.howto-step.hero .hs-n { position: static; width: 26px; height: 26px; font-size: 15px; }
.howto-txt { color: var(--cream); font-size: 10px; line-height: 1.25; }
.hs-title {
  display: block;
  color: var(--gold);
  font-size: 12px;
  font-weight: 900;
  letter-spacing: 0.14em;
  line-height: 1.1;
}
.hs-body { display: block; color: var(--cream); font-size: 10px; line-height: 1.25; }
.howto-txt b { color: var(--gold); }
.howto-step.hero .hs-title { font-size: 21px; letter-spacing: 0.16em; }
.howto-step.hero .hs-body { font-size: 12px; line-height: 1.3; margin-top: 2px; }
/* one-line fallbacks for panes too shallow for the cards / the scoring key —
   onboarding is never removed, only compressed (the shallow iframe embed used
   to get nothing at all). The look lives here, in one place; the height media
   queries below only flip `display`, because two different bands now use the
   scoring legend (<=900px swaps the panel for it, <=430px swaps the how-to
   cards too). */
.howto-legend, .pts-legend {
  display: none;
  text-align: center;
  width: 100%;
  margin-top: 4px;
  padding: 5px 8px;
  border: 1px solid rgba(242, 169, 59, 0.35);
  border-radius: 10px;
  background: rgba(17, 28, 78, 0.68);
  color: var(--cream);
  font-size: 10px;
  font-weight: 700;
  line-height: 1.5;
}
.pts-legend { border-color: rgba(242, 169, 59, 0.6); }
.howto-legend b, .pts-legend b { color: var(--gold); }
.howto-legend i, .pts-legend i { font-style: normal; opacity: 0.55; }

/* ---------- HOW POINTS WORK (client feedback v3 C) ----------
   One scoring key, two skins: .pts-dark on the start screen over the savanna,
   .pts-card on the cream game-over card. Whole numbers, plain words, and type
   big enough to scan in a couple of seconds. */
.pts {
  width: 100%;
  border-radius: 14px;
  padding: 8px 10px 9px;
  text-align: left;
}
.pts-dark {
  background: rgba(17, 28, 78, 0.84);
  border: 1px solid rgba(242, 169, 59, 0.5);
  box-shadow: 0 10px 22px rgba(0,0,0,0.42);
}
.pts-card {
  background: #fff;
  border: 2px solid rgba(107, 74, 47, 0.18);
  margin: 10px 0 4px;
}
.pts-head {
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.22em;
  text-align: center;
  margin-bottom: 6px;
}
.pts-dark .pts-head { color: var(--gold); }
.pts-card .pts-head { color: var(--brown); }
.pts-list { display: flex; flex-direction: column; gap: 3px; }
.pt {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 3px 7px;
  border-radius: 8px;
}
.pt-what { font-size: 13px; line-height: 1.2; }
.pt-val {
  flex: 0 0 auto;
  font-size: 17px;
  font-weight: 900;
  line-height: 1;
  white-space: nowrap;
}
.pts-dark .pt-what { color: var(--cream); }
.pts-dark .pt-what b { color: #fff; }
.pts-dark .pt-val { color: #fff; }
.pts-card .pt-what { color: #3A342B; }
.pts-card .pt-what b { color: var(--navy); }
.pts-card .pt-val { color: var(--navy); }
.pts-card .pt:nth-child(odd) { background: rgba(107, 74, 47, 0.06); }
/* the salt line is the hero of the whole scoring system */
.pt-hero {
  background: rgba(242, 169, 59, 0.20) !important;
  box-shadow: inset 0 0 0 1px rgba(242, 169, 59, 0.55);
}
.pt-hero .pt-val { color: var(--gold-deep); }
.pts-dark .pt-hero .pt-val { color: var(--gold); }
.pts-dark .pt-try .pt-val { color: #7BE08F; }
.pts-card .pt-try .pt-val { color: var(--green); }

/* ---------- mute button ---------- */
.mute-btn {
  position: absolute;
  top: max(10px, env(safe-area-inset-top));
  right: max(10px, env(safe-area-inset-right));
  width: 48px;
  min-width: 48px;
  min-height: 48px;
  padding: 0;
  border: 1px solid rgba(247, 242, 231, 0.5);
  border-radius: 12px;
  background: rgba(17, 28, 78, 0.6);
  color: var(--cream);
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  z-index: 30;
}
.mute-btn.is-off { opacity: 0.7; }
/* unmistakable "sound off" state: diagonal strike through the note */
.mute-btn.is-off::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 28px;
  height: 3px;
  margin: -1.5px 0 0 -14px;
  background: currentColor;
  border-radius: 2px;
  transform: rotate(-45deg);
  pointer-events: none;
}

/* ---------- cards / forms ---------- */
.card {
  --card-pad: 20px;
  width: 100%;
  max-width: 400px;
  background: var(--cream);
  border-radius: 20px;
  padding: 22px var(--card-pad);
  box-shadow: 0 18px 50px rgba(0,0,0,0.5), inset 0 0 0 3px rgba(107,74,47,0.16);
  animation: rise-in 0.35s ease-out both;
}
@keyframes rise-in {
  from { opacity: 0; transform: translateY(22px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.card h2 {
  color: var(--navy);
  font-size: 26px;
  font-weight: 900;
  margin-bottom: 4px;
}
.card .sub { color: #5B5346; font-size: 13px; margin-bottom: 14px; }
.fld { display: block; margin-bottom: 11px; text-align: left; }
.fld span {
  display: block;
  color: var(--brown);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: 4px;
}
.fld input, .fld select {
  width: 100%;
  min-height: 48px;
  padding: 10px 12px;
  border: 2px solid #D8CDBB;
  border-radius: 12px;
  background: #fff;
  color: #23201A;
  font-family: inherit;
  font-size: 16px;
}
.fld input:focus, .fld select:focus {
  outline: none;
  border-color: var(--navy);
  box-shadow: 0 0 0 3px rgba(27,42,107,0.15);
}
.consent {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  text-align: left;
  color: #5B5346;
  font-size: 12px;
  line-height: 1.4;
  margin: 4px 0 12px;
  cursor: pointer;
}
.consent input { width: 22px; height: 22px; flex: 0 0 auto; accent-color: var(--green); }
.consent b { color: var(--brown); }
.consent.consent-opt { margin-top: -4px; opacity: 0.92; }
.form-err {
  min-height: 18px;
  color: #B3261E;
  font-size: 13px;
  font-weight: 700;
  margin-bottom: 8px;
}
/* Leaderboard name moderation (v4 D): the refused field is marked so the
   player can see WHICH box to fix. The message itself never names the term. */
.fld.bad input {
  border-color: #B3261E;
  background: #FFF6F5;
}
.fld.bad input:focus {
  border-color: #B3261E;
  box-shadow: 0 0 0 3px rgba(179, 38, 30, 0.18);
}
.reg-card .btn-primary { width: 100%; font-size: 18px; }

/* ---------- HUD (rugby scoreboard chips) ----------
   Row 1: score / clock / perfect-in-a-row + the multiplier as its own gold
   badge. Row 2: the SEASONING streak (v3 A) and the Super Shake DOUBLE POINTS
   bar (v3 B) — the two states the client could not see before. */
.hud {
  position: absolute;
  top: max(8px, env(safe-area-inset-top));
  left: 0;
  right: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* the mute button lives in this corner (48px + its 10px inset) — reserve
     its lane so the STREAK pill and its x-multiplier can never slide under
     it at 360px or on short landscape panes */
  padding: 0 calc(max(10px, env(safe-area-inset-right)) + 58px)
           0 max(10px, env(safe-area-inset-left));
  pointer-events: none;
}
.hud-row { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; }
.hud-row2 { justify-content: flex-start; align-items: center; gap: 8px; }
.chip {
  display: flex;
  align-items: center;
  gap: 7px;
  background: linear-gradient(180deg, #14512C, #0D3A1F);
  border: 2px solid var(--gold);
  border-radius: 12px;
  padding: 8px 12px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.14);
}
.chip-lbl {
  color: var(--gold);
  font-size: 9px;
  font-weight: 900;
  letter-spacing: 0.12em;
  line-height: 1.15;
  text-align: left;
}
.chip-val {
  color: #fff;
  font-family: Consolas, 'Courier New', monospace;
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
  text-shadow: 0 0 8px rgba(242,169,59,0.5);
}
/* the multiplier is a badge, not a footnote — it is the score's biggest lever */
.chip-mult {
  background: var(--gold);
  color: var(--navy-deep);
  border-radius: 8px;
  padding: 3px 6px;
  font-family: Consolas, 'Courier New', monospace;
  font-size: 15px;
  font-weight: 700;
  line-height: 1;
}
.chip-streak.boosted { border-color: #FFD37A; }
.chip-streak.boosted .chip-mult { box-shadow: 0 0 12px rgba(242,169,59,0.95); }
.chip-mult.pop { animation: mult-pop 0.45s ease-out; }
@keyframes mult-pop {
  0% { transform: scale(1); }
  45% { transform: scale(1.35); }
  100% { transform: scale(1); }
}
/* seasoning streak: navy chip so it reads as the BRAND counter, not another
   scoreboard number; it lights up gold once the player is on a roll */
.chip-salt {
  background: linear-gradient(180deg, #263D8C, #16255E);
  border-color: rgba(242, 169, 59, 0.75);
  padding: 6px 10px;
}
.chip-salt .chip-lbl { color: #DCE3FF; }
.chip-salt .chip-val { font-size: 19px; }
.chip-salt.hot {
  border-color: #FFD37A;
  box-shadow: 0 0 16px rgba(242,169,59,0.6), 0 4px 12px rgba(0,0,0,0.45);
}
.salt-dots { display: flex; flex-direction: column; gap: 2px; align-items: center; }
.salt-dots i {
  display: block;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 4px rgba(255,255,255,0.8);
}
.salt-dots i:nth-child(2) { transform: translateX(3px); }
.salt-dots i:nth-child(3) { transform: translateX(-2px); }
/* SUPER SHAKE: an unmistakable gold bar that empties itself over the 6s.
   The fill is one CSS width transition — no per-frame JS, no layout thrash. */
.ss-bar {
  position: relative;
  flex: 1 1 auto;
  max-width: 240px;
  height: 32px;
  border-radius: 11px;
  overflow: hidden;
  border: 2px solid #FFD37A;
  background: rgba(17, 28, 78, 0.78);
  box-shadow: 0 0 16px rgba(242,169,59,0.55), 0 4px 12px rgba(0,0,0,0.45);
  animation: ss-glow 0.9s ease-in-out infinite alternate;
}
@keyframes ss-glow {
  to { box-shadow: 0 0 28px rgba(242,169,59,0.95), 0 4px 12px rgba(0,0,0,0.45); }
}
.ss-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 100%;
  background: linear-gradient(180deg, #FFE1A0, var(--gold) 55%, var(--gold-deep));
}
.ss-txt {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 100%;
  color: #FFF8E6;
  font-size: 12px;
  font-weight: 900;
  letter-spacing: 0.12em;
  text-shadow: 0 1px 2px rgba(52,22,0,0.9), 0 0 6px rgba(0,0,0,0.5);
}
.ss-txt span { font-family: Consolas, 'Courier New', monospace; font-size: 14px; }
.chip-time.low { border-color: #E2482C; animation: time-flash 0.5s steps(2) infinite; }
@keyframes time-flash {
  to { box-shadow: 0 0 16px rgba(226,72,44,0.9), 0 4px 12px rgba(0,0,0,0.45); }
}

/* ---------- game over ----------
   The KPI is replays, so PLAY AGAIN must never be below the fold. It is now
   the card's FIRST row rather than its last (client feedback v6 B: the button
   was being hit by accident, because the bottom of the screen is exactly where
   the fingers already were on the grill). The share block takes the pinned
   bottom row, the middle scrolls, and the leaderboard is capped so it scrolls
   inside itself instead of pushing either end off-screen. The over-card uses
   an opacity-only entrance because a residual transform on the card would
   break position:sticky inside it. */
.over-card {
  display: flex;
  flex-direction: column;
  max-height: 100%;
  padding-bottom: 0;
  text-align: center;
  max-width: 420px;
  animation: fade-in 0.35s ease-out both;
}
/* everything above the CTA scrolls INSIDE the card, so the card itself never
   grows past the viewport and PLAY AGAIN is never below the fold — verified
   against a 360x640 phone, which is the floor the client tests on */
.over-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

/* ---------- PLAY AGAIN: moved up, and armed (client feedback v6 B) ----------
   Position is half the fix — this row is pinned at the TOP of the card, clear
   of the grill's tap zone in the lower middle of the screen. The other half is
   time: for ~800ms after the card appears the button is disabled and shows a
   gold bar filling across its foot, so a leftover fast tap lands on something
   that visibly is not ready yet instead of restarting the round. */
.over-top {
  flex: 0 0 auto;
  z-index: 2;
  margin: calc(-1 * var(--card-pad)) calc(-1 * var(--card-pad)) 0;
  padding: 6px var(--card-pad) 8px;
  background: var(--cream);
  border-radius: 18px 18px 0 0;
}
.over-top .btn-primary { width: 100%; }
.btn-again { position: relative; overflow: hidden; }
.btn-again.arming {
  color: rgba(17, 28, 78, 0.42);
  background: linear-gradient(180deg, #E7E0D0, #D3C9B6 45%, #BCB19B);
  box-shadow: 0 5px 0 #948C78, 0 12px 24px rgba(0,0,0,0.3);
  cursor: default;
}
.btn-again.arming::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  height: 5px;
  width: 0;
  background: linear-gradient(90deg, #FFC155, var(--gold-deep));
  animation: arm-fill var(--arm-ms, 800ms) linear forwards;
}
@keyframes arm-fill { from { width: 0; } to { width: 100%; } }
.btn-again.armed { animation: again-ready 0.34s ease-out; }
@keyframes again-ready {
  0% { transform: scale(0.985); }
  55% { transform: scale(1.03); }
  100% { transform: scale(1); }
}
/* fixed height: the hint appears and clears without ever shifting the card */
.again-note {
  min-height: 14px;
  margin-top: 3px;
  color: #8A8271;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 14px;
}

.over-cta {
  position: relative;
  flex: 0 0 auto;
  z-index: 2;
  margin: 0 calc(-1 * var(--card-pad));
  padding: 8px var(--card-pad) max(10px, env(safe-area-inset-bottom));
  background: var(--cream);
  border-radius: 0 0 18px 18px;
  box-shadow: 0 -10px 16px rgba(247, 242, 231, 0.95);
}

/* ---------- share block (client feedback v6 B) ----------
   Four real networks plus the score-card action. Instagram has no web
   share-to-feed URL, so its button runs the native sheet / save-image path —
   it is here because it DOES something, never as decoration. */
.share-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 6px;
  color: var(--brown);
  font-size: 9.5px;
  font-weight: 900;
  letter-spacing: 0.2em;
}
.share-head::before, .share-head::after {
  content: '';
  flex: 1 1 auto;
  height: 1px;
  background: rgba(107, 74, 47, 0.22);
}
.share-row { display: flex; gap: 8px; justify-content: center; }
.sbtn {
  flex: 1 1 0;
  max-width: 64px;
  min-height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 13px;
  color: #fff;
  cursor: pointer;
  box-shadow: 0 3px 0 rgba(0,0,0,0.20), 0 4px 10px rgba(0,0,0,0.18);
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}
.sbtn svg { width: 22px; height: 22px; fill: currentColor; pointer-events: none; }
.sbtn:active { transform: translateY(2px); box-shadow: 0 1px 0 rgba(0,0,0,0.20); }
.sbtn-wa { background: #25D366; }
.sbtn-fb { background: #1877F2; }
.sbtn-x  { background: #0B0B0B; }
.sbtn-ig { background: linear-gradient(135deg, #F9CE34, #EE2A7B 55%, #6228D7); }
.sbtn-dl { background: var(--brown); }
/* the toast is the proof that a share DID something (pop-up blocked, image
   saved, link copied) — nothing in this block may fail silently */
.over-toast {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 6px);
  transform: translateX(-50%) translateY(6px);
  max-width: 94%;
  padding: 8px 12px;
  border-radius: 10px;
  background: rgba(17, 28, 78, 0.95);
  color: var(--cream);
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1.35;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.over-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ---------- CLAIM YOUR SCORE (client feedback v6 A) ----------
   Registration moved off the front door and onto the game-over card, where the
   score is already on the table. It must therefore look like a PRIZE COUNTER,
   not a form: gold-ringed, warm, with the reward spelled out before the first
   field. It can be folded away to a single line, and PLAY AGAIN (pinned above)
   never depends on it. */
.claim {
  position: relative;
  margin: 10px 0 8px;
  padding: 12px 12px 10px;
  border-radius: 16px;
  text-align: left;
  background: linear-gradient(180deg, #FFF8EA, #FFEED0);
  border: 2px solid rgba(242, 169, 59, 0.9);
  box-shadow: 0 8px 20px rgba(217, 126, 34, 0.18), inset 0 0 0 1px rgba(255,255,255,0.6);
}
.claim-badge {
  display: inline-block;
  background: var(--gold);
  color: var(--navy-deep);
  font-size: 9.5px;
  font-weight: 900;
  letter-spacing: 0.18em;
  padding: 4px 10px;
  border-radius: 999px;
}
.claim-title {
  color: var(--navy);
  font-size: 19px;
  font-weight: 900;
  line-height: 1.15;
  margin: 8px 0 3px;
}
.claim-sub { color: #5B5346; font-size: 12px; line-height: 1.4; }
.claim-sub b { color: var(--navy); }
.claim-perks { list-style: none; margin: 8px 0 10px; display: flex; flex-direction: column; gap: 3px; }
.claim-perks li {
  position: relative;
  padding-left: 20px;
  color: #4A4236;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.3;
}
.claim-perks li::before {
  content: '✓';
  position: absolute;
  left: 2px;
  top: 0;
  color: var(--green);
  font-weight: 900;
}
.claim-form .fld { margin-bottom: 9px; }
.claim-form .btn-primary { width: 100%; min-height: 52px; font-size: 17px; }
.claim-later {
  display: block;
  width: 100%;
  margin-top: 8px;
  padding: 6px;
  border: 0;
  background: transparent;
  color: #7A7263;
  font-family: inherit;
  font-size: 12px;
  font-weight: 700;
  text-decoration: underline;
  cursor: pointer;
}
.claim-open {
  display: none;
  width: 100%;
  min-height: 46px;
  border: 0;
  border-radius: 12px;
  background: var(--gold);
  color: var(--navy-deep);
  font-family: inherit;
  font-size: 14px;
  font-weight: 900;
  letter-spacing: 0.1em;
  cursor: pointer;
}
.claim.folded { padding: 8px; }
.claim.folded > *:not(.claim-open) { display: none; }
.claim.folded .claim-open { display: block; }
.claim.done { background: linear-gradient(180deg, #F1FBF1, #DDF3E0); border-color: rgba(31,107,58,0.55); }
.claim.done .claim-badge { background: var(--green); color: #fff; }
.claim.done .claim-title { color: var(--green); }
/* the brand lockup stays in the payoff frame, just smaller than the start
   screen's (the pack photo itself is painted on the plate canvas above it) */
.brandlock-sm {
  margin: 6px 0 2px;
  padding: 5px 12px 5px;
  border-radius: 11px;
  background: #FFFFFF;   /* white ground of the designer lockup art */
  box-shadow: none;
  border: 2px solid rgba(27, 42, 107, 0.14);
}
.brandlock-sm .bl-cv { width: 72px; }
/* Competition Rules / Privacy Notice pop-up (31 Aug): white reading card
   over a navy scrim, scrollable, closable by X, scrim tap or Escape. The
   copy comes from Braai.LEGAL in js/config.js. */
.legal-scrim {
  position: fixed; inset: 0; z-index: 80;
  background: rgba(9, 12, 34, 0.74);
  display: flex; align-items: center; justify-content: center;
  padding: 18px;
}
.legal-scrim.hidden { display: none; }
.legal-card {
  background: #FFFFFF; color: #1A2430;
  width: 100%; max-width: 560px; max-height: 84vh;
  border-radius: 16px; display: flex; flex-direction: column;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5);
}
.legal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px 10px; border-bottom: 2px solid rgba(27, 42, 107, 0.14);
}
.legal-head h2 { margin: 0; font-size: 18px; color: var(--navy); }
.legal-close {
  background: none; border: 0; min-height: 0; padding: 0 6px;
  font-size: 32px; line-height: 1; color: var(--navy); cursor: pointer;
}
.legal-body { overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 4px 18px 18px; }
.legal-body h3 {
  font-size: 12.5px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--navy); margin: 14px 0 4px;
}
.legal-body p { font-size: 13.5px; line-height: 1.55; margin: 0 0 8px; color: #2A3642; }

/* the "competition rules and privacy notice" link rows */
.legal-links { font-size: 11.5px; color: rgba(247, 242, 231, 0.85); margin-top: 6px; text-align: center; }
.legal-links button {
  background: none; border: 0; padding: 0; min-height: 0; font: inherit;
  font-weight: 700; color: var(--gold); text-decoration: underline; cursor: pointer;
}
/* inside the (light) form card the row reads dark with deep-gold links */
.reg-form .legal-links, .claim .legal-links { text-align: left; margin: 2px 0 8px; color: #5A6572; }
.reg-form .legal-links button, .claim .legal-links button { color: var(--gold-deep); }

/* "Playing as <name> - Not you?" - quiet identity line under the best (31 Aug) */
.over-ident { font-size: 11.5px; color: rgba(247,242,231,0.75); margin-top: 2px; }
.over-ident b { color: var(--cream); }
.ident-switch {
  background: none; border: 0; padding: 0; min-height: 0;
  font: inherit; font-weight: 700; color: var(--gold);
  text-decoration: underline; cursor: pointer;
}
.over-actions { display: flex; gap: 8px; margin-top: 8px; }
/* "Challenge a friend" rides beside the blue product CTA (v6 B) and wears
   WhatsApp's green, because that is where the duel message lands. */
.act-challenge { background: #128C4B; color: #fff; }
.act {
  flex: 1 1 0;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px 6px;
  border: 0;
  border-radius: 12px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 900;
  letter-spacing: 0.03em;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}
.act:active { transform: translateY(2px); }
.act-buy { background: var(--navy); color: var(--cream); }
.over-rank {
  color: var(--gold-deep);
  font-size: 30px;
  font-weight: 900;
  letter-spacing: 0.04em;
  text-shadow: 0 2px 0 rgba(107,74,47,0.25);
}
.over-score-lbl {
  color: var(--brown);
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.3em;
  margin-top: 8px;
}
.over-score {
  color: var(--navy);
  font-family: Consolas, 'Courier New', monospace;
  font-size: 58px;
  font-weight: 700;
  line-height: 1;
}
.over-brand { color: var(--brown); font-size: 12.5px; font-style: italic; margin-top: 4px; }
.over-brand b { color: var(--navy); font-style: normal; letter-spacing: 0.02em; }
.over-best { color: var(--green); font-size: 14px; font-weight: 700; margin: 4px 0 12px; }

/* ---------- the prize (v8) ----------
   Shown on the start screen (the reason a paid-social click becomes a
   registration) and on the game-over card (what the score they just saw is
   actually worth). Both read js/config.js -> Braai.COMPETITION and render
   nothing at all when the promo is switched off, so this never hard-codes a
   claim the config cannot back up. */
.prize-flag {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 10px auto 0;
  padding: 6px 14px 6px 6px;
  border: 2px solid var(--gold, #F2A93B);
  border-radius: 999px;
  background: rgba(27,42,107,0.86);
  max-width: 100%;
}
.prize-flag .pz-lbl {
  flex: 0 0 auto;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--gold, #F2A93B);
  color: #241608;
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.14em;
}
.prize-flag .pz-txt {
  color: var(--cream);
  font-size: 14px;
  font-weight: 800;
  line-height: 1.15;
  text-align: left;
}
.prize-sub {
  margin: 6px auto 0;
  max-width: 30rem;
  color: var(--cream);
  font-size: 11.5px;
  line-height: 1.35;
  opacity: 0.86;
  text-shadow: 0 1px 3px rgba(0,0,0,0.65);
}
/* on the cream game-over card the navy pill would shout; invert it there */
.prize-flag-over { background: var(--navy); margin-top: 2px; }
.over-rest .prize-sub {
  color: #5B5346;
  opacity: 1;
  text-shadow: none;
  margin-bottom: 10px;
}
.plate-wrap { line-height: 0; margin-top: 6px; }
/* Keep the plate's aspect when the card is narrower than the 320px canvas:
   the inline height would otherwise stretch the ceramic on a 360px phone.

   aspect-ratio carries the shape now instead of height:auto alone. The canvas
   is inserted while the game-over card is still display:none, and a bare
   height:auto has to derive its result from the element's intrinsic size at the
   moment the card is revealed. Stating the ratio explicitly means the box is
   correct before the first reflow rather than after it, which is the shape of
   the reported "plate was blank in portrait, appeared after I rotated" bug. */
.plate-cv {
  display: inline-block;
  max-width: 100%;
  aspect-ratio: 320 / 196;
  height: auto !important;
}
.over-stats { color: #5B5346; font-size: 12px; font-weight: 700; letter-spacing: 0.04em; margin-top: 2px; }
.lb-title {
  color: var(--cream);
  background: var(--navy);
  border-radius: 8px;
  font-size: 12px;
  font-weight: 900;
  /* the heading now names the view ("TOP BRAAIERS — KWAZULU-NATAL"), so it is
     tracked a little tighter and allowed to wrap rather than overflow */
  letter-spacing: 0.14em;
  line-height: 1.3;
  padding: 7px 8px;
  margin-bottom: 6px;
}
/* ---------- provincial leaderboard switcher (client feedback v4 C) ----------
   The prize is won per province, so the board opens on the player's own
   province and this row switches to any other province or the national view.
   Full-width and 44px tall: a thumb target on a 360px phone, not a 30px
   desktop dropdown. It sits inside .over-body's scroll port, so it can never
   push PLAY AGAIN (pinned in .over-cta) below the fold. */
.lb-switch {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}
.lb-switch-lbl {
  flex: 0 0 auto;
  color: var(--brown);
  font-size: 10px;
  font-weight: 900;
  letter-spacing: 0.16em;
}
.lb-prov-sel {
  flex: 1 1 auto;
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: 8px 10px;
  border: 2px solid #D8CDBB;
  border-radius: 10px;
  background: #fff;
  color: #23201A;
  font-family: inherit;
  font-size: 15px;
  font-weight: 700;
}
.lb-prov-sel:focus {
  outline: none;
  border-color: var(--navy);
  box-shadow: 0 0 0 3px rgba(27, 42, 107, 0.15);
}
/* the board is capped and scrolls inside itself, so ten rows can never push
   the scoring key or the CTA out of reach on a short phone.
   position:relative makes this the offsetParent, which is what lets ui.js
   scroll the player's own highlighted row into the port. */
.lb {
  position: relative;
  list-style: none;
  margin: 0 0 2px;
  max-height: 190px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
.lb-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 8px;
  border-radius: 8px;
  font-size: 14px;
  color: #33291C;
  animation: lb-in 0.4s ease-out both;
}
.lb-row:nth-child(even) { background: rgba(107, 74, 47, 0.08); }
.lb-row.me {
  background: linear-gradient(90deg, rgba(242,169,59,0.85), rgba(242,169,59,0.55));
  color: var(--navy-deep);
  font-weight: 900;
  box-shadow: 0 2px 8px rgba(217,126,34,0.4);
}
/* The player's row when they are outside the top ten: stuck to the bottom of
   the board's own scroll port, so "where am I?" is answered without scrolling
   (v4 C) — the rows above slide under it. */
.lb-row.outside {
  position: sticky;
  bottom: 0;
  z-index: 1;
  border-top: 2px dashed rgba(107,74,47,0.4);
  border-radius: 0 0 8px 8px;
  /* opaque, so the rows scrolling underneath never bleed through it */
  background: linear-gradient(90deg, #F2A93B, #EFBA6E);
  box-shadow: 0 -6px 12px rgba(247, 242, 231, 0.85);
}
/* a province nobody has played yet — never an empty box */
.lb-empty {
  display: block;
  color: #5B5346;
  font-size: 13px;
  font-style: italic;
  text-align: center;
  padding: 12px 8px;
}
@keyframes lb-in {
  from { opacity: 0; transform: translateX(-18px); }
  to { opacity: 1; transform: translateX(0); }
}
.lb-rank { flex: 0 0 22px; font-weight: 900; color: var(--brown); text-align: center; }
.lb-row.me .lb-rank { color: var(--navy-deep); }
.lb-name { flex: 1 1 auto; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lb-prov { flex: 0 0 auto; font-size: 11px; opacity: 0.75; }
.lb-score { flex: 0 0 48px; text-align: right; font-family: Consolas, 'Courier New', monospace; font-weight: 700; }

/* ---------- desktop tweaks ---------- */
@media (min-width: 700px) {
  .start-inner { max-width: 520px; }
  .howto-step { max-width: 128px; }
  .howto-step canvas { width: 54px !important; height: 54px !important; }
  .howto-step.hero canvas { width: 128px !important; height: 79px !important; }
  .howto-step.mini canvas { width: 46px !important; height: 46px !important; }
  .howto-txt { font-size: 11.5px; }
  .hs-body { font-size: 11.5px; }
  .howto, .howto-row { gap: 10px; }
  .chip-val { font-size: 26px; }
  .pt-what { font-size: 14px; }
  .pt-val { font-size: 18px; }
}
/* narrow phones: slim the scoreboard chips so the row still clears the
   mute-button lane reserved above (360px is the primary target) */
@media (max-width: 420px) {
  .chip { padding: 7px 9px; gap: 5px; }
  .chip-lbl { font-size: 8.5px; letter-spacing: 0.08em; }
  .chip-val { font-size: 19px; }
  .chip-mult { font-size: 13px; padding: 3px 5px; }
  .chip-salt { padding: 5px 8px; }
  .chip-salt .chip-val { font-size: 17px; }
  .ss-bar { height: 28px; max-width: 190px; }
  .ss-txt { font-size: 10.5px; letter-spacing: 0.08em; gap: 6px; }
  .ss-txt span { font-size: 13px; }
  .howto-step { padding: 7px 3px; }
  .howto-step canvas { width: 42px !important; height: 42px !important; }
  .howto-step.hero { padding: 7px 9px 7px 8px; gap: 8px; }
  .howto-step.hero canvas { width: 92px !important; height: 57px !important; }
  .howto-step.hero .hs-title { font-size: 19px; }
  .howto-step.hero .hs-body { font-size: 11px; }
  .howto-txt { font-size: 9.5px; }
  .hs-title { font-size: 11px; }
  .hs-body { font-size: 9.5px; }
  .howto-step.mini { padding: 5px 6px; gap: 6px; }
  .howto-step.mini canvas { width: 36px !important; height: 36px !important; }
  .act { font-size: 11px; padding: 6px 4px; }
  .sbtn { min-height: 44px; border-radius: 12px; }
  .sbtn svg { width: 20px; height: 20px; }
  .claim { padding: 10px 10px 9px; }
  .claim-title { font-size: 17px; }
}

/* ---------- landscape phones: the SCOREBOARD gives its height back first
   (client feedback v7 §B) ----------
   Rotate a phone and the whole game has ~390px of height to divide between
   the HUD, the play area and the canvas power-up banner lane. The grill now
   solves the play area (js/grill.js), which puts the topmost salt prompt at
   183px and the banner lane at 95px on an 844x390 phone — so the two HUD rows
   have to finish above 95px, and at full chip size they measured ~100px.
   The slimming below (the 420px-wide phone treatment, plus tighter rows and a
   shorter DOUBLE POINTS bar) lands them at ~66px, which clears the lane by
   28px and the prompts by well over 100px. Height only: every value, colour
   and border is the shipped one, so the scoreboard still reads as the same
   rugby scoreboard, and nothing here touches a portrait phone. */
@media (max-height: 520px) {
  .hud { top: max(6px, env(safe-area-inset-top)); gap: 4px; }
  .chip { padding: 5px 9px; gap: 5px; border-radius: 10px; }
  .chip-lbl { font-size: 8px; letter-spacing: 0.08em; }
  .chip-val { font-size: 18px; }
  .chip-mult { font-size: 12px; padding: 2px 5px; }
  .chip-salt { padding: 4px 8px; }
  .chip-salt .chip-val { font-size: 15px; }
  .ss-bar { height: 24px; max-width: 180px; }
  .ss-txt { font-size: 9.5px; letter-spacing: 0.06em; gap: 5px; }
  .ss-txt span { font-size: 12px; }
}

/* ---------- mid-height viewports: the 701–900px band ----------
   Every tightening step used to stop at max-height:700px, so ordinary phones
   and shallow desktop panes in between (390x740 measured 824px of start-card
   content, 1240x742 measured 902px) scrolled the start screen — the scoring
   key, and on the tightest of them the PLAY button, sat below the fold. This
   step reclaims that band: the full HOW POINTS WORK panel collapses to its
   one-line legend, the column gaps close, and the pack shot and title give
   back scale — the pack, the CEREBOS lockup, PLAY and the scoring legend all
   fit without a scroll. Declared BEFORE the <=700 blocks so those (tighter)
   values still win on the short phones, and after @media (min-width:700px) so
   it also reins in the desktop card sizes on a 742px-tall pane. */
@media (max-height: 900px) {
  .start-inner { gap: 6px; }
  .kicker { display: none; }
  .bottle-img { height: clamp(120px, 19vh, 168px); }
  /* the painted fallback (pack photo missing) has a fixed 150x260 canvas —
     scale it with the photo or the fallback path alone would overflow */
  .bottle-cv { width: 92px !important; height: 160px !important; }
  .title-block { padding: 5px 12px 6px; }
  .game-title { font-size: clamp(32px, 9vw, 46px); }
  .tagline { font-size: 13px; }
  .btn-play { min-height: 52px; padding: 12px 40px; font-size: 20px; }
  .howto { margin-top: 4px; gap: 6px; }
  .howto-row { gap: 6px; }
  .howto-step { padding: 6px 3px; }
  .howto-step canvas { width: 44px !important; height: 44px !important; }
  .howto-step.hero { padding: 6px 10px 6px 8px; }
  .howto-step.hero canvas { width: 96px !important; height: 59px !important; }
  .howto-step.hero .hs-title { font-size: 19px; }
  .howto-step.hero .hs-body { font-size: 11.5px; }
  .howto-txt { font-size: 10px; }
  .hs-title { font-size: 11px; }
  .hs-body { font-size: 10px; }
  .howto-step.mini { padding: 5px 7px; }
  .howto-step.mini canvas { width: 38px !important; height: 38px !important; }
  /* the scoring key is compressed, never removed (v3 C): the five-row panel
     becomes the one-line legend carrying the same numbers */
  .pts-dark { display: none; }
  .pts-legend { display: block; font-size: 11.5px; padding: 6px 10px; }
}

/* Short viewports (the common low-end Android, and any phone showing browser
   chrome): cap the board so the sticky CTA always has room above the fold,
   and pin both tall columns to the TOP so nothing (the pack's cap, the rank
   title) can be cropped off the top edge when the content overflows. */
@media (max-height: 700px) {
  .lb { max-height: 30vh; }
  .scr-over { justify-content: flex-start; }
  .scr-over > .over-card { margin-top: 0; }
  /* both pinned ends of the card give up padding first, so the middle keeps
     as much scroll port as possible on a 640px-tall phone */
  .over-top { padding-bottom: 6px; }
  .over-top .btn-primary { min-height: 52px; font-size: 20px; }
  .again-note { font-size: 10px; }
  .share-head { margin-bottom: 5px; }
  .sbtn { min-height: 44px; }
  .claim { margin: 8px 0 6px; }
  /* Game-over: the scoring key now sits directly under the score (see
     buildGameOver), so the only thing that can still push it out of the
     card's ~456px visible scroll port at 360x640 is the plate above it.
     Capping the ceramic (aspect kept — width only, height stays auto) buys
     the key its full five rows without touching the CTA, which stays pinned. */
  .plate-cv { max-width: 240px; }
  .pts-card { margin: 6px 0 4px; }
}
@media (max-height: 620px) {
  .scr-start { justify-content: flex-start; }
  .scr-start > .start-inner { margin-top: 0; }
}
@media (max-height: 640px) {
  .bottle-cv { width: 104px !important; height: 180px !important; }
  .game-title { font-size: clamp(34px, 9vw, 46px); }
  .howto { margin-top: 4px; }
  .over-rank { font-size: 26px; }
  .over-score { font-size: 48px; }
  .lb { max-height: 26vh; }
}

/* 360x640 is the client's test phone and the floor for this build: the start
   column must fit the pack shot, the salt-first how-to AND the scoring key
   without a scroll. Everything decorative gives up its height first — the
   "PRESENTS" kicker goes, the pack and the title shrink, the key tightens.
   Declared after the blocks above so it wins at equal specificity. */
@media (max-height: 700px) {
  .start-inner { gap: 6px; }
  .kicker { display: none; }
  .bottle-img { height: clamp(112px, 19vh, 168px); }
  .title-block { padding: 5px 12px 6px; }
  .game-title { font-size: clamp(32px, 9vw, 44px); }
  .tagline { font-size: 13px; }
  .btn-play { min-height: 52px; padding: 12px 40px; font-size: 20px; }
  .howto { margin-top: 3px; }
  .howto-step { padding: 5px 3px; }
  .howto-step.hero { padding: 6px 10px 6px 8px; }
  .howto-step.hero canvas { width: 88px !important; height: 54px !important; }
  .howto-step.hero .hs-title { font-size: 18px; }
  .howto-step.hero .hs-body { font-size: 11px; }
  .hs-body { font-size: 9.5px; }
  /* the two hero cards (SALT + the animated FLIP lesson) buy their height back
     here: BASTE and SERVE ride one compact row instead of three tall cards */
  .howto-step.mini { padding: 4px 7px; gap: 6px; }
  .howto-step.mini canvas { width: 34px !important; height: 34px !important; }
  .pts { padding: 6px 9px 7px; }
  .pts-head { font-size: 10px; margin-bottom: 4px; }
  .pt { padding: 2px 6px; }
  .pt-what { font-size: 12.5px; }
  .pt-val { font-size: 16px; }
  .pts-list { gap: 2px; }
}

/* ---------- short viewports (shallow desktop iframe embeds, landscape) ----------
   The hero pack is the one thing the client cares most about, so on a short
   pane the start column must start at the TOP and scroll rather than centre
   and crop the navy Cerebos logo off the screen. */
@media (max-height: 520px) {
  .scr {
    justify-content: flex-start;
    padding: max(10px, env(safe-area-inset-top))
             max(12px, env(safe-area-inset-right))
             max(10px, env(safe-area-inset-bottom))
             max(12px, env(safe-area-inset-left));
  }
  .scr > .start-inner, .scr > .card { margin-top: 0; margin-bottom: 0; }
  .start-inner { gap: 6px; max-width: 520px; }
  .bottle-wrap { animation: none; filter: drop-shadow(0 6px 12px rgba(0,0,0,0.45)); }
  .bottle-cv { width: 76px !important; height: 132px !important; }
  .bottle-img { height: clamp(96px, 21vh, 132px); }
  .brandlock { padding: 5px 12px 4px; gap: 3px; border-radius: 11px; }
  .bl-cv { width: 74px; }
  .kicker { font-size: 9px; letter-spacing: 0.22em; }
  .title-block { padding: 4px 12px 6px; }
  .game-title { font-size: clamp(26px, 8vh, 40px); }
  .tagline { font-size: 12px; }
  .btn-primary { min-height: 48px; min-width: 190px; padding: 10px 30px; font-size: 18px; }
  .btn-play { animation: none; margin-top: 2px; }
  .howto, .howto-row { margin-top: 4px; gap: 5px; }
  .howto-step { padding: 6px 3px; gap: 2px; max-width: 100px; }
  .howto-step canvas { width: 36px !important; height: 36px !important; }
  .howto-step.hero { padding: 5px 8px; gap: 7px; max-width: none; }
  .howto-step.hero canvas { width: 80px !important; height: 49px !important; }
  .howto-step.hero .hs-title { font-size: 16px; }
  .howto-step.hero .hs-body { font-size: 10px; }
  .howto-step.mini { padding: 4px 6px; gap: 5px; }
  .howto-step.mini canvas { width: 30px !important; height: 30px !important; }
  .howto-txt { font-size: 9px; line-height: 1.2; }
  .hs-title { font-size: 10px; }
  .hs-body { font-size: 9px; }
  .pts { padding: 6px 8px 7px; }
  .pts-head { font-size: 9px; letter-spacing: 0.16em; margin-bottom: 3px; }
  .pt { padding: 1px 5px; }
  .pt-what { font-size: 11px; }
  .pt-val { font-size: 14px; }
  .card { --card-pad: 14px; padding: 14px var(--card-pad); }
  .over-cta { padding: 7px var(--card-pad) max(6px, env(safe-area-inset-bottom)); }
  .over-top { padding: 4px var(--card-pad) 5px; }
  .over-top .btn-primary { min-height: 46px; font-size: 17px; }
  .again-note { min-height: 12px; line-height: 12px; font-size: 9.5px; }
  .sbtn { min-height: 40px; max-width: 56px; border-radius: 11px; }
  .sbtn svg { width: 19px; height: 19px; }
  .share-head { font-size: 9px; margin-bottom: 4px; }
  .over-actions { margin-top: 6px; }
  .claim { margin: 8px 0 6px; padding: 10px; }
  .claim-title { font-size: 16px; }
  .claim-perks { margin: 6px 0 8px; }
  .over-rank { font-size: 24px; }
  .over-score { font-size: 44px; }
  .lb { max-height: 24vh; }
}
/* wide-and-short = the landscape phone and the shallow desktop embed: content
   now starts at the top, so the top-right corner belongs to the card — park
   the mute control bottom-right, clear of the centred card on any pane this
   wide (and, in play, clear of the grill: the cooking band ends at 346px on an
   844x390 phone and its right-most meat at x=692, while this button sits from
   x=786). */
@media (max-height: 520px) and (min-width: 560px) {
  .mute-btn { top: auto; bottom: max(10px, env(safe-area-inset-bottom)); }
  /* The card is pinned to the top of a 390px-tall pane, so PLAY AGAIN (the
     card's FIRST row, v6 §B) sits at y 14-60 — measured, reachable with no
     scroll and nowhere near the lower-middle grill zone the player's fingers
     just left; its 800ms arming delay is untouched. What a landscape pane
     changes is the BODY: 370px of card minus the two pinned rows leaves an
     80->251px scroll port (171px) and the card's content measures 1315px.
     CORRECTION — this comment used to claim the wider card meant "most of the
     card needs no scroll at all". That was never true by measurement: at 171px
     of port against 1315px of content the score row sat 22px BELOW the fold
     (28px at 926x428, 52px at 740x360). Width alone does not fix a 1315px
     column; the two-column block further down does. All this rule still buys
     is 140px of extra width for the plate, the board rows and the claim form.
     .over-body scrolls INSIDE the card, so neither pinned row is ever pushed
     off — that part was, and stays, true. */
  .over-card { max-width: 560px; }
  .over-body { overscroll-behavior: contain; }
}
/* ---------- landscape phones: the game-over card goes TWO-COLUMN ----------
   MEASURED DEFECT (single-column card, before this block): .over-body is an
   inner scroller and the score row lands below its fold on every landscape
   phone — 844x390 gave a 171px port (scrollHeight 1315) with .over-score at
   272.9->316.9, i.e. 22px under; 926x428 gave 209px and 28px under; 740x360
   gave 141px and 52px under. The player had to make a gesture to see the one
   number they came for, which is exactly the number the paid-media click was
   bought for. (The comment that used to sit here claimed a wider card meant
   "most of the card needs no scroll at all" — that was never true by
   measurement: the content is ~1315px against a ~171px port.)

   Fix: split the port into two columns. LEFT (.over-head) is the headline —
   rank title, FINAL SCORE, the number, the round stats, then the plate — and
   its type block measures ~85px (~79px below 375px of height), so
   rank/label/score/stats land whole inside the port at all three sizes with
   room to spare: 85 of 171 at 844x390, 85 of 209 at 926x428, 79 of 141 at
   740x360. Only the plate under them ever has to give height back.
   RIGHT (.over-rest) carries the personal best pinned to its own top, then the
   scoring key, the CLAIM YOUR SCORE panel and the leaderboard, and takes the
   scrolling.
   Untouched on purpose: .over-top (PLAY AGAIN, y 14->60 at 844x390) and
   .over-cta (the share row) stay pinned as the card's first and last flex
   rows, and the 800ms arming delay is a js/ui.js timer this never touches. */
@media (max-height: 430px) and (min-width: 700px) {
  /* keep padding == --card-pad: .over-top / .over-cta bleed to the card edge
     with calc(-1 * var(--card-pad)) margins, so the two must stay in step */
  .over-card { --card-pad: 12px; padding: 12px var(--card-pad); }
  .over-body {
    display: flex;
    align-items: stretch;
    /* column-gap ONLY: a shorthand `gap` would also apply between rows */
    column-gap: 12px;
    overflow: hidden;              /* the columns scroll; the body itself does not */
  }
  .over-head, .over-rest {
    min-width: 0;
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
  /* a flex column with a bounded height squashes its items to fit unless they
     are told not to shrink — without this the score would compress instead of
     the column scrolling */
  .over-head > *, .over-rest > * { flex: 0 0 auto; }
  .over-head {
    flex: 0 0 42%;
    padding-right: 10px;
    border-right: 1px solid rgba(107, 74, 47, 0.18);
  }
  .over-rest { flex: 1 1 0; }
  /* score BEFORE plate: in portrait the plate is the reward you scroll past to
     reach the number; in a 171px landscape port it is the thing that buried
     it. Reordered here only — the DOM order (and portrait) is unchanged. */
  .over-head > .over-rank      { order: 1; font-size: 19px; line-height: 1.1; }
  .over-head > .over-score-lbl { order: 2; margin-top: 2px; font-size: 9px; letter-spacing: 0.22em; line-height: 1.2; }
  .over-head > .over-score     { order: 3; font-size: 38px; }
  /* the stat line is the ONE row here that can wrap: at its longest
     ("12 Perfect · 12 Geseason · 12 Burnt", 34 chars) 10px + 0.04em measures
     ~200px against 214px of column, i.e. a font fallback away from a second
     line that would eat 12px of the plate's budget. 9.5px with the tracking
     dropped measures ~174px and cannot wrap at any of the three sizes. */
  .over-head > .over-stats     { order: 4; margin-top: 2px; font-size: 9.5px; letter-spacing: 0.02em; line-height: 1.2; }
  .over-head > .plate-wrap     { order: 5; margin-top: 4px; }
  /* the plate takes what the headline leaves of the port: 171 - 85 - 4 = 82px
     at 844x390, and 124px of width paints 76px of ceramic (the canvas is
     320x196 and keeps its aspect) */
  .plate-cv { max-width: 124px; }
  /* the personal best is part of the headline the client asks for, so it rides
     at the top of the scrolling column and stays pinned there while the key,
     the claim panel and the board move underneath it */
  .over-rest > .over-best {
    order: -1;
    position: sticky;
    top: 0;
    z-index: 1;
    margin: 0 0 6px;
    padding-bottom: 3px;
    background: var(--cream);
    font-size: 12.5px;
    line-height: 1.2;
  }
}
/* a 420-430px-tall pane (926x428 is the common landscape iPhone) has ~209px of
   port, so the plate can stay near its portrait size there and still clear the
   headline: 85 + 4 + 108 = 197 <= 209. */
@media (min-height: 420px) and (max-height: 430px) and (min-width: 700px) {
  .plate-cv { max-width: 176px; }
}
/* The port is (viewport height - 219px): 70px of pinned PLAY AGAIN row, 117px
   of pinned share row, 20px of screen padding and 12px of card foot. The 124px
   plate above therefore needs 85 + 4 + 76 = 165px, i.e. a pane at least 385px
   tall. Between 376 and 384 — a 390px phone with a sliver of browser chrome —
   it would overflow by up to 9px and shave the bottom of the ceramic, so the
   plate steps down one size there instead: 85 + 4 + 64 = 153 <= 157. */
@media (min-height: 376px) and (max-height: 384px) and (min-width: 700px) {
  .plate-cv { max-width: 104px; }
}
/* 740x360 is the floor the client tests on and leaves only ~141px of port.
   The headline gives back 6px of type and the plate the rest, so the left
   column still lands WHOLE (78.9 + 4 + 53.9 = 137px) — a cropped plate reads
   as a bug, a small complete one reads as a design. */
@media (max-height: 375px) and (min-width: 700px) {
  .over-head > .over-rank { font-size: 17px; }
  .over-head > .over-score { font-size: 34px; }
  .plate-cv { max-width: 88px; }
}
/* Very shallow panes (the ~510x413 iframe embed the brief names as the
   distribution channel): the how-to CARDS don't fit, but onboarding is not
   optional — swap them for a single-line legend that still names all four
   verbs, so a player never lands on a game with no instructions. */
@media (max-height: 430px) {
  .bottle-cv { width: 62px !important; height: 108px !important; }
  .bottle-img { height: clamp(78px, 17vh, 106px); }
  .game-title { font-size: clamp(22px, 7.5vh, 32px); }
  .kicker { letter-spacing: 0.16em; }
  .bl-cv { width: 64px; }
  .howto, .pts-dark { display: none; }
  /* the box styling lives in the base sheet — only the display flips here */
  .howto-legend, .pts-legend { display: block; }
  .pts-legend { font-size: 10px; padding: 5px 8px; }
}
