/* =============================================================================
   FunLotto theme — warm cream ground, deep plum ink, sunset coral CTAs,
   candy "jewel" pills and glossy number balls. Matches the design mockups
   in FunLotto_Working/Design/Mockups.
============================================================================= */

:root {
  /* Type system — themes may swap the DISPLAY face (royal/emerald use
     Outfit); the body face stays put for readability. Canvas-BAKED
     artefacts (foil card faces, scratchie panels, the gashapon loss
     note) deliberately keep their authored fonts — they read as
     printed objects, not UI chrome (decision 2026-07-28). */
  --font-display: "Fredoka", "Nunito", sans-serif;
  --font-body: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  /* Outfit sets a touch narrower than Nunito — themes that swap to it
     open the tracking slightly so body text keeps its air. Explicit
     letter-spacing (uppercase labels, codes) is unaffected. */
  --body-tracking: normal;

  --cream:   #FCEADD;   /* page ground */
  --shell:   #FFF9F3;   /* card surface */
  --shell-2: #FFF3E7;   /* inputs / nested surfaces */
  --ink:     #2E1A35;   /* primary text (deep plum) */
  --muted:   #9B8391;   /* secondary text (warm mauve) */
  --faint:   #C9B6BF;   /* placeholders, empty states */
  --line:    #F3DCCB;   /* hairline borders on cream */
  --coral:   #F2536B;   /* primary action */
  --coral-deep: #D8435C;
  --teal:    #17A98B;   /* success / wins */
  --gold:    #F4B23F;   /* credits, warm highlights */
  --win:     #FF8A1E;   /* THE win/brand orange (2026-07-21, from the app
                           icon): every win marker + the hero Reveal button */
  --amber-text: #B8763F;/* readable amber for hints on cream */
  --blue:    #3E97D6;
  --purple:  #7D57B8;
  --danger:  #E24B4A;

  --sunset: linear-gradient(180deg,
    #FFE6A8 0%, #FEC896 30%, #FBAF9F 55%, #F29BB4 78%, #E393B6 100%);
  --beach: linear-gradient(180deg,
    #4FB4E6 0%, #3FBCCB 38%, #2FBDA9 55%, #93DCBE 70%, #E7E9C3 84%, #F9E4AE 100%);
}

/* =========================================================================
   Ground themes — every colour a theme owns lives in ITS block:
     --ground      the gradient itself
     --ground-top  its literal first stop (iOS samples the body's flat
                   colour for the installed-app status bar)
     --hud-*       reveal-HUD text (the HUD sits on the gradient's lower
                   third — beach ends light, sunset ended dark)
   Switching = setGround('name') in app.js: stamps body[data-ground],
   persists to localStorage, refreshes the theme-color meta. The reveal
   reflections DERIVE their stops from the computed --ground, so new
   themes need nothing outside this file (add a block + list the name in
   app.js GROUNDS). The bare `body` block is the default for first paint
   — keep it matching the theme-color meta in index.html.
========================================================================= */
body, body[data-ground="beach"] {
  --ground: var(--beach);
  --ground-top: #4FB4E6;
  --hud-text: #3D3527;
  --hud-text-sub: rgba(61, 53, 39, 0.66);
  --hud-shadow: 0 1px 3px rgba(255, 255, 255, 0.5);
}
body[data-ground="sunset"] {
  --ground: var(--sunset);
  --ground-top: #FFE6A8;
  --hud-text: #fff;
  --hud-text-sub: rgba(255, 255, 255, 0.85);
  --hud-shadow: 0 1px 4px rgba(120, 40, 60, 0.3);
}

/* =========================================================================
   Reset & base
========================================================================= */
* { box-sizing: border-box; margin: 0; padding: 0; }
/* App shell: the document NEVER scrolls — the body is locked to the
   viewport (dvh tracks mobile URL-bar changes) and each page scrolls its
   own content internally. This is what separates app-feel from
   browser-feel. */
html, body { height: 100%; overflow: hidden; }
/* The CANVAS (what iOS paints in regions outside its layout viewport —
   notably the strip below fixed elements in black-translucent
   standalone mode, where the tab bar can't reach) takes the html
   element's background. Cream to match the tab bar, so any such strip
   reads as part of it. The body paints the gradient over the whole
   real viewport, so this never shows elsewhere. */
html { background: var(--shell); }
body {
  font-family: var(--font-body);
  letter-spacing: var(--body-tracking);
  /* The active ground (--ground, themes defined with the palette). The
     body is viewport-fixed and pages scroll inside it, so the gradient
     never moves with the content. */
  /* The flat colour is invisible in normal rendering (the gradient image
     always covers the body box) but iOS SAMPLES it to paint the opaque
     status bar of the installed app (verified on device: the bar tracked
     this value across changes). --ground-top is the active gradient's
     literal first stop — keep it and the theme-color meta in index.html
     in sync with --ground. */
  background-color: var(--ground-top);
  background-image: var(--ground);
  color: var(--ink);
  position: fixed;      /* the strongest iOS document-bounce prevention */
  inset: 0;
  /* NO explicit viewport-unit height here: in iOS standalone
     (black-translucent) 100vh/100dvh report the FULL screen while fixed
     elements pin to a shorter internal viewport — an explicit height
     anchored at top:0 overrides bottom:0 (over-constrained) and the
     body's gradient overflows below where the tab bar is allowed to
     sit. inset:0 alone sizes the body to exactly the box fixed
     elements use, and the strip below shows the html canvas (cream,
     matching the tab bar) instead. */
}
:root {
  /* context row (40) + title row (48) */
  --appbar-h: calc(50px + env(safe-area-inset-top, 0px));
  --tabbar-h: calc(62px + env(safe-area-inset-bottom, 0px));
}
body.chromeless #appbar, body.chromeless #tabbar { display: none; }
body.chromeless .page { padding-top: 24px; padding-bottom: 40px; }

/* =========================================================================
   Page system — only one .page.active is visible at a time
========================================================================= */
.page {
  display: none;
  flex-direction: column;
  align-items: center;
  padding: calc(var(--appbar-h) + 16px) 16px calc(var(--tabbar-h) + 20px);
  gap: 20px;
  height: 100%;
  overflow-y: auto;
  /* NO -webkit-overflow-scrolling:touch — it's legacy (iOS has momentum
     scrolling by default since 13) and its special scroll layer is the
     classic cause of "container froze after content changed" bugs, which
     bit the scan page's confirm card on device (2026-07-16) */
  overscroll-behavior: contain;   /* no rubber-banding the whole document */
}
.page.active { display: flex; }

/* In-page headers are retired — the fixed app bar is the header now */
.page-header { display: none; }
.page-header .back-btn {
  position: absolute;
  left: 0;
}
.back-btn {
  background: none;
  border: none;
  color: var(--coral);
  font-size: 1rem;
  font-weight: 800;
  cursor: pointer;
  padding: 4px 0;
  flex-shrink: 0;
}
.page-title {
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 600;
  color: var(--ink);
}

/* App wordmark on home screen — "Fun" coral, "Lotto" teal */
h1 {
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: 600;
  text-align: center;
  margin-top: 12px;
  color: var(--ink);
}
h1 .fun   { color: var(--coral); }
h1 .lotto { color: var(--teal); }

/* =========================================================================
   Cards
========================================================================= */
.card {
  width: 100%;
  max-width: 480px;
  background: var(--shell);
  border: none;
  border-radius: var(--r-card, 22px);
  padding: 22px 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  box-shadow: 0 6px 18px rgba(46, 26, 53, 0.08);
}
.card h2 {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--ink);
}

/* =========================================================================
   Home — large nav cards
========================================================================= */
.nav-card {
  width: 100%;
  max-width: 480px;
  border: none;
  border-radius: var(--r-card, 24px);
  padding: 26px 24px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  cursor: pointer;
  /* candy gloss: white sheen over each card's tint, glossy bottom bevel */
  background-image: linear-gradient(170deg, rgba(255,255,255,0.75), rgba(255,255,255,0.15) 55%);
  background-color: var(--shell);
  box-shadow: inset 0 -4px 0 rgba(46, 26, 53, 0.06),
              0 8px 20px rgba(46, 26, 53, 0.10);
  transition: transform 0.15s, box-shadow 0.15s;
}
.nav-card:hover  { transform: translateY(-2px); box-shadow: inset 0 -4px 0 rgba(46,26,53,0.06), 0 12px 26px rgba(46, 26, 53, 0.14); }
.nav-card:active { transform: scale(0.98); }
.nav-card-icon {
  width: 54px; height: 54px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  color: #fff;
  /* glossy jewel: top sheen + bottom bevel */
  background-image: linear-gradient(rgba(255,255,255,0.35), rgba(255,255,255,0) 55%);
  box-shadow: inset 0 -3px 0 rgba(0,0,0,0.18), 0 4px 10px rgba(46,26,53,0.15);
}
/* Per-card candy colours (explicit classes — nth-of-type broke when the
   continue card was added in front) */
.nav-card--continue { background-color: #FFF3DC; }
.nav-card--scan     { background-color: #FFE9ED; }
.nav-card--tickets  { background-color: #E4F6EF; }
.nav-card--continue .nav-card-icon { background-color: var(--gold); }
.nav-card--scan     .nav-card-icon { background-color: var(--coral); }
.nav-card--tickets  .nav-card-icon { background-color: var(--teal); }
.nav-card-icon svg { width: 1.7rem; height: 1.7rem; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.icon { display: inline-flex; align-items: center; gap: 6px; }
.icon svg { width: 1em; height: 1em; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; vertical-align: -0.1em; flex-shrink: 0; }
.nav-card-title { font-family: var(--font-display); font-size: 1.15rem; font-weight: 600; color: var(--ink); margin-top: 6px; }
.nav-card-desc  { font-size: 0.88rem; color: var(--muted); line-height: 1.45; font-weight: 600; }

/* =========================================================================
   Capture area (scan page)
========================================================================= */
#capture-area {
  width: 100%;
  max-width: 480px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
#preview-container {
  width: 100%;
  aspect-ratio: 3/2;
  background: var(--shell);
  border: 2.5px dashed #EBCFB6;
  border-radius: var(--r-card, 22px);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
#preview-container.has-image { border-style: solid; border-color: var(--coral); }
/* Reviewing (confirm card / ticket details showing): the photo's job is
   done — collapse it to a thumbnail strip so the review content and its
   buttons fit ON SCREEN without depending on scrolling (which iOS was
   caught refusing on this page, 2026-07-16) */
#page-scan.reviewing #preview-container {
  aspect-ratio: auto;
  height: 96px;
  flex-shrink: 0;
}
#page-scan.reviewing .preview-thumb { object-fit: cover; }
#page-scan.reviewing #photo-btn-row { display: none; }
#photo-btn-row { display: flex; gap: 10px; width: 100%; }
#photo-btn-row .btn { flex: 1; min-width: 0; }
/* Pulsing coral glow while the AI reads the ticket */
#preview-container.scanning {
  border-style: solid;
  animation: scan-glow 0.9s ease-in-out infinite alternate;
}
@keyframes scan-glow {
  from { border-color: #F7A9B5; box-shadow: 0 0 8px  rgba(242, 83, 107, 0.25); }
  to   { border-color: var(--coral); box-shadow: 0 0 28px rgba(242, 83, 107, 0.55), 0 0 8px rgba(242, 83, 107, 0.7); }
}

/* up to 3 photos of one ticket, side by side; tap a thumb to remove it */
#preview-imgs {
  display: none;   /* flex when photos exist (renderPhotoPreviews) */
  width: 100%;
  height: 100%;
  gap: 6px;
  padding: 6px;
  align-items: stretch;
}
.preview-thumb {
  flex: 1 1 0;
  min-width: 0;
  height: 100%;
  object-fit: contain;
  border-radius: var(--r-inner, 12px);
  cursor: pointer;
}
#placeholder-text { color: var(--faint); font-size: 0.92rem; font-weight: 700; text-align: center; padding: 16px; }

/* =========================================================================
   Buttons — candy pills with a pressed 3D "lip"
========================================================================= */
.btn {
  width: 100%;
  padding: 15px;
  border: none;
  border-radius: 999px;
  font-family: var(--font-body);
  font-size: 1.02rem;
  font-weight: 800;
  cursor: pointer;
  transition: filter 0.15s, transform 0.1s;
  box-shadow: inset 0 -4px 0 rgba(0, 0, 0, 0.16), 0 6px 14px rgba(46, 26, 53, 0.14);
}
.btn:active { transform: scale(0.97); box-shadow: inset 0 -2px 0 rgba(0,0,0,0.16), 0 3px 8px rgba(46,26,53,0.12); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-primary   { background: var(--coral); color: #fff; }
.btn-primary:hover:not(:disabled)   { filter: brightness(1.07); }
.btn-secondary { background: var(--shell); color: var(--ink); box-shadow: inset 0 -4px 0 rgba(46,26,53,0.08), 0 4px 10px rgba(46,26,53,0.10); }
.btn-secondary:hover:not(:disabled) { filter: brightness(0.98); }
.btn-green  { background: var(--teal); color: #fff; }
.btn-green:hover:not(:disabled)  { filter: brightness(1.07); }
.btn-orange { background: var(--gold); color: #5A3A00; }
.btn-orange:hover:not(:disabled) { filter: brightness(1.05); }

/* Small icon-only button */
.btn-icon {
  background: none;
  border: none;
  cursor: pointer;
  padding: 6px;
  border-radius: 10px;
  font-size: 1rem;
  color: var(--muted);
  transition: background 0.15s, color 0.15s;
  line-height: 1;
}
.btn-icon:hover { background: var(--shell-2); color: var(--ink); }
.btn-icon.danger:hover { color: var(--danger); }

/* Status line */
#status { text-align: center; font-size: 0.88rem; font-weight: 700; min-height: 20px; color: var(--amber-text); }
#status.error { color: var(--danger); }

/* =========================================================================
   Ticket fields — label / value rows
========================================================================= */
.field {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding: 8px 0;
  border-bottom: 1.5px solid var(--line);
  gap: 12px;
}
.field:last-child { border-bottom: none; }
.field-label { font-size: 0.72rem; font-weight: 800; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; flex-shrink: 0; padding-top: 3px; }
.field-value { font-size: 0.95rem; font-weight: 700; color: var(--ink); text-align: right; word-break: break-word; }

/* Stacked variant — label above full-width value, used for number rows so all
   chips (e.g. 7 for Set for Life) have the entire card width to lay out in. */
.field-stacked { flex-direction: column; align-items: stretch; gap: 6px; }
.field-stacked .field-value { text-align: left; }
.field-stacked .numbers-row { justify-content: flex-start; }

/* =========================================================================
   Number chips — glossy jewel balls
========================================================================= */
.numbers-row { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; align-items: flex-end; }
/* bonus chips carry a tiny pool tag ("PB") above the ball */
.chip-stack { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.chip-tag {
  font-size: 0.55rem; font-weight: 800; letter-spacing: 0.05em;
  line-height: 1; color: var(--muted);
}
.number-chip {
  background-color: var(--coral);
  /* glossy highlight — background-image survives per-game background-color overrides */
  background-image: radial-gradient(circle at 32% 28%, rgba(255,255,255,0.55), rgba(255,255,255,0) 45%);
  box-shadow: inset 0 -3px 4px rgba(0, 0, 0, 0.22), 0 3px 6px rgba(46, 26, 53, 0.18);
  color: var(--game-ink, #fff); border-radius: 50%;
  width: 36px; height: 36px;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 0.85rem;
}
.bonus-chip { background-color: var(--game-accent-alt, var(--gold)); }
.match-chip { background-color: var(--teal); }

/* =========================================================================
   Form rows
========================================================================= */
.form-row { display: flex; flex-direction: column; gap: 6px; }
.form-row label { font-size: 0.72rem; font-weight: 800; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; }
.form-row input, .form-row input[type="date"] {
  background: var(--shell-2); border: 1.5px solid var(--line); border-radius: 14px;
  color: var(--ink); padding: 11px 14px; font-size: 0.95rem; font-weight: 700; width: 100%;
  font-family: var(--font-body);
}
.form-row input:focus { outline: none; border-color: var(--coral); }
.form-hint { font-size: 0.78rem; color: var(--faint); font-weight: 600; }

/* =========================================================================
   Number picker grid (draw entry)
========================================================================= */
.picker-section { display: flex; flex-direction: column; gap: 8px; }
.picker-label { font-size: 0.72rem; font-weight: 800; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; }
.picker-sublabel { font-size: 0.78rem; color: var(--coral); font-weight: 800; }
.picker-grid { display: flex; flex-wrap: wrap; gap: 6px; }
.pick-btn {
  width: 40px; height: 40px; border-radius: 50%;
  border: 2px solid var(--line); background: var(--shell); color: var(--muted);
  font-size: 0.8rem; font-weight: 800; cursor: pointer; padding: 0;
  font-family: var(--font-body);
  display: flex; align-items: center; justify-content: center;
  transition: background 0.12s, color 0.12s, border-color 0.12s, transform 0.1s;
}
.pick-btn:active { transform: scale(0.9); }
.pick-btn.selected-main  { background: var(--coral); border-color: var(--coral); color: #fff; }
.pick-btn.selected-bonus { background: var(--gold); border-color: var(--gold); color: #5A3A00; }
.pick-btn.disabled-pick  { opacity: 0.35; cursor: not-allowed; }
.picker-counter { font-size: 0.82rem; font-weight: 700; color: var(--amber-text); text-align: right; }
.picker-counter.complete { color: var(--teal); font-weight: 800; }

/* =========================================================================
   Confidence confirmation card
========================================================================= */
#confirm-card { box-shadow: 0 6px 18px rgba(46,26,53,0.08), 0 0 0 2px var(--gold); }
#confirm-card h2 { color: var(--amber-text); }
.confirm-msg { font-size: 0.88rem; color: var(--ink); font-weight: 600; line-height: 1.5; }
.game-select {
  background: var(--shell-2); border: 1.5px solid var(--line); border-radius: 14px;
  color: var(--ink); padding: 11px 14px; font-size: 0.95rem; font-weight: 700; width: 100%;
  appearance: none; font-family: var(--font-body);
}
.game-select:focus { outline: none; border-color: var(--coral); }

/* =========================================================================
   Check result blocks
========================================================================= */
.game-result {
  border: 1.5px solid var(--line); border-radius: var(--r-card-sm, 16px); padding: 12px;
  display: flex; flex-direction: column; gap: 8px;
  background: var(--shell);
}
.game-result.winner { border-color: var(--teal); background: #E9F7F1; }
.game-result-label { font-size: 0.75rem; font-weight: 800; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; }
.division-badge {
  display: inline-block; background: var(--teal); color: #fff;
  border-radius: 999px; padding: 4px 12px; font-size: 0.8rem; font-weight: 800;
  box-shadow: inset 0 -2px 0 rgba(0,0,0,0.14);
}
.no-win-badge {
  display: inline-block; background: var(--shell-2); color: var(--muted);
  border-radius: 999px; padding: 4px 12px; font-size: 0.8rem; font-weight: 700;
}

/* =========================================================================
   Spinner
========================================================================= */
.spinner {
  display: inline-block; width: 16px; height: 16px;
  border: 2px solid rgba(255,255,255,0.4); border-top-color: #fff;
  border-radius: 50%; animation: spin 0.7s linear infinite;
  vertical-align: middle; margin-right: 8px;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Page fade-in */
.page.active { animation: page-enter 0.3s ease both; }
@keyframes page-enter {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* Glow pulse on division win badge */
.division-badge { animation: win-glow 2.2s ease-in-out infinite; }
@keyframes win-glow {
  0%, 100% { box-shadow: inset 0 -2px 0 rgba(0,0,0,0.14), 0 0 6px rgba(23,169,139,0.35); }
  50%      { box-shadow: inset 0 -2px 0 rgba(0,0,0,0.14), 0 0 18px rgba(23,169,139,0.7); }
}

/* Pop-in for number chips */
.number-chip { animation: chip-pop 0.25s cubic-bezier(0.34,1.56,0.64,1) both; }
@keyframes chip-pop {
  from { transform: scale(0.5); opacity: 0; }
  to   { transform: scale(1);   opacity: 1; }
}

/* =========================================================================
   Per-game colour palettes
   The accent/alt colours were chosen per game (user decisions) and are kept;
   only the tint backgrounds are re-balanced for the light cream ground.
   Vars are declared for every scope that uses them: ticket cards, the scan
   results card, and the results page.
========================================================================= */
.ticket-card,
#scan-results,
#page-results                        { --game-accent: var(--coral); --game-bg: rgba(242, 83, 107, 0.08); }

/* Per-game hues are deliberately offset from the official game branding —
   close enough to read as "the teal one" / "the blue one", far enough that
   nothing colour-matches operator trade dress. Tuned for the cream ground. */
.ticket-card.game-set-for-life,
#scan-results.game-set-for-life,
.game-badge.game-set-for-life,
.ctx-mini.game-set-for-life,
#ctx-ticket-chip.game-set-for-life,
#page-results.game-set-for-life      { --game-accent: #17A98B; --game-bg: rgba(23, 169, 139, 0.10); --game-accent-alt: #F5A03C; }

.ticket-card.game-powerball,
#scan-results.game-powerball,
.game-badge.game-powerball,
.ctx-mini.game-powerball,
#ctx-ticket-chip.game-powerball,
#page-results.game-powerball         { --game-accent: #3D5FD0; --game-bg: rgba(61, 95, 208, 0.09); --game-accent-alt: #8A93C8; }

.ticket-card.game-oz-lotto,
#scan-results.game-oz-lotto,
.game-badge.game-oz-lotto,
.ctx-mini.game-oz-lotto,
#ctx-ticket-chip.game-oz-lotto,
#page-results.game-oz-lotto          { --game-accent: #3FA75B; --game-bg: rgba(63, 167, 91, 0.10); --game-accent-alt: #E8B04B; }

.ticket-card.game-saturday-lotto,
#scan-results.game-saturday-lotto,
.game-badge.game-saturday-lotto,
.ctx-mini.game-saturday-lotto,
#ctx-ticket-chip.game-saturday-lotto,
#page-results.game-saturday-lotto    { --game-accent: #F0B429; --game-bg: rgba(240, 180, 41, 0.13); --game-ink: #5A3A00; }

.ticket-card.game-weekday-windfall,
#scan-results.game-weekday-windfall,
.game-badge.game-weekday-windfall,
.ctx-mini.game-weekday-windfall,
#ctx-ticket-chip.game-weekday-windfall,
#page-results.game-weekday-windfall  { --game-accent: #A263C9; --game-bg: rgba(162, 99, 201, 0.10); }

/* Single-letter game-type badge — circle, coloured per game above
   (S/P/O/L/W). Currently used in the reveal-select/draws context chip;
   .game-badge picks up whichever game-xxx class is applied to it. */
.game-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px; height: 20px;
  flex: none;
  border-radius: 50%;
  background: var(--game-accent, var(--coral));
  color: var(--game-ink, #fff);
  font-family: var(--font-display);
  font-size: 0.68rem;
  font-weight: 800;
  line-height: 1;
  vertical-align: middle;
}
/* Inline next to a game title (field rows) — the context chip and
   ticket-card header space via flex gap instead, so margin lives here */
.field-value .game-badge { margin-right: 6px; }
/* Leading badge on ticket-card headers — same size circle as the
   chevron button on the other end of the row */
.ticket-card-header > .game-badge { width: 34px; height: 34px; font-size: 1rem; }

/* Ticket cards — child elements pick up --game-accent from the card */
.ticket-card .number-chip        { background-color: var(--game-accent); }
/* the game-accent override above outranks .bonus-chip (2 classes vs 1) —
   without this the Powerball chip was indistinguishable (Jamie 2026-08-02) */
.ticket-card .bonus-chip         { background-color: var(--game-accent-alt, var(--gold)); }
.ticket-card .btn-primary        { background: var(--game-accent); color: var(--game-ink, #fff); }
.ticket-card .btn-primary:hover:not(:disabled) { filter: brightness(1.1); }
.ticket-card .name-edit-row input { border-color: var(--game-accent); }
.ticket-card .field              { border-bottom-color: var(--line); }
.ticket-card-body                { border-top-color: var(--line); }

/* Scan results card — themed elements */
#scan-results.game-set-for-life,
#scan-results.game-powerball,
#scan-results.game-oz-lotto,
#scan-results.game-saturday-lotto,
#scan-results.game-weekday-windfall { box-shadow: 0 6px 18px rgba(46,26,53,0.08), 0 0 0 2px var(--game-accent); transition: box-shadow 0.3s; }
#scan-results h2            { color: var(--game-accent); }
#scan-results .number-chip  { background-color: var(--game-accent); }
#scan-results .bonus-chip   { background-color: var(--game-accent-alt, var(--gold)); }
#scan-results .btn-primary  { background: var(--game-accent); color: var(--game-ink, #fff); }
#scan-results .btn-primary:hover:not(:disabled) { filter: brightness(1.1); }
#scan-results #ticket-name-input { border-color: var(--game-accent); }
#ticket-name-input::placeholder,
.name-edit-row input::placeholder { color: var(--faint); opacity: 1; }

/* Results page — cards pick up the game accent from the page-level class */
#page-results .card { transition: box-shadow 0.3s; }
#page-results.game-set-for-life .card,
#page-results.game-powerball .card,
#page-results.game-oz-lotto .card,
#page-results.game-saturday-lotto .card,
#page-results.game-weekday-windfall .card { box-shadow: 0 6px 18px rgba(46,26,53,0.08), 0 0 0 2px var(--game-accent); }
#page-results .card h2 { color: var(--game-accent); }
#page-results .back-btn { color: var(--game-accent); }
#page-results .draw-list-item.selected { border-color: var(--game-accent); background: var(--game-bg); }
#page-results .draw-list-item:active { background: var(--game-bg); }
#page-results .page-title { color: var(--game-accent); }

/* =========================================================================
   My Tickets page — collapsible ticket cards
========================================================================= */
.ticket-card {
  width: 100%;
  max-width: 480px;
  background: var(--shell);
  border: none;
  border-radius: var(--r-card, 20px);
  overflow: hidden;
  box-shadow: 0 6px 18px rgba(46, 26, 53, 0.08);
  transition: box-shadow 0.15s;
}
/* Highlight ring when expanded */
.ticket-card.open { box-shadow: 0 6px 18px rgba(46,26,53,0.08), 0 0 0 2px var(--game-accent); }

/* Tappable header row — always visible */
.ticket-card-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 15px 16px;
  cursor: pointer;
  user-select: none;
}
.ticket-card-header:hover { background: var(--game-bg); }

.ticket-card-info { flex: 1; display: flex; flex-direction: column; gap: 3px; }
.ticket-card-name { font-size: 0.98rem; font-weight: 800; color: var(--game-accent); }
.ticket-card-meta { font-size: 0.78rem; font-weight: 600; color: var(--muted); }

/* Chevron rotates when card is open */
.ticket-chevron { color: var(--faint); font-size: 0.75rem; transition: transform 0.2s; flex-shrink: 0; }
.ticket-card.open .ticket-chevron { transform: rotate(180deg); }

/* Expandable body */
.ticket-card-body {
  display: none;
  flex-direction: column;
  gap: 12px;
  padding: 0 16px 16px;
  border-top: 1.5px solid var(--line);
}
.ticket-card.open .ticket-card-body { display: flex; }

/* Field rows inside the expanded body */
.ticket-card-body .field { padding: 6px 0; }

/* Name edit row */
.name-edit-row { display: flex; gap: 8px; align-items: center; }
.name-edit-row input {
  flex: 1; background: var(--shell-2); border: 1.5px solid var(--coral);
  border-radius: 14px; color: var(--ink); padding: 9px 12px; font-size: 0.9rem;
  font-weight: 700; font-family: var(--font-body);
}
.name-edit-row input:focus { outline: none; }
.name-edit-error { font-size: 0.78rem; color: var(--danger); font-weight: 700; min-height: 16px; }

/* Row that holds the edit-name + delete actions at the bottom of the body */
.ticket-card-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  padding-top: 4px;
}
.ticket-card-actions .btn-icon { font-size: 0.85rem; font-weight: 700; padding: 7px 12px; border: 1.5px solid var(--line); border-radius: 999px; color: var(--muted); }
.ticket-card-actions .btn-icon:hover { border-color: var(--game-accent); color: var(--ink); }
.ticket-card-actions .btn-icon.danger:hover { border-color: var(--danger); color: var(--danger); }

/* =========================================================================
   Results page — draw selector
========================================================================= */
.draw-select-row { display: flex; flex-direction: column; gap: 6px; }
.draw-select-row label { font-size: 0.72rem; font-weight: 800; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; }
.draw-select-row select {
  background: var(--shell-2); border: 1.5px solid var(--line); border-radius: 14px;
  color: var(--ink); padding: 11px 14px; font-size: 0.9rem; font-weight: 700; width: 100%;
  appearance: none; font-family: var(--font-body);
}
.draw-select-row select:focus { outline: none; border-color: var(--coral); }

/* Custom draw list — replaces native <select> for full styling control */
.draw-list-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 11px 15px;
  border-radius: var(--r-card-sm, 14px);
  border: 1.5px solid var(--line);
  background: var(--shell-2);
  cursor: pointer;
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--ink);
  transition: background 0.15s, border-color 0.15s;
  user-select: none;
  -webkit-user-select: none;
}
.draw-list-item:active  { background: var(--line); }
.draw-list-item.selected {
  border-color: var(--coral);
  background: #FDE3DC;
  color: var(--ink);
}
.draw-list-item.in-range {
  border-color: var(--teal);
  background: #E9F7F1;
  color: #0E7A63;
}
.draw-list-item.in-range.selected { border-color: #0E7A63; background: #CDEFE2; color: #0A5c4B; }
.draw-list-item.awaiting { color: var(--muted); }
.draw-list-item .draw-badge {
  font-size: 0.72rem;
  font-weight: 800;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--shell);
  color: var(--amber-text);
  white-space: nowrap;
}
.draw-list-item.in-range .draw-badge { background: #CDEFE2; color: #0E7A63; }
.draw-list-item-new {
  border-style: dashed;
  color: var(--amber-text);
  justify-content: center;
}

.range-hint { font-size: 0.75rem; font-weight: 700; color: var(--teal); }

/* =========================================================================
   Empty / loading states
========================================================================= */
.empty-state {
  text-align: center;
  /* sits directly on the ground gradient — the per-theme HUD ink is
     tuned for exactly that (hard-coded white vanished on champagne's
     pale ivory, 2026-07-20) */
  color: var(--hud-text, rgba(255, 255, 255, 0.92));
  text-shadow: var(--hud-shadow, 0 1px 4px rgba(120, 40, 60, 0.25));
  font-weight: 700;
  font-size: 0.9rem;
  padding: 40px 0;
  line-height: 1.6;
}

/* =========================================================================
   Fun prompt strip — playful wait-time micro-question (docs/FUN_PROMPTS.md).
   Slides up while a scan is processing; small glossy "jewel" answer pills.
========================================================================= */
.fun-prompt {
  background: #fff;
  border-radius: var(--r-card, 22px);
  padding: 14px 18px 18px;
  margin-top: 14px;
  box-shadow: 0 -4px 24px rgba(46, 26, 53, 0.10), 0 6px 18px rgba(46, 26, 53, 0.08);
  animation: fun-prompt-in 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes fun-prompt-in {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: translateY(0); }
}
.fun-prompt.leaving {
  animation: fun-prompt-out 0.3s ease forwards;
}
@keyframes fun-prompt-out {
  to { opacity: 0; transform: translateY(16px); }
}
.fun-prompt-handle {
  width: 42px; height: 5px;
  border-radius: 999px;
  background: var(--line);
  margin: 0 auto 10px;
}
.fun-prompt-question {
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
  margin: 0 0 12px;
}
.fun-prompt-answers {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 10px;
}
.fun-pill {
  border: none;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 0.88rem;
  font-weight: 600;
  color: #fff;
  padding: 8px 16px;
  border-radius: 999px;
  /* glossy jewel finish: top sheen + inset bottom bevel */
  background-image: linear-gradient(rgba(255,255,255,0.28), rgba(255,255,255,0) 55%);
  box-shadow: inset 0 -3px 0 rgba(0, 0, 0, 0.18), 0 4px 10px rgba(46, 26, 53, 0.14);
  transition: transform 0.12s;
}
.fun-pill:active { transform: scale(0.94); }
/* jewel colour rotation */
.fun-pill:nth-child(5n+1) { background-color: #4FA8E0; }  /* sky   */
.fun-pill:nth-child(5n+2) { background-color: var(--teal); }
.fun-pill:nth-child(5n+3) { background-color: #8E5BC7; }  /* grape */
.fun-pill:nth-child(5n+4) { background-color: var(--coral); }
.fun-pill:nth-child(5n+5) { background-color: var(--gold); color: #5A3A00; }
.fun-prompt-ack {
  text-align: center;
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 600;
  color: var(--teal);
  padding: 6px 0 2px;
}

/* =========================================================================
   Reveal select screen — filterable menu of reveal types. Cards come from
   the reveal registry's metadata; coming-soon teasers render locked.
========================================================================= */
.reveal-select-sub {
  text-align: center;
  /* on the bare ground gradient — per-theme HUD ink, like .empty-state */
  color: var(--hud-text, rgba(255, 255, 255, 0.92));
  text-shadow: var(--hud-shadow, 0 1px 4px rgba(120, 40, 60, 0.25));
  font-size: 0.9rem;
  font-weight: 700;
  margin: -4px 0 14px;
}
.reveal-filter-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-bottom: 18px;
}
.reveal-filter-chip {
  border: 1.5px solid var(--line);
  background: var(--shell);
  color: var(--muted);
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-weight: 800;
  padding: 6px 14px;
  border-radius: 999px;
  transition: transform 0.12s;
}
.reveal-filter-chip:active { transform: scale(0.95); }
.reveal-filter-chip.active {
  background: var(--coral);
  border-color: var(--coral);
  color: #fff;
  box-shadow: inset 0 -2px 0 rgba(0,0,0,0.14);
}
.reveal-card-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.reveal-card {
  border: none;
  border-radius: var(--r-card, 20px);
  padding: 16px 14px 14px;
  cursor: pointer;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* candy gloss: white sheen over the card tint, glossy bottom bevel */
  background-image: linear-gradient(170deg, rgba(255,255,255,0.8), rgba(255,255,255,0.2) 55%);
  background-color: #fff;
  box-shadow: inset 0 -4px 0 rgba(46, 26, 53, 0.06),
              0 6px 16px rgba(46, 26, 53, 0.10);
  transition: transform 0.15s, box-shadow 0.15s;
  position: relative;
}
.reveal-card:hover { transform: translateY(-2px); box-shadow: inset 0 -4px 0 rgba(46,26,53,0.06), 0 10px 22px rgba(46, 26, 53, 0.14); }
.reveal-card:active { transform: scale(0.97); }
/* Candy tint rotation — each reveal panel gets its own colour */
.reveal-card:nth-child(5n+1) { background-color: #FFE9ED; }  /* coral  */
.reveal-card:nth-child(5n+2) { background-color: #E4F6EF; }  /* teal   */
.reveal-card:nth-child(5n+3) { background-color: #EFE7FB; }  /* grape  */
.reveal-card:nth-child(5n+4) { background-color: #E3F2FB; }  /* sky    */
.reveal-card:nth-child(5n+5) { background-color: #FFF3DC; }  /* gold   */
.reveal-card-icon {
  width: 48px; height: 48px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  color: #fff;
  margin-bottom: 8px;
  background-image: linear-gradient(rgba(255,255,255,0.35), rgba(255,255,255,0) 55%);
  box-shadow: inset 0 -3px 0 rgba(0,0,0,0.18), 0 4px 10px rgba(46,26,53,0.15);
}
.reveal-card:nth-child(5n+1) .reveal-card-icon { background-color: var(--coral); }
.reveal-card:nth-child(5n+2) .reveal-card-icon { background-color: var(--teal); }
.reveal-card:nth-child(5n+3) .reveal-card-icon { background-color: #8E5BC7; }
.reveal-card:nth-child(5n+4) .reveal-card-icon { background-color: #4FA8E0; }
.reveal-card:nth-child(5n+5) .reveal-card-icon { background-color: var(--gold); }
.reveal-card-icon svg {
  width: 26px; height: 26px;
  stroke: currentColor; fill: none;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.reveal-card-name {
  font-family: var(--font-display);
  font-size: 1.02rem;
  font-weight: 600;
  color: var(--ink);
}
.reveal-card-desc {
  font-size: 0.78rem;
  color: var(--muted);
  line-height: 1.45;
  margin: 6px 0 10px;
  min-height: 2.9em;
}
.reveal-card-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 5px;
}
.reveal-meta-chip {
  font-size: 0.68rem;
  font-weight: 800;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--shell);
  color: var(--amber-text);
}
.reveal-meta-chip.duration { background: #E3F2FB; color: #2B6C9E; }
.reveal-card.locked {
  cursor: default;
  opacity: 0.65;
  background: var(--shell);
}
.reveal-card.locked:hover { transform: none; box-shadow: 0 6px 16px rgba(46, 26, 53, 0.08); }
.reveal-card-soon {
  position: absolute;
  top: 10px; right: 10px;
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: var(--gold);
  color: #5A3A00;
  padding: 3px 8px;
  border-radius: 999px;
  box-shadow: inset 0 -2px 0 rgba(0,0,0,0.12);
}

/* =========================================================================
   Context bar — the always-selected ticket + draw, shown consistently on
   the reveal-select and draws pages. Chips are tappable to change either.
========================================================================= */
.context-bar {
  display: flex;
  justify-content: center;
  gap: 8px;
  width: 100%;
  max-width: 480px;
  margin-bottom: 14px;
}
.context-chip {
  flex: 1;
  min-width: 0;
  border: 1.5px solid var(--line);
  background: #fff;
  border-radius: 14px;
  padding: 8px 12px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  font-family: var(--font-body);
  box-shadow: 0 4px 10px rgba(46, 26, 53, 0.06);
  transition: transform 0.12s;
}
.context-chip:active { transform: scale(0.97); }
.context-chip-label {
  font-size: 0.66rem;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.context-chip-label svg {
  width: 13px; height: 13px;
  stroke: currentColor; fill: none;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.context-chip-value {
  font-size: 0.86rem;
  font-weight: 800;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.context-chip-value.awaiting { color: var(--amber-text); }

/* Quick draw menu — the 4 most recent draws + "More draws…".
   A fixed dropdown under the app bar (not page content): the draw pill
   lives in the app bar and shows on more than one page, so the menu must
   too. Below the bars (100), above everything in the pages. */
#draw-menu {
  position: fixed;
  /* sits ABOVE the draw tile (2026-07-21) — anchored to the tile's
     right edge, clear of its top, and z-INDEXED over the tiles (99)
     and bar (100): at z-90 it opened BEHIND the docked tabs (Jamie).
     Slides in quickly from the right, matching its trigger's side. */
  bottom: calc(var(--tabbar-h) + 112px);   /* the docked tile stands ~104px proud */
  right: 8px;
  left: auto;
  width: calc(100% - 32px);
  max-width: 420px;
  margin: 0;
  z-index: 130;
  box-shadow: 0 14px 34px rgba(46, 26, 53, 0.22);
  animation: draw-menu-in 0.22s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes draw-menu-in {
  from { opacity: 0; transform: translateX(60px); }
  to   { opacity: 1; transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  #draw-menu { animation: none; }
}
.draw-menu {
  width: 100%;
  max-width: 480px;
  background: #fff;
  border: 1.5px solid var(--line);
  border-radius: var(--r-card-sm, 16px);
  padding: 8px;
  margin: -6px 0 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  box-shadow: 0 10px 24px rgba(46, 26, 53, 0.12);
  animation: fun-prompt-in 0.25s cubic-bezier(0.22, 1, 0.36, 1);
}

/* =========================================================================
   Credits (docs/REVENUE.md) — gold jewel balance chips + card cost labels.
   Hidden entirely while the server reports credits disabled.
========================================================================= */
.context-chip--credits {
  flex: 0 0 auto;
  background-color: #FFF3DC;
}
.context-chip--credits .context-chip-value { color: #8A5A00; }
.home-credits {
  align-items: center;
  gap: 6px;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 0.9rem;
  font-weight: 600;
  color: #8A5A00;
  background-color: var(--gold);
  background-image: linear-gradient(rgba(255,255,255,0.35), rgba(255,255,255,0) 55%);
  padding: 7px 16px;
  border-radius: 999px;
  box-shadow: inset 0 -3px 0 rgba(0,0,0,0.14), 0 4px 10px rgba(46,26,53,0.12);
}
.reveal-meta-chip.cost { background: #FFF3DC; color: #8A5A00; }

/* =========================================================================
   Desktop phone-frame: on wide screens the app column is capped at phone
   width and centred, so desktop browser testing shows the same framing,
   canvas aspect, and 3D sizing as the real phone product. Under 520px
   (actual phones) nothing changes.
========================================================================= */
@media (min-width: 520px) {
  body { background: #F0DCC8; }
  .page {
    width: 430px;
    margin: 0 auto;
    background: var(--cream);
    box-shadow: 0 0 44px rgba(46, 26, 53, 0.18);
  }
  /* The reveal page opts out of normal page padding via inline styles, so
     the width cap needs to win over them here */
  #page-reveal {
    width: 430px !important;
    max-width: 430px !important;
  }
}
@media (min-width: 520px) {
  /* Cap the reveal canvas height on tall desktop windows so the 430px
     column keeps a phone-like aspect */
  #reveal-canvas {
    height: min(calc(100vh - var(--appbar-h) - var(--tabbar-h) - 168px), 640px) !important;
  }
  #page-reveal.summary-mode #reveal-canvas { height: min(30vh, 300px) !important; }
}

/* Info button on ticket cards — expands details (tapping the card itself
   goes straight to the reveal-select screen) */
.ticket-info-btn {
  flex-shrink: 0;
  width: 34px; height: 34px;
  border: none;
  border-radius: 50%;
  background: var(--shell-2);
  color: var(--muted);
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  box-shadow: inset 0 -2px 0 rgba(46, 26, 53, 0.06);
}
.ticket-info-btn:active { transform: scale(0.92); }
.ticket-info-btn svg {
  width: 18px; height: 18px;
  stroke: currentColor; fill: none;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
  transition: transform 0.2s;
}
.ticket-card.open .ticket-info-btn { background: var(--coral); color: #fff; }
.ticket-card.open .ticket-info-btn svg { transform: rotate(180deg); }


/* App-shell sizing for the reveal canvas (inline height removed from the
   HTML so the dvh fallback chain works) */
/* No background of its own: the 3D reveals render on transparent canvases
   so the page's sunset gradient is the stage backdrop (video reveals paint
   their own full-bleed frame). Height fills everything between the bars
   minus the HUD's reserved slot — the SAME on every reveal type, so the
   controls never wander between games. */
#reveal-canvas {
  position: relative;   /* anchors overlays: reveal prompts + the win card */
  width: 100%;
  /* the 168px reserve MUST cover the HUD's real worst case (progress +
     draw + status + Next button ≈ 116px) + the page's 48px bottom
     padding that clears the full-size hero button (2026-07-21; a
     shorter reserve = dead scroll or the Next button under the bar) */
  height: calc(100vh - var(--appbar-h) - var(--tabbar-h) - 168px);
  height: calc(100dvh - var(--appbar-h) - var(--tabbar-h) - 168px);
  flex-shrink: 0;
}

/* Win card — pops over the scene when a winning game resolves: that
   game's numbers (hits coloured like the summary), ticket, draw,
   division, prize. Tap to dismiss; advancing clears it. */
#win-card {
  position: absolute;
  left: 50%;
  top: 46%;
  transform: translate(-50%, -50%);
  z-index: 6;
  width: min(340px, 88%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
  padding: 18px 20px 12px;
  border-radius: var(--r-card, 20px);
  border: 1.5px solid rgba(255, 138, 30, 0.65);
  background: linear-gradient(180deg, #FFFBF0, #FFEFC9);
  box-shadow: 0 18px 44px rgba(46, 26, 53, 0.28), 0 4px 14px rgba(255, 138, 30, 0.35);
  cursor: pointer;
  overflow: hidden;
  animation: win-card-in 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* own keyframes — they must carry the translate(-50%,-50%) centring */
@keyframes win-card-in {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0.7); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
#win-card::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg,
    transparent 32%, rgba(255, 255, 255, 0.8) 48%, transparent 62%);
  transform: translateX(-130%);
  animation: summary-shine 3s cubic-bezier(0.4, 0, 0.2, 1) infinite;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  #win-card::after { animation: none; }
  #win-card { animation: none; }
}
.win-card-kicker {
  font-family: var(--font-display);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #B8763F;
}
.win-card-division {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 600;
  color: #F2536B;
}
.win-card-prize {
  font-family: var(--font-display);
  font-size: 1.9rem;
  font-weight: 600;
  color: #8A5A00;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
}
#win-card .summary-balls { justify-content: center; margin-top: 4px; }
.win-card-meta {
  font-size: 0.76rem;
  font-weight: 700;
  color: var(--muted);
  margin-top: 6px;
}
.win-card-hint {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(155, 131, 145, 0.75);
}
/* Summary mode: the show is over — the scene shrinks so the summary card
   sits in view instead of below a fold of empty stage */
#page-reveal.summary-mode #reveal-canvas { height: 30vh; height: 30dvh; }
#page-reveal.summary-mode #reveal-hud { min-height: 0; }
/* Summary states must clear the DOCKED TILES (~110px above the tab bar),
   not just the bar itself — and on iOS the safe-area inset lifts the
   whole bottom chrome higher still. The clearance is a REAL in-flow
   spacer (::after), NOT container padding: iOS Safari does not include
   a scroll container's own padding-bottom in the scrollable range, so
   the padding version scrolled fine in Chromium/the suite while an
   iPhone stopped at the content edge with the last game still under
   the tiles (Jamie 2026-08-03, 3-game ticket). padding zeroed so
   Chromium doesn't get the clearance twice; !important beats the
   page's inline padding. */
#page-reveal.summary-mode {
  padding-bottom: 0 !important;
}
#page-reveal.summary-mode::after {
  content: "";
  display: block;
  flex: none;
  height: calc(var(--tabbar-h) + 150px + env(safe-area-inset-bottom, 0px));
}

/* Compact reveal HUD — Skip / progress·status / Skip all in ONE thin row,
   identical across reveal types (mockup 2026-07-15). Fixed min-height:
   Skip↔Next toggling must never shift the page. */
#reveal-hud {
  max-width: 480px;
  width: 100%;
  margin: 0 auto;
  padding: 0 10px;
  display: flex;
  align-items: flex-start;
  gap: 4px;
  min-height: 52px;    /* two text lines; the action button floats over
                          the canvas — reserve math at #reveal-canvas */
}
.reveal-text-btn {
  flex-shrink: 0;
  min-width: 58px;
  background: none;
  border: none;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-weight: 800;
  color: #7A4460;
  text-decoration: underline;
  text-underline-offset: 3px;
  padding: 10px 6px;
  transition: transform 0.1s;
}
.reveal-text-btn:active { transform: scale(0.92); }
.reveal-hud-centre {
  flex: 1;
  min-width: 0;
  position: relative;   /* anchors the floating action button */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
#reveal-progress {
  font-size: 0.72rem;
  font-weight: 800;
  color: var(--hud-text-sub);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  text-shadow: var(--hud-shadow);
  margin-top: 8px;
}
#reveal-draw-label {
  font-size: 0.68rem;
  color: var(--hud-text-sub);
  text-shadow: var(--hud-shadow);
}
#reveal-status {
  font-size: 1rem;
  font-weight: 800;
  color: var(--hud-text);
  min-height: 1.45em;
  text-shadow: var(--hud-shadow);
}
.reveal-next {
  padding: 8px 24px;
  font-size: 0.9rem;
  border-radius: 999px;
  width: auto;
  /* floats over the scene's bottom edge, just above the HUD text —
     ABSOLUTE so it takes NO layout space: the text sits at the same
     height whether the button is showing or not (Jamie 2026-07-23:
     the resolved height is the default now, no pop) */
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 6;
  box-shadow: 0 6px 18px rgba(46, 26, 53, 0.35);
}


/* =========================================================================
   App chrome — fixed top bar + bottom tab bar, OUTSIDE the page scroll
   containers (they can never move with iOS rubber-banding).
========================================================================= */
#appbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  /* True frosted glass: a near-neutral white haze over a backdrop blur,
     so the bar takes its colour entirely from whatever is behind it —
     it reads as the gradient while idle and frosts content that scrolls
     under, and automatically matches any future ground. (The first
     attempt tinted the glass CREAM, which visibly fought the orange
     behind it — keep the tint neutral.) */
  background: rgba(255, 255, 255, 0.14);
  -webkit-backdrop-filter: blur(18px) saturate(1.5);
  backdrop-filter: blur(18px) saturate(1.5);
  border-bottom: 1px solid rgba(255, 255, 255, 0.25);
  padding-top: env(safe-area-inset-top, 0px);
}
.appbar-row {
  position: relative;   /* anchors the absolutely-placed credits chip */
  height: 48px;
  display: flex;
  align-items: center;
  padding: 0 12px;
  gap: 8px;
}
/* Invisible right-hand twin of the 40px back button — the title is
   flex:1 between them, so its centre is the TRUE row centre whether or
   not the back button is showing (it's visibility-hidden, never
   removed, so the left side always reserves its 40px) */
.appbar-row::after { content: ""; width: 40px; flex-shrink: 0; }
.appbar-back {
  width: 40px; height: 40px;
  border: none; background: none;
  color: var(--coral);
  font-size: 1.35rem;
  font-weight: 800;
  cursor: pointer;
  flex-shrink: 0;
}
.appbar-title {
  /* absolutely centred (2026-07-24): the flex:1-between-twins trick
     only balanced the BACK button — the filter funnel and view toggle
     pushed the title 21px off screen-centre on select/check pages.
     Absolute centring is immune to however many buttons flank it. */
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  max-width: 50%;
  text-align: center;
  font-family: var(--font-display);
  font-size: 1.12rem;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.appbar-title .fun   { color: var(--coral); }
.appbar-title .lotto { color: var(--teal); }
.appbar-credits {
  /* pinned right so the title's flex centring never shifts when the
     balance appears/disappears */
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 5px;
  border: none;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 0.86rem;
  font-weight: 800;
  color: #8A5A00;
  background-color: var(--gold);
  background-image: linear-gradient(rgba(255,255,255,0.35), rgba(255,255,255,0) 55%);
  padding: 5px 12px;
  border-radius: 999px;
  box-shadow: inset 0 -2px 0 rgba(0,0,0,0.14);
}
.appbar-credits svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 2.2; }

/* Context pills — now living in the BOTTOM tab bar on reveal-flow pages
   (body.context-tabs): Home/Tickets swap out, ticket + draw pills swap in
   flanking the Reveal button. Hidden everywhere else. */
/* Floating tiles (2026-07-19): the ticket + draw tiles sit ABOVE the tab
   bar in the corners, with the normal Home/Reveal/Tickets tabs staying
   put beneath them. Shown on reveal-select + draws; hidden during an
   active reveal (body.reveal-stage) — the HUD owns that zone. */
#ctx-ticket-chip, #ctx-draw-chip { display: none; }
body.context-tabs #ctx-ticket-chip,
body.context-tabs #ctx-draw-chip { display: flex; }
/* During an active check the tiles STAY UP (2026-07-22 — they replace
   the HUD's draw-label text) as a glowing, display-only statement of
   WHICH ticket and draw are being checked. Static glow — no animation
   (house motion rules); dark grounds glow in their own gold below. */
body.context-tabs.reveal-stage #ctx-ticket-chip,
body.context-tabs.reveal-stage #ctx-draw-chip {
  pointer-events: none;
}
body.context-tabs.reveal-stage #ctx-ticket-chip {
  border-color: var(--game-accent, rgba(242, 83, 107, 0.75));
  box-shadow: 0 0 18px color-mix(in srgb, var(--game-accent, #F2536B) 55%, transparent),
              0 -4px 14px rgba(46, 26, 53, 0.16),
              inset 0 1px 0 rgba(255, 255, 255, 0.65);
}
body.context-tabs.reveal-stage #ctx-draw-chip {
  border-color: rgba(23, 169, 139, 0.8);
  box-shadow: 0 0 18px rgba(23, 169, 139, 0.55),
              0 -4px 14px rgba(46, 26, 53, 0.16),
              inset 0 1px 0 rgba(255, 255, 255, 0.65);
}
body.ground-dark.context-tabs.reveal-stage #ctx-ticket-chip,
body.ground-dark.context-tabs.reveal-stage #ctx-draw-chip {
  border-color: var(--hl);
  box-shadow: 0 0 18px rgba(214, 173, 66, 0.5),
              0 -4px 14px rgba(0, 0, 0, 0.3);
}

/* Glossy candy tiles (2026-07-19): the ticket + draw pills are the
   persistent "you are here" anchors of the reveal flow — they read as
   raised glass sweets. Efficiency rules: gloss is a STATIC gradient,
   the glint is a transformed ::after (compositor-only, mostly-idle
   loop), the entrance is transform/opacity keyframes. Nothing here
   repaints per frame. */
.tab-pill {
  position: fixed;
  /* DOCKED to the tab bar: the bottom edge tucks 12px under the bar
     (2026-07-21, was 2px) so the entrance overshoot can lift the tile
     a few px without ever exposing a gap; the tucked strip hides
     behind the bar (tiles are now SIBLINGS below its z-index, so the
     occlusion is real — and the gold hero button covers their inner
     corners deliberately) */
  bottom: calc(var(--tabbar-h) - 12px);
  z-index: 99;                       /* under the bar: rises from BEHIND it */
  flex-direction: column;
  align-items: center;
  gap: 2px;
  /* identical size, both tiles — wider since 2026-07-21 (each reaches
     toward the middle; the hero button covers their inner corners) */
  width: 47vw;
  max-width: 216px;
  padding: 10px 14px 22px;           /* +10px bottom = the deeper tuck */
  overflow: hidden;                  /* clips the glint sweep */
  border-radius: var(--r-card, 18px) var(--r-card, 18px) 0 0;      /* top corners only — attached below */
  transition: transform 0.12s;
}
.tab-pill:active { transform: scale(0.94); }
/* Visuals live on the IDs: the base .tab class (later in this file)
   resets background/border for in-bar tabs, and would erase a
   class-level frost. TRUE frosted glass, same recipe as #appbar: a
   near-neutral white haze over a backdrop blur — the tile takes its
   colour from whatever scrolls beneath (neutral-tint rule; the
   accents stay in the borders and badges). */
#ctx-ticket-chip, #ctx-draw-chip {
  background: rgba(255, 255, 255, 0.34);
  -webkit-backdrop-filter: blur(18px) saturate(1.5);
  backdrop-filter: blur(18px) saturate(1.5);
  border: 2px solid rgba(242, 83, 107, 0.30);
  border-bottom: none;               /* continuous with the bar beneath */
  box-shadow: 0 -4px 14px rgba(46, 26, 53, 0.16),
              inset 0 1px 0 rgba(255, 255, 255, 0.65);
}
#ctx-ticket-chip {
  left: 8px;
  /* border in the GAME's accent colour (Jamie 2026-07-23) — the game-*
     class supplies --game-accent; coral tint when no ticket yet */
  border-color: var(--game-accent, rgba(242, 83, 107, 0.30));
}
#ctx-draw-chip {
  right: 8px;
  border-color: rgba(23, 169, 139, 0.34);
}
/* glossy top-light over the frost (neutral whites only) */
.tab-pill::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.10) 46%,
    rgba(255, 255, 255, 0) 54%, rgba(255, 255, 255, 0.15));
  pointer-events: none;
}
/* the glint — a light stripe sweeping the tile ONCE, just after it
   docks (2026-07-20: was a 4.4s loop, too busy). display:none→flex
   restarts it, so every re-entry to a context page earns one sweep;
   the draw tile follows its later dock-in */
.tab-pill::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg,
    transparent 36%, rgba(255, 255, 255, 0.9) 50%, transparent 62%);
  transform: translateX(-130%);
  animation: pill-glint 1.2s cubic-bezier(0.4, 0, 0.2, 1) 0.7s 1 both;
  pointer-events: none;
}
#ctx-draw-chip::after { animation-delay: 0.95s; }
@keyframes pill-glint {
  0%   { transform: translateX(-130%); }
  100% { transform: translateX(130%); }
}
/* a SECOND shine when a check starts (2026-07-23): entering
   body.reveal-stage swaps the animation NAME, which restarts it —
   one sweep over each tile as the check kicks off, draw a beat later */
body.context-tabs.reveal-stage #ctx-ticket-chip::after,
body.context-tabs.reveal-stage #ctx-draw-chip::after {
  animation: pill-glint-check 0.9s cubic-bezier(0.4, 0, 0.2, 1) 0.35s 1 both;
}
body.context-tabs.reveal-stage #ctx-draw-chip::after { animation-delay: 0.6s; }
@keyframes pill-glint-check {
  0%   { transform: translateX(-130%); }
  100% { transform: translateX(130%); }
}
/* entrance: both tiles RISE from BEHIND the bar and dock onto it — the
   bar (z-100) genuinely occludes them (z-99) mid-rise, so they emerge.
   Draw follows a beat behind. display:none→flex restarts this on every
   context entry. */
body.context-tabs #ctx-ticket-chip {
  animation: tile-dock 0.5s cubic-bezier(0.34, 1.45, 0.64, 1) backwards;
}
body.context-tabs #ctx-draw-chip {
  animation: tile-dock 0.5s cubic-bezier(0.34, 1.45, 0.64, 1) 0.1s backwards;
}
@keyframes tile-dock {
  from { transform: translateY(105%); }
  to   { transform: translateY(0); }
}
/* the docked tiles cover the page's bottom corners — give the two
   context pages room so the last content can always scroll clear */
#page-reveal-select, #page-draws { padding-bottom: calc(var(--tabbar-h) + 118px); }
@media (prefers-reduced-motion: reduce) {
  .tab-pill::after { animation: none; opacity: 0; }
  body.context-tabs.reveal-stage #ctx-ticket-chip::after,
  body.context-tabs.reveal-stage #ctx-draw-chip::after { animation: none; opacity: 0; }
  body.context-tabs #ctx-ticket-chip,
  body.context-tabs #ctx-draw-chip { animation: none; }
}
.tab-pill .game-badge { width: 32px; height: 32px; font-size: 0.85rem; }
.tab-pill-badge {
  width: 32px; height: 32px;
  border-radius: 50%;
  background-color: var(--teal);
  background-image: linear-gradient(rgba(255,255,255,0.35), rgba(255,255,255,0) 55%);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.tab-pill-badge svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2.2; }
.tab-pill-caption {
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.tab-pill-caption.awaiting { color: var(--amber-text); }
.tab-pill-value {
  font-size: 0.9rem;
  font-weight: 800;
  color: var(--ink);
  max-width: 150px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#ctx-draw-name.awaiting { color: var(--amber-text); }
#ctx-draw-chip.in-range #ctx-draw-name { color: #17A98B; }
/* Admin draw-scan review: Vision confidence chips */
.ad-conf-chip {
  font-size: 0.72rem;
  font-weight: 800;
  text-transform: capitalize;
  padding: 4px 12px;
  border-radius: 999px;
}
.ad-conf-chip.good { background: #DFF4EB; color: #0E7A63; }
.ad-conf-chip.warn { background: #FFF3DC; color: #8A5A00; }
.ad-conf-chip.bad  { background: #FFE4E9; color: #C33553; }

/* Page filters: a funnel in the frosted app bar (left of the title) that
   expands into a chips row on the page — reveals on reveal-select,
   game types on My Tickets */
.appbar-filter {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border: 1.5px solid rgba(255, 255, 255, 0.75);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.65);
  color: var(--muted);
  cursor: pointer;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 8px rgba(46, 26, 53, 0.08);
  transition: transform 0.1s;
}
.appbar-filter:active { transform: scale(0.92); }
.appbar-filter svg {
  width: 16px; height: 16px;
  stroke: currentColor; fill: none;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
/* an active (non-All) filter tints the funnel so its effect is never
   hidden by the collapse */
.appbar-filter.filtering { background: var(--coral); color: #fff; border-color: var(--coral); }
/* with the funnel showing, the title's right-hand balance spacer grows to
   match back(40) + gap + funnel(34) so the title stays truly centred */
.appbar-row.has-filter::after { width: 82px; }

/* Draw pill turns softly green when the selected draw falls inside the
   selected ticket's draw range */
#ctx-draw-chip.in-range .tab-pill-badge { box-shadow: 0 0 10px rgba(23, 169, 139, 0.55); }

#tabbar {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  z-index: 100;
  background: var(--shell);
  border-top: 1.5px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  display: flex;
  height: calc(62px + env(safe-area-inset-bottom, 0px));
}
/* Solid bleed below the bar: in black-translucent standalone mode iOS
   can anchor fixed elements to a layout viewport that ends ABOVE the
   home-indicator zone, letting the body gradient peek out underneath —
   this paints the bar's own shell colour all the way to the physical
   bottom no matter where iOS draws that line */
#tabbar::after {
  content: "";
  position: absolute;
  top: 100%; left: 0; right: 0;
  height: 120px;
  background: var(--shell);
}
.tab {
  flex: 1;
  border: none;
  background: none;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  font-family: var(--font-body);
  font-size: 0.68rem;
  font-weight: 800;
  color: var(--muted);
}
.tab svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.tab.active { color: var(--coral); }
/* Hero centre button (2026-07-21): the brand-orange ball, 70px (92px
   read too big on device — Jamie), centre EXACTLY on the bar's top line
   (absolute top:-35px = half of 70). SAME size everywhere including
   active reveals — the reveal page clears it with extra HUD bottom
   padding instead of the button shrinking (consistency, Jamie). The
   label tucks into the strip under the circle via flex-end. */
.tab--scan { position: relative; justify-content: flex-end; padding-bottom: 4px; }
.tab-scan-circle {
  position: absolute;
  top: -35px; left: 50%;
  margin-left: -35px;
  width: 70px; height: 70px;
  border-radius: 50%;
  background-color: var(--win);
  background-image: linear-gradient(rgba(255,255,255,0.35), rgba(255,255,255,0) 55%);
  box-shadow: inset 0 -4px 0 rgba(0,0,0,0.18), 0 5px 16px rgba(46,26,53,0.30);
  display: flex; align-items: center; justify-content: center;
  color: #fff;
}
.tab-scan-circle svg { width: 30px; height: 30px; }

/* Desktop phone-frame: the bars hug the 430px column, not the window */
@media (min-width: 520px) {
  #appbar, #tabbar {
    left: 50%;
    transform: translateX(-50%);
    width: 430px;
  }
}


/* Premium feel: pages enter with a soft fade-rise; tabs give press feedback */
@keyframes page-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.page.active { animation: page-in 0.18s ease; }
.tab { transition: transform 0.1s; }
.tab:active { transform: scale(0.92); }
.appbar-back:active { transform: scale(0.88); }
.appbar-back { transition: transform 0.1s; }


/* Favourite star on reveal cards */
.reveal-fav {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 2;
  width: 34px;
  height: 34px;
  /* circle + touch target stay 34px; the star itself is smaller
     (2026-07-20 — was padding 6px / a 22px glyph) */
  padding: 9px;
  border: none;
  background: rgba(255, 255, 255, 0.55);
  border-radius: 50%;
  cursor: pointer;
  color: #C9B8A8;
  transition: transform 0.12s, color 0.15s;
}
.reveal-fav svg { width: 100%; height: 100%; fill: none; }
.reveal-fav:active { transform: scale(0.85); }
.reveal-fav.active { color: #F4B23F; }
.reveal-fav.active svg { fill: #F4B23F; }
.reveal-card.locked .reveal-fav { opacity: 0.8; }

/* Ticket favourite star — inline flex sibling of the unfurl chevron
   (the reveal-fav look, without its absolute positioning) */
.ticket-fav {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  padding: 9px;   /* smaller star, same 34px touch target */
  border: none;
  background: transparent;
  cursor: pointer;
  color: #C9B8A8;
  transition: transform 0.12s, color 0.15s;
}
.ticket-fav svg { width: 100%; height: 100%; fill: none; }
.ticket-fav:active { transform: scale(0.85); }
.ticket-fav.active { color: #F4B23F; }
.ticket-fav.active svg { fill: #F4B23F; }

/* =========================================================================
   Top-up modal (Stripe credit packs) — docs/STRIPE_SPEC.md
========================================================================= */
#topup-modal {
  position: fixed;
  inset: 0;
  z-index: 400;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(46, 26, 53, 0.5);
  backdrop-filter: blur(3px);
  animation: topup-fade 0.2s ease;
}
@keyframes topup-fade { from { opacity: 0; } to { opacity: 1; } }
.topup-sheet {
  width: 100%;
  max-width: 460px;
  background: #FFFDF6;
  border-radius: var(--r-card, 22px) var(--r-card, 22px) 0 0;
  padding: 20px 20px calc(20px + env(safe-area-inset-bottom));
  box-shadow: 0 -8px 30px rgba(46, 26, 53, 0.25);
  position: relative;
  animation: topup-rise 0.28s cubic-bezier(0.34, 1.4, 0.64, 1);
}
@keyframes topup-rise { from { transform: translateY(100%); } to { transform: translateY(0); } }
.topup-close {
  position: absolute; top: 12px; right: 14px;
  width: 34px; height: 34px; border: none; border-radius: 50%;
  background: #F1E5D8; color: #8A7767; font-size: 1.4rem; line-height: 1;
  cursor: pointer;
}
.topup-title { font-size: 1.2rem; font-weight: 800; color: #2E1A35; margin: 2px 0 2px; }
.topup-balance { color: #9B8391; font-size: 0.9rem; margin-bottom: 14px; }
.topup-balance strong { color: #F2536B; }
.topup-packs { display: flex; gap: 10px; }
.topup-pack {
  flex: 1;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 16px 8px;
  border: 2px solid var(--line, #F3DCCB);
  border-radius: var(--r-card-sm, 16px);
  background: #FFF3E7;
  cursor: pointer;
  transition: transform 0.12s, border-color 0.15s;
}
.topup-pack:active { transform: scale(0.96); }
.topup-pack:hover { border-color: #F2536B; }
.topup-pack.loading { opacity: 0.6; pointer-events: none; }
.topup-pack-credits { font-size: 1.5rem; font-weight: 800; color: #2E1A35; }
.topup-pack-label { font-size: 0.72rem; color: #9B8391; text-transform: uppercase; letter-spacing: 0.05em; }
.topup-pack-price { margin-top: 6px; font-size: 1rem; font-weight: 700; color: #F2536B; }
.topup-fineprint { text-align: center; color: #B9A899; font-size: 0.72rem; margin-top: 14px; }

/* Reveal summary — traditional check-results layout: drawn numbers up
   top (once), then every game's picks with matches lit gold/teal.
   Tightened: no per-pool label lines — bonus/supplementary numbers sit
   inline behind a compact tag instead of forcing a new row. */
.summary-section-title {
  font-size: 0.78rem;
  font-weight: 800;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin: 0 0 6px;
}
.summary-balls { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; }
.summary-tag {
  font-size: 0.62rem;
  font-weight: 800;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0 1px 0 3px;
  align-self: center;
}
.summary-ball {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 28px;
  height: 28px;
  padding: 0 3px;
  border-radius: 50%;
  background: #F1E5D8;
  color: #8A7767;
  font-weight: 800;
  font-size: 0.8rem;
}
.summary-ball.drawn { background: #2E1A35; color: #fff; }
.summary-ball.hit-main { background: var(--win); color: #fff; box-shadow: 0 2px 6px rgba(255, 138, 30, 0.5); }
.summary-ball.hit-bonus { background: #17A98B; color: #fff; box-shadow: 0 2px 6px rgba(23, 169, 139, 0.45); }

.summary-game {
  padding: 8px 0;
  border-top: 1px solid var(--line);
}
.summary-game:first-of-type { border-top: none; padding-top: 2px; }
/* Winning rows get the gold treatment: a warm card + a slow shine sweep.
   THEME-OWNED via --win-* (2026-07-21): the hard-coded cream card made
   ivory ink unreadable on dark grounds — light themes keep this exact
   look through the fallbacks; dark themes supply a dark-gold recipe. */
/* winning rows are tappable — they replay the win card + coin shower */
#reveal-summary-body .summary-game.win { cursor: pointer; }
#reveal-summary-body .summary-game.win:active { transform: scale(0.99); }

.summary-game.win {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--win-border, rgba(255, 138, 30, 0.55));
  border-radius: var(--r-card-sm, 14px);
  background: var(--win-bg, linear-gradient(180deg, #FFF6EA, #FFE9CF));
  padding: 8px 10px;
  margin: 6px 0;
  box-shadow: var(--win-shadow, 0 3px 10px rgba(255, 138, 30, 0.22));
}
.summary-game.win + .summary-game { border-top: none; }
.summary-game.win::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg,
    transparent 32%, var(--win-shine, rgba(255, 255, 255, 0.8)) 48%, transparent 62%);
  transform: translateX(-130%);
  animation: summary-shine 3s cubic-bezier(0.4, 0, 0.2, 1) infinite;
  pointer-events: none;
}
@keyframes summary-shine {
  0%       { transform: translateX(-130%); }
  55%, 100% { transform: translateX(130%); }
}
@media (prefers-reduced-motion: reduce) {
  .summary-game.win::after { animation: none; }
}
.summary-game-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 6px;
}
.summary-game-result { font-size: 0.85rem; font-weight: 700; color: var(--muted); }
.summary-game-result.win { color: var(--win-result, #F25F0A); }

/* =========================================================================
   Simple Results reveal — the summary card, performed (DOM, no WebGL).
   Borrows the summary ball/game classes above; these styles only add the
   card shell, the unrevealed-slot state, the pop, and the match shine.
========================================================================= */
.sr-card {
  height: auto;
  max-height: 100%;
  overflow-y: auto;
  max-width: 480px;
  margin: 0 auto;
  background: rgba(255, 253, 246, 0.94);
  border: 1px solid rgba(255, 255, 255, 0.7);
  border-radius: var(--r-card, 22px);
  padding: 14px 16px 18px;
  box-shadow: 0 10px 30px rgba(46, 26, 53, 0.16);
  touch-action: pan-y;
}
/* a pick not yet revealed: a quiet empty slot, number hidden */
.summary-ball.sr-slot {
  color: transparent;
  background: rgba(138, 119, 103, 0.14);
  box-shadow: inset 0 0 0 1.5px rgba(138, 119, 103, 0.22);
  transform: scale(0.92);
}
/* the pop: overshoot in, then settle — colours come from the hit classes */
.summary-ball.sr-slot.sr-in {
  color: inherit;
  background: #F1E5D8;
  box-shadow: none;
  transform: none;
  animation: sr-pop 0.42s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.summary-ball.sr-slot.sr-in.hit-main {
  color: #fff; background: var(--win);
  box-shadow: 0 2px 6px rgba(255, 138, 30, 0.5);
}
.summary-ball.sr-slot.sr-in.hit-bonus {
  color: #fff; background: #17A98B;
  box-shadow: 0 2px 6px rgba(23, 169, 139, 0.45);
}
@keyframes sr-pop {
  0%   { transform: scale(0.3); opacity: 0.2; }
  62%  { transform: scale(1.16); opacity: 1; }
  100% { transform: scale(1); }
}
/* match moment: a white ring blooms off the ball, synced with its sting */
.summary-ball.sr-shine { position: relative; }
.summary-ball.sr-shine::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.95);
  animation: sr-ring 0.7s ease-out 0.05s both;
  pointer-events: none;
}
@keyframes sr-ring {
  0%   { transform: scale(0.7); opacity: 1; }
  100% { transform: scale(1.7); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .summary-ball.sr-slot.sr-in { animation: none; }
  .summary-ball.sr-shine::after { animation: none; opacity: 0; }
}

/* =========================================================================
   Portrait lock — the manifest's orientation flag covers installed
   Android; iOS web apps have NO orientation lock, so touch devices in
   landscape get a full-screen ask instead. The hover/pointer terms keep
   it away from desktops, where landscape windows are simply normal.
========================================================================= */
#rotate-lock { display: none; }
@media screen and (orientation: landscape) and (hover: none) and (pointer: coarse) {
  #rotate-lock {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    background: var(--cream);
    text-align: center;
  }
  #rotate-lock svg {
    width: 54px; height: 54px;
    stroke: var(--coral); fill: none;
    stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
    animation: rotate-hint 2.2s ease-in-out infinite;
  }
  #rotate-lock p {
    font-family: var(--font-display);
    font-size: 1.15rem; font-weight: 600; color: var(--ink);
  }
  #rotate-lock span { font-size: 0.85rem; font-weight: 600; color: var(--muted); }
}
@keyframes rotate-hint {
  0%, 20%       { transform: rotate(0deg); }
  45%, 75%      { transform: rotate(90deg); }
  95%, 100%     { transform: rotate(0deg); }
}

/* =========================================================================
   Preferences page — theme picker cards + iOS-style switches. House motion
   rules apply: transform/opacity only, reduced-motion respected.
========================================================================= */

.prefs-wrap {
  width: 100%; max-width: 480px;
  display: flex; flex-direction: column; gap: 10px;
}
.prefs-group-title {
  margin-top: 10px;
  font-size: 0.72rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink); opacity: 0.55;
}
.pref-card {
  background: rgba(255, 255, 255, 0.72);
  border: 2px solid rgba(255, 255, 255, 0.85);
  border-radius: var(--r-card-sm, 16px);
  box-shadow: 0 4px 14px rgba(46, 26, 53, 0.10);
}
.pref-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 14px 16px;
}
.pref-row + .pref-row { border-top: 1px solid rgba(46, 26, 53, 0.08); }
.pref-text { min-width: 0; }
.pref-label { font-weight: 700; color: var(--ink); font-size: 0.95rem; }
.pref-sub { font-size: 0.75rem; color: var(--ink); opacity: 0.55; margin-top: 2px; }

/* on/off switch — track colours flip, the knob slides via transform */
.pref-switch {
  flex: none; position: relative;
  width: 48px; height: 28px;
  border: none; border-radius: 14px;
  background: rgba(46, 26, 53, 0.22);
  cursor: pointer; padding: 0;
  transition: background 0.18s;
}
.pref-switch::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 22px; height: 22px; border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(46, 26, 53, 0.3);
  transition: transform 0.18s;
}
.pref-switch.on { background: var(--teal); }
.pref-switch.on::after { transform: translateX(20px); }
.pref-switch:focus-visible { outline: 3px solid var(--coral); outline-offset: 2px; }

/* theme cards — the swatch IS the theme's real ground gradient */
.pref-themes { display: flex; gap: 10px; }
.pref-theme {
  flex: 1;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 10px 8px 12px;
  border: 2px solid rgba(255, 255, 255, 0.85);
  border-radius: var(--r-card-sm, 16px);
  background: rgba(255, 255, 255, 0.72);
  box-shadow: 0 4px 14px rgba(46, 26, 53, 0.10);
  cursor: pointer;
  transition: transform 0.12s, border-color 0.18s;
}
.pref-theme:active { transform: scale(0.96); }
.pref-theme-swatch {
  width: 100%; height: 64px; border-radius: var(--r-inner, 10px);
  border: 1px solid rgba(46, 26, 53, 0.10);
}
.pref-theme-name { font-weight: 700; font-size: 0.85rem; color: var(--ink); }
.pref-theme.active { border-color: var(--teal); }
.pref-theme.active .pref-theme-name::after {
  content: " ✓"; color: var(--teal);
}

.pref-signout {
  flex: none;
  padding: 8px 14px;
  border: 2px solid var(--coral); border-radius: 12px;
  background: none; color: var(--coral);
  font-weight: 700; font-size: 0.82rem;
  cursor: pointer;
  transition: transform 0.12s;
}
.pref-signout:active { transform: scale(0.95); }

/* --- Install prompts (A2HS): in-app-browser coach + guided sheet --------- */
/* The coach banner sits under the app bar: in-app browsers (Instagram,
   FB, TikTok) break Google OAuth and have no Add to Home Screen, so the
   FIRST job is getting the user to their real browser. */
#inapp-banner {
  position: fixed;
  top: calc(var(--appbar-h) + env(safe-area-inset-top, 0px));
  left: 0; right: 0; z-index: 150;
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px 9px 14px;
  background: rgba(255, 251, 242, 0.94);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid rgba(46, 26, 53, 0.12);
  font-size: 0.8rem; line-height: 1.35; color: var(--ink);
  animation: inapp-banner-in 0.3s ease;
}
@keyframes inapp-banner-in { from { transform: translateY(-100%); } to { transform: translateY(0); } }
#inapp-banner-close {
  flex: none; width: 30px; height: 30px;
  border: none; border-radius: 50%;
  background: rgba(46, 26, 53, 0.08); color: var(--muted);
  font-size: 1.2rem; line-height: 1; cursor: pointer;
}
.install-benefit { color: #9B8391; font-size: 0.9rem; margin: 0 0 14px; }
.install-step {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; margin-bottom: 8px;
  background: #FFF6E9; border-radius: var(--r-inner, 12px);
  font-size: 0.92rem; color: #2E1A35;
}
.install-step svg {
  flex: none; width: 22px; height: 22px;
  stroke: var(--coral); fill: none; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.install-step b { font-weight: 800; }
.install-now-btn {
  width: 100%; padding: 13px 0; margin-top: 4px;
  border: none; border-radius: 14px;
  background: var(--win, #FF8A1E); color: #fff;
  font-family: var(--font-display);
  font-size: 1rem; font-weight: 700; cursor: pointer;
}
.install-later-btn {
  width: 100%; padding: 11px 0; margin-top: 8px;
  border: none; border-radius: 14px;
  background: none; color: #9B8391;
  font-size: 0.9rem; font-weight: 600; cursor: pointer;
}
@media (prefers-reduced-motion: reduce) {
  #inapp-banner { animation: none; }
}

/* --- Referrals (docs/REFERRALS.md): share button, code, welcome toast ---- */
.referral-share-btn {
  flex: none;
  padding: 10px 18px;
  border: none; border-radius: 12px;
  background: var(--win, #FF8A1E); color: #fff;
  font-weight: 800; font-size: 0.85rem;
  box-shadow: 0 3px 10px rgba(46, 26, 53, 0.18);
  cursor: pointer;
  transition: transform 0.12s;
}
.referral-share-btn:active { transform: scale(0.95); }
.referral-code-line {
  margin-top: 6px; font-size: 0.78rem; color: var(--ink); opacity: 0.75;
}
.referral-code {
  font-family: Consolas, Menlo, monospace;
  font-weight: 700; letter-spacing: 2px;
  color: var(--ink); opacity: 1;
}
.summary-share-row { margin-top: 14px; text-align: center; }
.referral-claim-row { display: flex; gap: 8px; margin-top: 8px; }
.referral-code-input {
  flex: 1; min-width: 0; max-width: 140px;
  padding: 8px 10px;
  border: 1px solid rgba(46, 26, 53, 0.22); border-radius: 10px;
  background: rgba(255, 255, 255, 0.75);
  font-family: Consolas, Menlo, monospace;
  font-weight: 700; font-size: 0.95rem; letter-spacing: 2px;
  color: var(--ink); text-transform: uppercase;
}
.referral-code-input:focus-visible { outline: 3px solid var(--coral); outline-offset: 1px; }
body.ground-dark .referral-code-input {
  background: var(--card-glass, rgba(255, 255, 255, 0.10));
  border-color: var(--line, rgba(255, 255, 255, 0.22));
}
.referral-claim-msg {
  margin-top: 6px; font-size: 0.75rem; font-weight: 600;
  color: var(--coral); min-height: 1em;
}
.referral-toast {
  position: fixed; z-index: 400;
  top: calc(env(safe-area-inset-top, 0px) + 10px);
  left: 50%; transform: translate(-50%, -160%);
  padding: 10px 18px; border-radius: 14px;
  background: var(--win, #FF8A1E); color: #fff;
  font-weight: 800; font-size: 0.9rem;
  box-shadow: 0 6px 20px rgba(46, 26, 53, 0.35);
  transition: transform 0.35s cubic-bezier(0.2, 0.9, 0.3, 1.2);
  pointer-events: none;
}
.referral-toast.show { transform: translate(-50%, 0); }

@media (prefers-reduced-motion: reduce) {
  .pref-switch, .pref-switch::after, .pref-theme, .pref-signout,
  .referral-share-btn, .referral-toast { transition: none; }
}

/* --- Notification prefs: game rows, expand chevron, day pills ------------ */

.pref-notif-body.disabled { opacity: 0.45; pointer-events: none; }
.pref-mini-title {
  padding: 12px 16px 0;
  font-size: 0.66rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink); opacity: 0.45;
}
.pref-notif-game { padding-top: 10px; padding-bottom: 10px; }
.pref-notif-game + .pref-day-row { border-top: none; }

.pref-expand {
  flex: none;
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 50%;
  background: rgba(46, 26, 53, 0.06);
  cursor: pointer;
  transition: transform 0.18s;
}
.pref-expand svg {
  width: 18px; height: 18px;
  fill: none; stroke: var(--ink); stroke-width: 2.4;
  stroke-linecap: round; stroke-linejoin: round;
  opacity: 0.6;
}
.pref-expand.open { transform: rotate(180deg); }

.pref-day-row {
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 0 16px 14px;
}
.pref-day-pill {
  flex: none;
  padding: 6px 11px;
  border: 2px solid rgba(46, 26, 53, 0.15);
  border-radius: 999px;
  background: none;
  font-weight: 700; font-size: 0.78rem;
  color: var(--ink); opacity: 0.6;
  cursor: pointer;
  transition: transform 0.12s;
}
.pref-day-pill:active { transform: scale(0.92); }
.pref-day-pill.on {
  border-color: var(--teal);
  background: var(--teal);
  color: #fff; opacity: 1;
}
.pref-day-pill:disabled { opacity: 0.3; cursor: default; }

@media (prefers-reduced-motion: reduce) {
  .pref-expand, .pref-day-pill { transition: none; }
}

/* =========================================================================
   DARK GROUNDS (2026-07-20) — shared mechanics for every dark theme.
   setGround() stamps body.ground-dark for themes in DARK_GROUNDS
   (app.js); each dark theme block then only supplies its palette:
     --card-solid  solid card fill (nav/reveal cards)
     --card-glass  translucent card fill (pref cards, Simple Results)
     --haze        chrome glass (appbar + docked tiles — the white haze
                   reads too LIGHT over mid-tone dark grounds)
     --bar         tab bar fill
     --hl / --hl-soft   the theme's highlight accent + border alpha
   plus the usual ground/ink/shell/radius vars. Ink is ivory in all dark
   themes; coral stays the action colour, teal stays success.
========================================================================= */

body.ground-dark .nav-card,
body.ground-dark .reveal-card {
  background-color: var(--card-solid);
  /* the candy-gloss white sheen (0.75-0.8 alpha) washes dark cards into
     glossy grey — keep the glass feel at a whisper */
  background-image: linear-gradient(170deg,
    rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0.02) 55%);
}
body.ground-dark .pref-card,
body.ground-dark .pref-theme {
  background: var(--card-glass);
  border-color: rgba(245, 241, 230, 0.16);
}
body.ground-dark .pref-switch { background: rgba(245, 241, 230, 0.28); }
body.ground-dark .pref-switch.on { background: var(--teal); }
body.ground-dark .pref-expand { background: rgba(245, 241, 230, 0.12); }
body.ground-dark .pref-day-pill { border-color: rgba(245, 241, 230, 0.35); }
body.ground-dark .pref-day-pill.on { border-color: var(--teal); }
body.ground-dark .pref-theme-swatch { border-color: rgba(245, 241, 230, 0.2); }

body.ground-dark #appbar { background: var(--haze); }
body.ground-dark #ctx-ticket-chip,
body.ground-dark #ctx-draw-chip {
  background-color: var(--haze);
  border-color: var(--hl-soft);
  border-bottom: none;
}
body.ground-dark #ctx-ticket-chip {
  border-color: var(--game-accent, var(--hl-soft));
}
body.ground-dark .tab-pill::before {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.03) 46%,
    rgba(255, 255, 255, 0) 54%, rgba(255, 255, 255, 0.05));
}
body.ground-dark #tabbar,
body.ground-dark #tabbar::after {
  background: var(--bar);
  border-top-color: var(--hl-soft);
}
body.ground-dark .tab { color: rgba(245, 241, 230, 0.55); }
body.ground-dark .tab.active { color: var(--hl); }

body.ground-dark .pref-theme.active { border-color: var(--hl); }
body.ground-dark .pref-theme.active .pref-theme-name::after { color: var(--hl); }

/* Simple Results / summary: hard-coded warm whites get dark equivalents;
   unmatched beige balls stay — light chips read fine on dark cards */
body.ground-dark .sr-card {
  background: var(--card-glass);
  border-color: rgba(245, 241, 230, 0.16);
}
body.ground-dark .summary-ball.sr-slot {
  background: rgba(245, 241, 230, 0.10);
  box-shadow: inset 0 0 0 1.5px rgba(245, 241, 230, 0.22);
}
body.ground-dark .summary-ball.drawn {
  background: #F5F1E6;
  color: #2E1A35;
}

/* favourites star circle: the white haze is too light on dark cards */
body.ground-dark .reveal-fav {
  background: rgba(10, 13, 22, 0.5);
  color: rgba(245, 241, 230, 0.45);
}

/* =========================================================================
   ROYAL — sapphire, amethyst, disciplined gold, ivory (Jamie's palette).
   Solid amethyst ground, dark NAVY cards (midnight proper reads black),
   gold on key touchpoints only, sharper card corners (9/7/5px).
========================================================================= */

:root {
  --royal: linear-gradient(180deg, #4B2E83 0%, #4B2E83 100%);
}

body[data-ground="royal"] {
  --font-display: "Outfit", "Nunito", sans-serif;
  --font-body: "Outfit", "Nunito", sans-serif;
  --body-tracking: 0.012em;
  --ground: var(--royal);
  --ground-top: #4B2E83;
  --hud-text: #F5F1E6;
  --hud-text-sub: rgba(245, 241, 230, 0.8);
  --hud-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
  --shell: #16214A;
  --shell-2: #1F2C5E;
  --ink: #F5F1E6;
  --muted: rgba(245, 241, 230, 0.62);
  --faint: rgba(245, 241, 230, 0.38);
  --line: rgba(245, 241, 230, 0.16);
  --amber-text: #C9A227;
  --card-solid: #16214A;
  --card-glass: rgba(22, 33, 74, 0.93);
  --haze: rgba(14, 21, 48, 0.55);
  --bar: #101838;
  --hl: #C9A227;
  --hl-soft: rgba(201, 162, 39, 0.4);
  /* win rows: the standard win orange over the dark card */
  --win-bg: linear-gradient(180deg, rgba(255, 138, 30, 0.28), rgba(255, 138, 30, 0.12));
  --win-border: rgba(255, 138, 30, 0.6);
  --win-shadow: 0 3px 12px rgba(0, 0, 0, 0.35);
  --win-shine: rgba(255, 190, 120, 0.28);
  --win-result: #FFA149;
  --r-card: 9px;
  --r-card-sm: 7px;
  --r-inner: 5px;
  color: #F5F1E6;
}

/* =========================================================================
   EMERALD — the casino table: deep felt green, near-black green cards,
   brass on the touchpoints. The second dark theme; rides the shared
   body.ground-dark mechanics above.
========================================================================= */

:root {
  --emerald: linear-gradient(180deg, #07271E 0%, #0E4A39 100%);
}

body[data-ground="emerald"] {
  --font-display: "Outfit", "Nunito", sans-serif;
  --font-body: "Outfit", "Nunito", sans-serif;
  --body-tracking: 0.012em;
  --ground: var(--emerald);
  --ground-top: #07271E;
  --hud-text: #F5F1E6;
  --hud-text-sub: rgba(245, 241, 230, 0.8);
  --hud-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
  --shell: #0C2C23;
  --shell-2: #12402F;
  --ink: #F5F1E6;
  --muted: rgba(245, 241, 230, 0.62);
  --faint: rgba(245, 241, 230, 0.38);
  --line: rgba(245, 241, 230, 0.16);
  --amber-text: #CFA548;
  --card-solid: #0C2C23;
  --card-glass: rgba(12, 44, 35, 0.93);
  --haze: rgba(7, 30, 24, 0.55);
  --bar: #071E17;
  --hl: #CFA548;
  --hl-soft: rgba(207, 165, 72, 0.4);
  /* win rows: the standard win orange over the felt */
  --win-bg: linear-gradient(180deg, rgba(255, 138, 30, 0.28), rgba(255, 138, 30, 0.12));
  --win-border: rgba(255, 138, 30, 0.6);
  --win-shadow: 0 3px 12px rgba(0, 0, 0, 0.35);
  --win-shine: rgba(255, 190, 120, 0.28);
  --win-result: #FFA149;
  color: #F5F1E6;
}

/* =========================================================================
   CANDY POP — fairy-floss pink through lilac into sky: the FUN theme.
   Light ground, standard light mechanics; only the gradient + HUD ink.
========================================================================= */

:root {
  --candy: linear-gradient(180deg,
    #FF9FC7 0%, #EFA6DE 40%, #C3B2F0 70%, #9FC6FF 100%);
}

body[data-ground="candy"] {
  --ground: var(--candy);
  --ground-top: #FF9FC7;
  --hud-text: #4A2440;
  --hud-text-sub: rgba(74, 36, 64, 0.7);
  --hud-shadow: 0 1px 3px rgba(255, 255, 255, 0.5);
}

/* =========================================================================
   CHAMPAGNE — ivory falling into soft gold: quiet luxury, the premium
   light theme. Slightly crisper corners (12/10/6px) for the stationery
   feel — between the playful default and Royal's sharpness.
========================================================================= */

:root {
  --champagne: linear-gradient(180deg,
    #FBF6EC 0%, #F3E3C2 55%, #E8C98F 100%);
}

body[data-ground="champagne"] {
  --ground: var(--champagne);
  --ground-top: #FBF6EC;
  --hud-text: #5A4632;
  --hud-text-sub: rgba(90, 70, 50, 0.7);
  --hud-shadow: 0 1px 3px rgba(255, 255, 255, 0.6);
  --r-card: 12px;
  --r-card-sm: 10px;
  --r-inner: 6px;
}

/* six themes now — the picker wraps to a 3-per-row grid */
.pref-themes {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}
.pref-theme-swatch { height: 56px; }

/* =========================================================================
   CANDY POP gloss pass (2026-07-21) — the theme leans into the app
   icon's look: hard top highlights, jelly bevels, glossy spheres.
   Static gradients only — zero per-frame cost, house motion rules
   untouched.
========================================================================= */

body[data-ground="candy"] .card,
body[data-ground="candy"] .ticket-card,
body[data-ground="candy"] .pref-card,
body[data-ground="candy"] .pref-theme,
body[data-ground="candy"] .sr-card,
body[data-ground="candy"] .game-result,
body[data-ground="candy"] .draw-menu {
  background-image: linear-gradient(172deg,
    rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.30) 34%,
    rgba(255, 255, 255, 0) 55%, rgba(255, 255, 255, 0.22) 94%);
  box-shadow: inset 0 1.5px 0 rgba(255, 255, 255, 0.95),
              inset 0 -5px 8px rgba(160, 80, 150, 0.10),
              0 8px 22px rgba(150, 70, 140, 0.18);
}
body[data-ground="candy"] .nav-card,
body[data-ground="candy"] .reveal-card {
  background-image: linear-gradient(172deg,
    rgba(255, 255, 255, 0.98), rgba(255, 255, 255, 0.40) 36%,
    rgba(255, 255, 255, 0.05) 58%, rgba(255, 255, 255, 0.30) 95%);
  box-shadow: inset 0 1.5px 0 rgba(255, 255, 255, 0.95),
              inset 0 -6px 10px rgba(160, 80, 150, 0.12),
              0 10px 26px rgba(150, 70, 140, 0.20);
}

/* balls & jewels: the icon's glossy-sphere treatment (radial hotspot
   high-left, shaded base) — rides OVER each ball's own colour, so win
   orange, teal and game accents all read as candy */
body[data-ground="candy"] .summary-ball,
body[data-ground="candy"] .game-badge,
body[data-ground="candy"] .tab-pill-badge {
  background-image: radial-gradient(circle at 32% 26%,
    rgba(255, 255, 255, 0.90), rgba(255, 255, 255, 0.25) 34%,
    rgba(255, 255, 255, 0) 55%),
    linear-gradient(rgba(255, 255, 255, 0.10), rgba(0, 0, 0, 0.10));
  box-shadow: inset 0 -2px 3px rgba(0, 0, 0, 0.15);
}
body[data-ground="candy"] .number-chip {
  background-image: radial-gradient(circle at 32% 26%,
    rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0.20) 36%,
    rgba(255, 255, 255, 0) 56%);
  box-shadow: inset 0 -4px 6px rgba(0, 0, 0, 0.25),
              0 3px 7px rgba(46, 26, 53, 0.20);
}

/* buttons: jelly press */
body[data-ground="candy"] .btn-primary,
body[data-ground="candy"] .tab-scan-circle {
  background-image: linear-gradient(rgba(255, 255, 255, 0.50),
    rgba(255, 255, 255, 0.08) 48%, rgba(0, 0, 0, 0.05) 100%);
  box-shadow: inset 0 -4px 0 rgba(0, 0, 0, 0.16),
              0 5px 14px rgba(150, 70, 140, 0.25);
}
body[data-ground="candy"] .pref-switch.on {
  background-image: linear-gradient(rgba(255, 255, 255, 0.45),
    rgba(255, 255, 255, 0) 60%);
}

/* frosted chrome: a sharper glass edge */
body[data-ground="candy"] #appbar,
body[data-ground="candy"] #ctx-ticket-chip,
body[data-ground="candy"] #ctx-draw-chip {
  box-shadow: inset 0 1.5px 0 rgba(255, 255, 255, 0.90),
              0 -4px 14px rgba(120, 50, 120, 0.14);
}

/* tiny third line on the docked tiles: games count / "latest" flag */
.tab-pill-sub {
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--muted);
  line-height: 1.2;
  min-height: 0.75rem;
}

/* =========================================================================
   Reveal-select CAROUSEL view (2026-07-22) — one near-full-screen card
   per reveal, native scroll-snap swiping (browser momentum, no JS
   physics). The grid view above it is untouched and stays the default.
========================================================================= */

#reveal-carousel {
  display: flex;
  gap: 14px;
  /* FULL-BLEED: escape the page's 16px side padding so slides run to
     the screen edge instead of clipping in an invisible frame (Jamie
     2026-07-22), and vertical padding INSIDE the scroll container so
     the scroller's y-clip can't shave corners and shadows */
  width: calc(100% + 32px);
  margin: auto -16px 0;   /* margin-top:auto sinks the block to close the
                             dead gap above the docked tiles */
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  padding: 12px 7% 26px;
  scrollbar-width: none;
}
#reveal-carousel::-webkit-scrollbar { display: none; }

.reveal-slide {
  position: relative;
  flex: 0 0 86%;
  scroll-snap-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 12px;
  padding: 28px 22px;
  height: max(400px, calc(100dvh - var(--appbar-h) - var(--tabbar-h) - 236px));
  border-radius: var(--r-card, 22px);
  background-color: var(--shell);
  background-image: linear-gradient(170deg, rgba(255,255,255,0.8), rgba(255,255,255,0.2) 55%);
  box-shadow: inset 0 -4px 0 rgba(46,26,53,0.06), 0 10px 26px rgba(46, 26, 53, 0.14);
  cursor: pointer;
  overflow: hidden;
}
.reveal-slide:active { transform: scale(0.985); }
/* same candy rotation as the grid cards */
.reveal-slide:nth-child(5n+1) { background-color: #FFE9ED; }
.reveal-slide:nth-child(5n+2) { background-color: #E4F6EF; }
.reveal-slide:nth-child(5n+3) { background-color: #EFE7FB; }
.reveal-slide:nth-child(5n+4) { background-color: #E3F2FB; }
.reveal-slide:nth-child(5n+5) { background-color: #FFF3DC; }
.reveal-slide.locked { cursor: default; opacity: 0.7; background-color: var(--shell); }
.reveal-slide .reveal-fav { top: 14px; left: 14px; }

.reveal-slide-icon {
  width: 112px; height: 112px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  color: #fff;
  background-image: linear-gradient(rgba(255,255,255,0.35), rgba(255,255,255,0) 55%);
  box-shadow: inset 0 -5px 0 rgba(0,0,0,0.12), 0 6px 16px rgba(46,26,53,0.18);
  background-color: var(--coral);
}
.reveal-slide:nth-child(5n+1) .reveal-slide-icon { background-color: var(--coral); }
.reveal-slide:nth-child(5n+2) .reveal-slide-icon { background-color: var(--teal); }
.reveal-slide:nth-child(5n+3) .reveal-slide-icon { background-color: #8E5BC7; }
.reveal-slide:nth-child(5n+4) .reveal-slide-icon { background-color: #4FA8E0; }
.reveal-slide:nth-child(5n+5) .reveal-slide-icon { background-color: var(--gold); }
.reveal-slide-icon svg {
  width: 58px; height: 58px;
  stroke: currentColor; fill: none;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.reveal-slide-name {
  font-family: var(--font-display);
  font-size: 1.5rem; font-weight: 600;
  color: var(--ink);
}
.reveal-slide-desc {
  font-size: 0.98rem; line-height: 1.5;
  color: var(--muted); font-weight: 600;
  max-width: 30ch;
}
.reveal-slide-cta {
  margin-top: 4px;
  font-size: 0.72rem; font-weight: 800;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--coral);
}

#reveal-carousel-dots {
  display: flex;
  justify-content: center;
  gap: 7px;
  padding: 0 0 2px;
  margin-top: -14px;   /* tucks under the scroller's shadow padding */
}
.reveal-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--ink);
  opacity: 0.22;
}
.reveal-dot.active { background: var(--coral); opacity: 1; }

/* dark grounds: slides join the dark-card language (pastels replaced) */
body.ground-dark .reveal-slide {
  background-color: var(--card-solid);
  background-image: linear-gradient(170deg,
    rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0.02) 55%);
}
body.ground-dark .reveal-slide.locked { background-color: var(--card-solid); }
body.ground-dark .reveal-dot { background: #F5F1E6; }
body.ground-dark .reveal-dot.active { background: var(--hl); }

/* candy: the gloss pass reaches the slides too */
body[data-ground="candy"] .reveal-slide {
  background-image: linear-gradient(172deg,
    rgba(255, 255, 255, 0.98), rgba(255, 255, 255, 0.40) 36%,
    rgba(255, 255, 255, 0.05) 58%, rgba(255, 255, 255, 0.30) 95%);
  box-shadow: inset 0 1.5px 0 rgba(255, 255, 255, 0.95),
              inset 0 -6px 10px rgba(160, 80, 150, 0.12),
              0 10px 26px rgba(150, 70, 140, 0.20);
}

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

/* Reveal filters as a SLIDE-DOWN PANEL from the app bar (2026-07-22):
   overlays the page instead of pushing it down. Opens on the funnel,
   a chip tap applies the filter and the panel slides home half a
   second later (app.js). */
#reveal-filter-chips {
  position: fixed;
  top: var(--appbar-h);
  left: 50%;
  width: min(100vw, 430px);
  margin: 0;
  padding: 14px 16px 16px;
  /* UNDER the app bar (2026-07-22): at z-120 the panel slid up IN FRONT
     of the bar — the "snaps to the very top" glitch. Below the bar's
     100 it exits behind the frosted glass instead. */
  z-index: 95;
  /* the app bar's frost recipe — neutral haze, colour from the backdrop
     (cream fought the brighter grounds) */
  background: rgba(255, 255, 255, 0.30);
  -webkit-backdrop-filter: blur(18px) saturate(1.5);
  backdrop-filter: blur(18px) saturate(1.5);
  border-bottom: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 0 0 18px 18px;
  box-shadow: 0 14px 28px rgba(46, 26, 53, 0.20);
  transform: translate(-50%, -110%);
  visibility: hidden;
  transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1),
              visibility 0s linear 0.25s;
}
#reveal-filter-chips.open {
  transform: translate(-50%, 0);
  visibility: visible;
  transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1);
}
@media (prefers-reduced-motion: reduce) {
  #reveal-filter-chips { transition: none; }
}

/* dark grounds: the panel wears the dark frost like the app bar */
body.ground-dark #reveal-filter-chips { background: var(--haze); }

/* the HUD's draw-label line retired 2026-07-22 — the glowing draw tile
   states the draw now (element kept for the code that writes it) */
#reveal-draw-label { display: none; }

/* Tile backdrop art (2026-07-23): Jamie's ghosted ticket illustration —
   alpha pre-baked to ~26% at install (master in Photoshop/UI stays
   full-strength). Appended late so it wins the shorthand background
   resets above. */
#ctx-ticket-chip {
  background-image: url("/img/Ticket_Tile.webp?v=5");
  background-repeat: no-repeat;
  /* px-exact framing matched to Jamie's mock (2026-07-23): the drawn
     ticket sits ~15px in from the left, top edge ~16px down, ~123px
     wide, lower half cropped by the tile */
  background-position: -62px -7px;
  background-size: 290px auto;
}
#ctx-draw-chip {
  background-image: url("/img/Draw_Tile.webp?v=4");
  background-repeat: no-repeat;
  /* px-exact per the mock: sphere centred behind the badge, top just
     under the tile edge, base cropped */
  background-position: 11px 1px;
  background-size: 188px auto;
}
/* dark grounds get the quieter 10% bake (light themes run 30%) */
body.ground-dark #ctx-ticket-chip { background-image: url("/img/Ticket_Tile_dark.webp?v=2"); }
body.ground-dark #ctx-draw-chip { background-image: url("/img/Draw_Tile_dark.webp?v=2"); }

/* Tab anchoring (2026-07-24): Home centred at 1/4 of the bar width,
   Prefs at 3/4, Check dead-centre (Jamie). Absolute quarter-width
   slots instead of thirds-flex; height pinned to the bar's content
   box so the safe-area padding stays clear. */
#tab-home, #tab-reveal, #tab-prefs {
  position: absolute;
  top: 0;
  height: 62px;
  width: 25%;
  flex: none;
}
#tab-home   { left: 12.5%; }
#tab-reveal { left: 37.5%; }
#tab-prefs  { left: 62.5%; }

/* =========================================================================
   Check previews (2026-07-24) — a square preview of the check replaces
   the coloured ball on cards/slides when /img/checks/<id>.webp exists
   (onerror falls back to the icon, so types without assets are
   untouched). The CAROUSEL's active slide plays a short muted MP4 loop
   — one video decoding at a time, posters everywhere else.
========================================================================= */

.reveal-slide-media {
  position: relative;
  width: min(70%, 250px);
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
.reveal-slide-preview,
.reveal-slide-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 18px;
  box-shadow: 0 8px 22px rgba(46, 26, 53, 0.25);
}
.reveal-slide-video { display: none; }
.reveal-slide.live .reveal-slide-video { display: block; }
.reveal-slide-media.no-preview .reveal-slide-preview,
.reveal-slide-media.no-preview .reveal-slide-video { display: none; }
.reveal-slide-media:not(.no-preview) .reveal-slide-icon { display: none; }

.reveal-card-media {
  position: relative;
  width: 64px;
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 8px;
}
.reveal-card-preview {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 14px;
  box-shadow: 0 4px 12px rgba(46, 26, 53, 0.22);
}
.reveal-card-media.no-preview .reveal-card-preview { display: none; }
.reveal-card-media:not(.no-preview) .reveal-card-icon { display: none; }
.reveal-card-media .reveal-card-icon { margin-bottom: 0; }

/* ==========================================================================
   Email magic-link sign-in — the login page's no-Google path (2026-08-04)
   ========================================================================== */
.email-login-toggle {
  background: none; border: none; padding: 2px 6px; cursor: pointer;
  font-family: inherit; font-size: 0.85rem; font-weight: 700;
  color: var(--coral); text-decoration: underline; text-underline-offset: 3px;
}
.email-login { display: flex; flex-direction: column; gap: 10px; width: 100%; max-width: 300px; }
.email-login-input {
  width: 100%; padding: 10px 12px;
  border: 1px solid rgba(46, 26, 53, 0.22); border-radius: 10px;
  background: rgba(255, 255, 255, 0.75);
  font-family: inherit; font-size: 1rem; font-weight: 600;
  color: var(--ink); text-align: center;
}
.email-login-input:focus-visible { outline: 3px solid var(--coral); outline-offset: 1px; }
body.ground-dark .email-login-input {
  background: var(--card-glass, rgba(255, 255, 255, 0.10));
  border-color: var(--line, rgba(255, 255, 255, 0.22));
}
.email-login-status {
  margin: 0; min-height: 1em;
  font-size: 0.8rem; font-weight: 600; color: var(--muted);
}
.email-login-status.is-error { color: var(--coral); }
