:root {
  color-scheme: dark;
  --bg: #0b0b0e;
  --bg-elevated: #17171c;
  --bg-inset: #101014;
  --border: #2a2a32;
  --text: #f2f1ee;
  --text-dim: #97969f;
  /* Interactive = teal, sampled from the logo (#184860 is its single most
     common colour). The old #e2a640 gold appeared nowhere in logo-large.png —
     a pixel histogram puts it at 0% — so every button and selected state was
     built on a hue the brand doesn't contain.

     Teal needs two tokens where gold needed one. Gold was light enough to work
     as a fill AND as text on a dark ground; #2e6f8e as text on --bg-elevated
     is only 3.2:1, under the 4.5 floor. So: --accent fills (with light ink on
     top), --accent-bright is the same hue raised to 7.5:1 for text, icons and
     focus rings. Never use --accent for type. */
  --accent: #2e6f8e;
  --accent-deep: #184860;
  --accent-bright: #5fb3d4;
  --accent-text: #f4fafd;
  --accent-tint: rgba(46, 111, 142, 0.18);
  --accent-tint-strong: rgba(46, 111, 142, 0.32);
  --accent-line: rgba(95, 179, 212, 0.45);

  /* Vermilion, also straight off the logo. Deliberately rationed: it sits one
     hue-step from --danger, and red already carries "played recently, bad
     pick" on song rows. If it showed up on ordinary controls the two would
     blur, so it is reserved for things that happen rarely and deserve to
     shout — bust-outs, bingos, the win banner. */
  --celebrate: #f06030;
  --celebrate-bright: #ff9068;
  --celebrate-text: #2a1006;
  --celebrate-tint: rgba(240, 96, 48, 0.16);

  --success: #3ecf7e;
  --success-bg: rgba(62, 207, 126, 0.14);
  --danger: #ef5f5f;
  --danger-bg: rgba(239, 95, 95, 0.14);
  /* Used by the run view's Top Bust-Out tile. It was referencing var(--warning)
     with no such token defined, so the declaration was invalid and that one
     tile silently fell back to plain body text while the four tiles around it
     were coloured. */
  --warning: #f0883e;
  /* Every tier fill is a light colour, so tier badges carry their own dark ink
     rather than --accent-text (which is now near-white for use on teal). */
  --tier-ink: #14110c;
  --radius: 12px;
  --radius-sm: 8px;
}

/* Dark only, deliberately — this is meant to be used at a show. No toggle
   ever set a light theme (the old `prefers-color-scheme: light` block just
   fired automatically off a phone's OS setting, with no way back to dark),
   and the confetti/logo/accent are all tuned for dark. */

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background:
    radial-gradient(ellipse 900px 500px at 50% -10%, rgba(46, 111, 142, 0.08), transparent),
    var(--bg);
  background-attachment: fixed;
  color: var(--text);
  overflow-x: hidden;
  -webkit-tap-highlight-color: transparent;
}

#app { min-height: 100vh; display: flex; flex-direction: column; }

.wrap { width: 100%; max-width: 880px; margin: 0 auto; padding: 0 20px; }

@media (max-width: 480px) {
  .wrap { padding: 0 16px; }
}

a { color: inherit; }

button, input {
  font: inherit;
  color: inherit;
}

button {
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--bg-elevated);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 10px 16px;
  font-weight: 600;
  min-height: 40px;
  transition: transform 0.05s ease, background 0.15s ease, border-color 0.15s ease;
}
button:hover { border-color: var(--accent-bright); }
button:active { transform: scale(0.98); }
button:disabled { opacity: 0.5; cursor: not-allowed; }
/* Only inputs had a focus ring before this, so a keyboard user got no
   indication of where they were on any button in the app. */
button:focus-visible,
summary:focus-visible,
a:focus-visible {
  outline: 2px solid var(--accent-bright);
  outline-offset: 2px;
}

/* Size scale. Every small button used to be an inline override
   (style="font-size:12.5px; padding:5px 10px" and friends) because one size
   was all that existed. */
button.btn-sm { min-height: 32px; padding: 6px 12px; font-size: 13px; }
button.btn-xs { min-height: 26px; padding: 3px 9px; font-size: 12px; border-radius: 6px; }

button.primary {
  background: var(--accent);
  color: var(--accent-text);
  border-color: var(--accent);
}
button.primary:hover { filter: brightness(1.16); }

button.danger {
  background: var(--danger); color: #fff; border-color: var(--danger);
}
button.danger:hover { filter: brightness(1.1); }

button.ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-dim);
  padding: 6px 10px;
}
button.ghost:hover { color: var(--text); border-color: var(--border); }

input[type="text"], input[type="password"], input[type="date"] {
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 9px 12px;
  width: 100%;
}
input:focus { outline: 2px solid var(--accent-bright); outline-offset: -1px; }

label { display: block; font-size: 12.5px; color: var(--text-dim); margin-bottom: 5px; font-weight: 600; letter-spacing: 0.02em; text-transform: uppercase; }

.field { display: flex; flex-direction: column; gap: 4px; }
.row { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; }

.panel {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.02) inset, 0 8px 20px -12px rgba(0, 0, 0, 0.4);
}
@media (max-width: 480px) {
  .panel, .panel-muted { padding: 16px; }
}

/* Top bar */
.topbar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: calc(14px + env(safe-area-inset-top)) 0 14px; border-bottom: 1px solid var(--border);
}
.brand { display: flex; align-items: center; gap: 8px; min-width: 0; }
.brand-logo { height: 42px; width: 134px; object-fit: cover; object-position: center; display: block; }
.brand .name { font-size: 19px; font-weight: 800; letter-spacing: -0.01em; white-space: nowrap; }
.brand .tag { font-size: 12px; color: var(--text-dim); white-space: nowrap; }

@media (max-width: 480px) {
  .brand-logo { height: 33px; width: 105px; }
}
.who { display: flex; align-items: center; gap: 4px; font-size: 14px; color: var(--text-dim); flex-shrink: 0; }
.who button { padding: 8px 12px; }

@media (max-width: 480px) {
  .brand .tag { display: none; }
  .brand .name { font-size: 17px; }
  .who button { padding: 8px 10px; font-size: 13px; }
}

main { flex: 1; padding: 16px 0 60px; }
main > *:first-child { margin-top: 12px; }

.toast {
  position: fixed; top: 16px; left: 50%; transform: translateX(-50%);
  background: #2a1515; border: 1.5px solid var(--danger); color: #ff8080;
  padding: 12px 20px; border-radius: var(--radius-sm); font-size: 14px; font-weight: 700;
  z-index: 50; max-width: 90vw; box-shadow: 0 10px 30px rgba(0,0,0,0.8);
}
/* Success and neutral variants. Every transient message in the app came
   through the error style above, so scoring a show, inviting someone, and
   reading a badge's description all announced themselves in alarm red. */
.toast-success { background: #10241a; border-color: var(--success); color: #7fe3a9; }
.toast-info { background: #0e1d25; border-color: var(--accent); color: var(--accent-bright); }

/* Modal (replaces native prompt/confirm) */
.modal-overlay {
  position: fixed; inset: 0; background: rgba(0, 0, 0, 0.75);
  display: flex; align-items: flex-start; justify-content: center; z-index: 200;
  padding: 24px 16px; overflow-y: auto; animation: modal-fade 0.15s ease;
}
@keyframes modal-fade { from { opacity: 0; } to { opacity: 1; } }
.modal {
  margin-top: 6vh;
  background: var(--bg-elevated); border: 1.5px solid var(--accent); border-radius: var(--radius);
  padding: 22px; width: 360px; max-width: 100%;
  box-shadow: 0 20px 50px -12px rgba(0, 0, 0, 0.9);
  animation: modal-pop 0.15s ease;
}
@keyframes modal-pop { from { transform: scale(0.96); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.modal h3 { margin: 0; font-size: 17px; }
.modal-message { color: var(--text-dim); font-size: 13.5px; margin: 8px 0 0; line-height: 1.5; }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }

/* Song detail — the ⓘ on a search row, or tapping a filled square.
   The three facts used to be bold-label sentences ("⏳ Last Played: 91 shows
   ago"), which read as prose when they're really a stat block. Same idea as
   .stat-grid on the dashboard, at the size a modal can afford. */
.song-detail-tier { display: flex; align-items: center; gap: 9px; margin-top: 12px; font-size: 14px; font-weight: 700; color: var(--text); }
.song-detail-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; margin-top: 12px; }
.song-detail-stat {
  background: var(--bg-inset); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 9px 11px; min-width: 0;
}
.song-detail-stat .v {
  font-size: 17px; font-weight: 800; color: var(--text);
  font-variant-numeric: tabular-nums; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.song-detail-stat .v small { font-size: 11px; font-weight: 600; color: var(--text-dim); }
/* Same red "just played, bad bingo pick" signal the search row uses. */
.song-detail-stat .v.recent, .song-detail-stat .v.recent small { color: var(--danger); }
.song-detail-stat .l {
  color: var(--text-dim); font-size: 10.5px; margin-top: 3px;
  text-transform: uppercase; letter-spacing: 0.04em; font-weight: 700;
}
.song-detail-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 12px; }
/* Same tag, same casing as the filter chip that selects it. .badge's
   uppercase meant "Type II Jam Vehicle" and "TYPE II JAM VEHICLE" could name
   the same thing three inches apart on one screen. */
.song-detail-tags .pill { font-size: 11.5px; }
.song-detail-note { margin: 14px 0 0; font-size: 12px; color: var(--text-dim); line-height: 1.5; }
.song-detail-note a { color: var(--accent-bright); }
.song-detail-msg { display: block; margin-top: 12px; font-size: 13.5px; color: var(--text-dim); line-height: 1.5; }

.whats-new-list { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.whats-new-list li { font-size: 13.5px; color: var(--text-dim); line-height: 1.45; }
.whats-new-list strong { color: var(--text); }

/* Auth */
.auth-shell { display: flex; align-items: center; justify-content: center; min-height: 100vh; }
.auth-card { width: 360px; max-width: 90vw; }
.auth-logo { width: 100%; max-width: 320px; height: 107px; object-fit: cover; object-position: center; display: block; margin: 0 auto 10px; border-radius: 10px; }
.auth-card .tag { color: var(--text-dim); font-size: 13px; margin: 0 0 22px; text-align: center; }

.how-it-works {
  list-style: none; margin: 0 0 20px; padding: 14px 16px;
  background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--radius-sm);
  display: flex; flex-direction: column; gap: 8px;
  font-size: 13px; color: var(--text-dim);
}
.tabs { display: flex; gap: 4px; margin-bottom: 18px; background: var(--bg-inset); border-radius: var(--radius-sm); padding: 3px; }
.tabs button { flex: 1; background: transparent; border: none; padding: 7px; }
.tabs button.active { background: var(--bg-elevated); border: 1px solid var(--border); }

/* Dashboard */
.section-title { display: flex; align-items: baseline; justify-content: space-between; margin: 0 0 14px; }
.section-title h2 {
  font-size: 13.5px; margin: 0; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-dim);
  display: flex; align-items: center; gap: 7px; font-weight: 700;
}

.run-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; margin-bottom: 28px; }
.run-tile {
  background: var(--bg-elevated); border: 1px solid var(--border); border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  padding: 16px; text-align: left; cursor: pointer; font-weight: 600;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.run-tile:hover { border-color: var(--accent-bright); border-left-color: var(--accent-bright); }
.run-tile .meta { font-weight: 400; color: var(--text-dim); font-size: 12.5px; margin-top: 6px; }
.run-tile .chevron { color: var(--accent-bright); font-weight: 400; font-size: 18px; flex-shrink: 0; }

.past-runs { margin-bottom: 28px; }
.past-runs summary {
  cursor: pointer; font-size: 13.5px; font-weight: 600; color: var(--text-dim);
  padding: 8px 0; list-style: none;
}
.past-runs summary::-webkit-details-marker { display: none; }
.past-runs summary::before { content: '▸ '; }
.past-runs[open] summary::before { content: '▾ '; }
.past-runs .run-grid { margin-top: 10px; margin-bottom: 0; opacity: 0.75; }

/* Secondary/setup panels ("create a run") — deliberately flatter than
   .run-tile so the actual runs (the primary, clickable content) stand out
   as distinct from these one-time setup actions, not just more of the same. */
.panel-muted {
  background: transparent; border: 1px dashed var(--border); border-radius: var(--radius);
  padding: 20px;
}

.new-run-form { display: flex; gap: 8px; }
.new-run-form input { flex: 1; }

/* Run view */
.back-link { display: inline-flex; align-items: center; gap: 4px; margin-bottom: 16px; }
.run-header {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px 16px;
  flex-wrap: wrap; margin-bottom: 4px;
}
.run-header h1 { margin: 0; font-size: 24px; }
.refresh-row { display: flex; align-items: center; gap: 2px; }

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; margin-bottom: 20px; }
@media (max-width: 720px) { .grid-2 { grid-template-columns: 1fr; } }

.chat-messages {
  max-height: 320px; overflow-y: auto;
  display: flex; flex-direction: column; gap: 8px;
  padding-right: 4px;
}
.chat-message { font-size: 13.5px; line-height: 1.4; }
.chat-message .chat-author { font-weight: 700; margin-right: 6px; }
.chat-message.mine .chat-author { color: var(--accent-bright); }
.chat-message .chat-text { overflow-wrap: break-word; }
.chat-text .mention { color: var(--accent-bright); font-weight: 600; }
.chat-message-system {
  text-align: center; color: var(--text-dim); font-size: 12.5px; font-style: italic;
  padding: 4px 0;
}

.mention-badge {
  font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 20px;
  background: var(--danger-bg); color: var(--danger);
}

.show-item-wrap { padding: 8px 0; border-bottom: 1px solid var(--border); }
.show-item-wrap:last-child { border-bottom: none; }
.show-item {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-size: 14px;
}
.show-item .info strong { display: block; }
.show-item .venue { color: var(--text-dim); font-size: 12.5px; }
.show-item .status { font-size: 11.5px; color: var(--text-dim); padding: 3px 8px; border-radius: 20px; background: var(--bg-inset); display: inline-block; margin-top: 4px; }
.show-item .status.scored { color: var(--success); background: var(--success-bg); }

/* Already-scored shows condense to one line — the fuller layout above is
   reserved for shows still needing attention (not yet scored). */
.show-item-compact { font-size: 13px; }
.show-item-compact .info-compact { color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.show-item-compact .status { margin-top: 0; }

/* Collapsible "In the Lot" panel — <details> styled to sit flush inside
   the existing .panel padding instead of adding its own. */
.lot-panel summary {
  cursor: pointer; list-style: none; font-size: 16px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-dim);
}
.lot-panel summary::-webkit-details-marker { display: none; }
.lot-panel summary::before { content: '▸ '; color: var(--accent-bright); }
.lot-panel[open] summary::before { content: '▾ '; }

.setlist-toggle { margin-top: 8px; padding: 4px 0; font-size: 12px; }
.setlist-panel:empty { display: none; }
.setlist-panel {
  margin-top: 8px; padding: 10px 12px;
  background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-size: 13px; line-height: 1.5; overflow-wrap: break-word;
}
.setlist-set { margin: 0 0 8px; }
.setlist-set:last-of-type { margin-bottom: 0; }
.setlist-soundcheck { margin: 8px 0 0; color: var(--text-dim); font-size: 12.5px; }
.setlist-notes {
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border);
  color: var(--text-dim); font-size: 12.5px;
}
.setlist-notes p { margin: 0 0 6px; }
.setlist-notes p:last-child { margin-bottom: 0; }

.card-tile {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 2px; border-bottom: 1px solid var(--border); cursor: pointer;
  border-radius: var(--radius-sm);
  transition: background 0.15s ease;
}
.card-tile:last-child { border-bottom: none; }
.card-tile:hover { background: var(--bg-inset); }
.card-tile .card-tile-name { font-weight: 600; display: block; }
.card-tile .card-tile-meta { display: flex; align-items: center; gap: 8px; margin-top: 5px; flex-wrap: wrap; }
.mini-progress { width: 90px; height: 10px; background: var(--bg-inset); border: 1px solid var(--border); border-radius: 20px; overflow: hidden; flex-shrink: 0; }
.mini-progress-fill { display: block; height: 100%; background: var(--success); border-radius: 20px; box-shadow: 0 0 8px 0 rgba(62, 207, 126, 0.8); }
.mini-progress-label { font-size: 11.5px; color: var(--text-dim); white-space: nowrap; }
.rank-badge {
  flex-shrink: 0; width: 28px; height: 28px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 11.5px; font-weight: 700; color: var(--text-dim);
  background: var(--bg-inset); border: 1px solid var(--border);
}
.rank-badge.trophy { font-size: 17px; background: transparent; border-color: transparent; }

/* Stats-page milestone badges — a pill plus the two things that make it this
   control. The inline style block these used to carry is what forced the old
   hover rules to use !important. */
.badge-chips-wrap { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 24px; }
.badge-chip { user-select: none; -webkit-tap-highlight-color: transparent; cursor: pointer; }
.badge-chip.locked { opacity: 0.6; }
.badge-chip.unlocked:hover { filter: brightness(1.2); }
.badge-chip.locked:hover { color: var(--text); border-color: var(--accent-line); }
.badge-chip-emoji { font-size: 14px; line-height: 1; }
.badge-chip-state { font-size: 9px; opacity: 0.8; }
.title-rank { font-size: 20px; vertical-align: middle; }
/* ── Pill ──────────────────────────────────────────────────────────────────
   One primitive on two axes: tone (what it means) and size (how loud).
   This replaced eight overlapping components — .badge plus four modifiers,
   .member-chip, .category-chip, .badge-chip — that ran at three different
   radii and disagreed on casing, height and how "selected" looked. The
   stats-page chips in particular carried ~400 characters of inline style
   each, which is why the hover rules here used to need !important to win.

   .tier-badge stays out of this on purpose: it is a bingo letter tile, not
   a label, and it is square for that reason. */
.pill {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  min-height: 24px; padding: 0 10px; border-radius: 999px;
  font-size: 11px; font-weight: 700; line-height: 1;
  text-transform: uppercase; letter-spacing: 0.03em;
  white-space: nowrap; flex-shrink: 0;
  border: 1px solid transparent;
  background: var(--bg-inset); color: var(--text-dim);
}

/* Size */
.pill--sm { min-height: 18px; padding: 0 7px; font-size: 9.5px; gap: 3px; }
.pill--lg { min-height: 32px; padding: 0 12px; font-size: 12.5px; gap: 6px; }

/* Tone */
.pill--neutral { background: var(--bg-inset); color: var(--text-dim); border-color: var(--border); }
.pill--good    { background: var(--success-bg); color: var(--success); border-color: rgba(62, 207, 126, 0.35); }
.pill--bad     { background: var(--danger-bg); color: var(--danger); border-color: rgba(239, 95, 95, 0.35); }
.pill--accent  { background: var(--accent-tint); color: var(--accent-bright); border-color: var(--accent-line); }
/* Rationed — see --celebrate. Bust-outs, bingos, wins. */
.pill--hot     { background: var(--celebrate-tint); color: var(--celebrate-bright); border-color: rgba(240, 96, 48, 0.5); }
/* The loudest thing the app can say. Named bingo shapes only. */
.pill--solid   { background: var(--celebrate); color: var(--celebrate-text); border-color: var(--celebrate); }

/* Modifiers */
/* For content that is a name, not a status — .badge's forced uppercase and
   wide tracking is right for "BINGO!" and mangles a username. */
.pill--cased { text-transform: none; letter-spacing: 0; font-weight: 600; }
.pill--avatar { padding-left: 4px; }
.pill--interactive { cursor: pointer; transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease; }
.pill--interactive:hover { border-color: var(--accent-line); color: var(--text); }
.pill--interactive:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 2px; }
/* Selected is a filled slab, the same language #song-results li.selected
   speaks, driven straight off the ARIA state so the two can't drift. */
.pill--interactive[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent); color: var(--accent-text);
}
.pill--interactive[aria-pressed="true"]:hover { filter: brightness(1.12); color: var(--accent-text); }

/* Inviting is a utility, not the main event — as a solid-accent .primary it was
   the loudest thing on the run screen, outshouting scoring and the cards. Same
   accent, carried on the icon and border instead of a filled slab. */
.share-btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 14px; font-size: 13px; font-weight: 600;
  color: var(--accent-bright); border-color: rgba(46, 111, 142, 0.45);
  background: rgba(46, 111, 142, 0.10);
}
.share-btn:hover { background: rgba(46, 111, 142, 0.18); border-color: var(--accent-bright); }

/* Members are people, not statuses — hence .pill--cased, which drops the
   uppercase that is right for "BINGO!" and mangles a username
   (YOUENJOYMYLAWN). Everything else comes from the pill primitive. */
.member-chip { color: var(--text); max-width: 100%; min-width: 0; }
.member-chip-dot {
  flex: none;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--accent); color: var(--accent-text);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700;
}

.autocomplete-list {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 10;
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  max-height: 220px;
  overflow-y: auto;
}
.autocomplete-list:empty { display: none; border: none; }
.autocomplete-list li { padding: 8px 12px; font-size: 14px; cursor: pointer; border-bottom: 1px solid var(--border); }
.autocomplete-list li:last-child { border-bottom: none; }
.autocomplete-list li:hover { background: var(--bg-inset); }

/* Sticky Header for Card View */
.sticky-card-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: rgba(11, 11, 14, 0.92);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  padding: 10px 0 12px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--border);
}

/* Bingo card */
.bingo-title { display: flex; align-items: center; justify-content: space-between; gap: 10px 16px; margin-bottom: 12px; flex-wrap: wrap; }
.bingo-title-main { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.bingo-title-actions { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.bingo-title h1 { margin: 0; font-size: 22px; }
@media (max-width: 480px) {
  .bingo-title { flex-direction: column; align-items: flex-start; }
  .bingo-title h1 { font-size: 19px; }
}
.icon-btn {
  background: transparent; border: none; padding: 6px; font-size: 15px;
  min-height: 32px; min-width: 32px;
  vertical-align: middle; opacity: 0.65; border-radius: var(--radius-sm);
}
.icon-btn:hover { opacity: 1; border-color: transparent; }

.progress-row { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
.progress-track { flex: 1; max-width: 480px; height: 8px; background: var(--bg-inset); border: 1px solid var(--border); border-radius: 20px; overflow: hidden; }
.progress-fill { height: 100%; background: var(--success); border-radius: 20px; transition: width 0.3s ease; box-shadow: 0 0 6px 0 rgba(62, 207, 126, 0.6); }
.progress-label { font-size: 12.5px; color: var(--text-dim); white-space: nowrap; }

/* Mobile Bottom Sheet for Song Picker */
.sheet-handle { display: none; }

.builder { display: flex; gap: 24px; flex-wrap: wrap; align-items: flex-start; }

.search-panel { width: 240px; flex-shrink: 0; max-width: 100%; order: 2; }
.card-face { order: 1; }

@media (min-width: 621px) {
  .search-panel { order: 1; }
  .card-face { order: 2; }
}
@media (max-width: 620px) {
  .builder { flex-direction: column; gap: 20px; }
  .card-face { max-width: 100%; min-width: 0; width: 100%; }

  .search-panel-backdrop {
    position: fixed; inset: 0; background: rgba(0, 0, 0, 0.65); z-index: 99;
    animation: modal-fade 0.2s ease;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
  }

  .search-panel {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 100;
    width: 100%; max-height: 82vh;
    background: var(--bg-elevated);
    border-top: 1.5px solid var(--accent);
    border-radius: 20px 20px 0 0;
    padding: 12px 16px 28px;
    box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.85);
    animation: sheet-slide-up 0.25s cubic-bezier(0.16, 1, 0.3, 1);
    overflow-y: auto;
    order: 3;
  }

  .sheet-handle {
    display: block;
    width: 40px; height: 5px; background: var(--border);
    border-radius: 3px; margin: 0 auto 14px; opacity: 0.8;
  }
}

@keyframes sheet-slide-up {
  from { transform: translateY(100%); }
  to { transform: translateY(0); }
}
/* The placeholder used to carry this ("Search songs — or leave blank for the
   likeliest") and was clipped mid-word at every width the panel is ever shown
   at — 240px on desktop, ~343px in the sheet. A placeholder can't wrap, so the
   half of it that mattered was the half you never saw. */
.search-caption {
  margin: 0 0 5px;
  font-size: 11px; line-height: 1.35; color: var(--text-dim);
}
#song-search { margin-bottom: 8px; }
#song-results {
  list-style: none; padding: 0; margin: 0;
  max-height: 560px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-elevated);
}
#song-results li {
  padding: 9px 12px; font-size: 13.5px; cursor: pointer;
  border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; gap: 8px; align-items: center;
}
#song-results li:last-child { border-bottom: none; }
#song-results li:hover { background: var(--bg-inset); }
#song-results li.selected { background: var(--accent); color: var(--accent-text); }
#song-results li.selected .gap { color: var(--accent-text); opacity: 0.7; }
#song-results li.selected .tier-badge { box-shadow: 0 0 0 1px var(--accent-text); }
#song-results .song-meta { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
#song-results .gap { color: var(--text-dim); font-size: 11px; white-space: nowrap; }
#song-results .recent { color: var(--danger); }
#song-results .song-abbr { color: var(--text-dim); font-weight: 400; font-size: 12px; }
.song-info-btn {
  background: none; border: none; cursor: pointer; padding: 0; margin: 0;
  color: var(--text-dim); font-size: 14px; line-height: 1; flex-shrink: 0;
}
.song-info-btn:hover, .song-info-btn:focus { color: var(--accent-bright); }

/* Category tag filter.
   Collapsed to a single line by default: all eight tags laid out at once took
   4 rows / 175px of an 812px phone screen, leaving the song list about four
   songs tall. A sideways-scrolling row got that back to one line but put the
   tags behind a swipe, which is a poor trade on touch — so the tags fold into
   a panel instead, and the line stays one line.
   The tags that are ON stay out on the collapsed row (see renderCategoryFilter)
   and are tapped there to switch off, so an active filter is never hidden and
   there's no separate clear control coming and going. */
.category-filter { margin: 8px 0; }
.category-row { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }

/* Trigger and chips are both pills; only what makes them *this* control
   lives here. Dashed border marks the trigger as a disclosure rather than a
   value you've picked. */
.category-trigger { border-style: dashed; }
.category-trigger.open { border-style: solid; border-color: var(--accent-bright); color: var(--text); }
.category-caret { font-size: 10px; transition: transform 0.15s ease; }
.category-trigger.open .category-caret { transform: rotate(180deg); }

/* The expanded picker. Wrapping is fine here — it's a state you opened on
   purpose and close again, not a permanent tax on the results. */
.category-panel {
  display: flex; flex-wrap: wrap; gap: 5px;
  margin-top: 6px; padding: 9px;
  background: var(--bg-inset); border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
/* The tagged-song count answers "is this filter worth a tap?" before you
   spend one. It used to live only in the title attribute, which a phone
   never shows. */
.category-chip-count { color: var(--text-dim); font-weight: 700; font-size: 11px; font-variant-numeric: tabular-nums; }
.category-chip[aria-pressed="true"] .category-chip-count { color: var(--accent-text); opacity: 0.75; }
/* The count helps you decide whether a tag is worth picking, so it belongs in
   the picker. Out on the collapsed row the decision is already made, and
   dropping it keeps two active tags on one line instead of two. */
.category-row .category-chip-count { display: none; }

/* Desktop runs the tags tighter. The search panel there is a 240px column, so
   at the touch size the open picker wrapped one tag per row, eight rows deep.
   The count comes off because a mouse can read it from the tooltip, which is
   the one affordance a phone doesn't have. */
@media (min-width: 621px) {
  .category-chip, .category-trigger { font-size: 11.5px; padding: 0 9px; min-height: 30px; gap: 4px; }
  .category-chip .category-chip-count { display: none; }
}

/* Reads as "N of M songs match" under the chips whenever a filter is on, so
   a filter that quietly narrows 400 songs to 3 says so instead of just
   looking like a short list. */
.filter-status { font-size: 11.5px; color: var(--accent-bright); font-weight: 600; margin: 0 0 6px; }
.filter-status:empty { display: none; }

.song-results-empty { color: var(--text-dim); cursor: default; }

/* Loading rows. These hold the list's height open while the songs are being
   fetched, so the bottom sheet reaches its final size before it finishes
   sliding up instead of jumping partway through. */
.song-skeleton { cursor: default; pointer-events: none; }
.song-skeleton .sk-bar {
  display: block; height: 11px; border-radius: 4px;
  background: var(--bg-inset);
  animation: sk-pulse 1.4s ease-in-out infinite;
}
.song-skeleton .sk-bar-meta { width: 54px; flex-shrink: 0; }
@keyframes sk-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
@media (prefers-reduced-motion: reduce) {
  .song-skeleton .sk-bar { animation: none; }
}

/* Tier ramp: hot (B, most likely) -> cold (O, longest shot). Bright fills with
   dark ink — white text on these only hit ~2:1 and was unreadable at 10px.
   Every pair below clears 6.3:1 against --tier-ink.

   Rebuilt when the accent moved to teal. Tier 3 used to be var(--accent), so
   it changed hue with the brand — and once teal became "selected", an
   accent-coloured tier badge would have read as a chosen state rather than a
   position on a scale. The ramp is now its own closed set: a warm-to-cool
   temperature sweep that deliberately borrows no semantic colour. It stays
   clear of teal (~200°) and vermilion (~16°) so nothing here can be mistaken
   for "selected" or "you won". */
.tier-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 5px;
  font-size: 10px; font-weight: 800; flex-shrink: 0;
  color: var(--tier-ink);
}
.tier-badge.tier-1 { background: #4ec98a; }
.tier-badge.tier-2 { background: #9ac95e; }
.tier-badge.tier-3 { background: #d9c26a; }
.tier-badge.tier-4 { background: #b9a08a; }
.tier-badge.tier-5 { background: #8f96a3; }

.card-face {
  flex: 1;
  min-width: 280px;
  max-width: 560px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) + 4px);
  padding: 18px;
  /* Reads as a physical object sitting above the page rather than another
     flat panel: a warm rim-light at the top edge and a deeper drop shadow
     than .panel uses, so it out-weighs the search list beside it. */
  box-shadow:
    0 1px 0 rgba(46, 111, 142, 0.10) inset,
    0 0 0 1px rgba(46, 111, 142, 0.06),
    0 18px 40px -18px rgba(0, 0, 0, 0.75);
}

.card-analysis {
  margin-top: 14px;
  padding: 14px 16px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.card-analysis-text {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--text);
}

.bingo-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 7px;
}
.letter-row {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  margin-bottom: 8px;
}
.letter {
  text-align: center; font-weight: 800; font-size: 22px; color: var(--accent-bright);
  padding-bottom: 6px; border-bottom: 2px solid var(--border);
}
.square {
  aspect-ratio: 1;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-inset);
  display: flex; align-items: center; justify-content: center;
  text-align: center; padding: 4px; line-height: 1.15;
  cursor: pointer; user-select: none; overflow-wrap: break-word; hyphens: auto;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
  /* Scales with the card instead of a fixed px size, so a 5-across grid on a
     375px phone doesn't render 11.5px text into a ~58px box. */
  font-size: clamp(9px, 2.6vw, 12.5px);
}
/* Long titles ("Also Sprach Zarathustra", "Down with Disease") wrapped into
   an unreadable stack at one fixed size — step down by name length so the
   long ones still fit the box rather than overflowing it. */
.square.len-m { font-size: clamp(8.5px, 2.3vw, 11.5px); }
.square.len-l { font-size: clamp(8px, 2.05vw, 10.5px); letter-spacing: -0.01em; }
.square.len-xl { font-size: clamp(7.5px, 1.85vw, 9.5px); letter-spacing: -0.015em; line-height: 1.1; }
.square:active { transform: scale(0.96); }
.square.empty { color: var(--text-dim); font-style: italic; }
.square.free { font-weight: 800; color: var(--accent-bright); border-color: var(--accent-bright); background: rgba(46, 111, 142, 0.08); }
.square.hit { background: var(--success-bg); border-color: var(--success); color: var(--success); font-weight: 700; }
.square.placeable { border-color: var(--accent-bright); border-style: dashed; animation: pulse-placeable 1.4s ease-in-out infinite; }
.square.picked-up { border-color: var(--accent-bright); border-style: solid; background: rgba(46, 111, 142, 0.22); }
@keyframes pulse-placeable {
  0%, 100% { background: var(--bg-inset); }
  50% { background: rgba(46, 111, 142, 0.14); }
}

@media (max-width: 480px) {
  .card-face { padding: 12px; min-width: 0; }
  .square { padding: 3px; }
  .letter { font-size: 18px; }
  .bingo-grid { gap: 5px; }
}

.bingo-banner {
  background: var(--success-bg); border: 1px solid var(--success); color: var(--success);
  padding: 6px 12px; border-radius: 20px; font-weight: 700; font-size: 13px;
}

/* Sits next to the green BINGO banner, so it reads as a separate, rarer
   achievement rather than a restatement of the same win. Vermilion, because a
   named shape is exactly the kind of rare thing --celebrate is rationed for. */
.pattern-banner {
  background: var(--celebrate-tint); border-color: var(--celebrate); color: var(--celebrate-bright);
  margin-left: 6px;
}

/* The two moments the app is allowed to shout. Both were accent-coloured, so
   they looked like every button on the page; now they're the only vermilion
   surfaces in the run view and read as events rather than furniture. */
.bustout-banner {
  background: #2a1610;
  border: 1.5px solid var(--celebrate); color: var(--celebrate-bright);
  padding: 14px 18px; border-radius: var(--radius); margin-bottom: 16px;
  font-weight: 600; font-size: 14px; line-height: 1.5;
  box-shadow: 0 8px 24px rgba(0,0,0,0.6);
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  animation: bustout-pop 0.4s ease;
}
@keyframes bustout-pop {
  0% { transform: scale(0.96); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}

/* Green is what a *square* hitting looks like, all over the app. A win is
   rarer than a hit and shouldn't be the same colour as one. */
.win-banner {
  background: #2f1409;
  border: 1.5px solid var(--celebrate); color: var(--celebrate-bright);
  padding: 16px 20px; border-radius: var(--radius); margin-bottom: 16px;
  font-weight: 700; font-size: 16px; line-height: 1.5;
  box-shadow: 0 8px 24px rgba(0,0,0,0.6);
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  animation: bustout-pop 0.4s ease;
}

.confetti-container { position: fixed; inset: 0; pointer-events: none; z-index: 200; overflow: hidden; }
.confetti-piece {
  position: absolute; top: -20px; width: 8px; height: 14px; opacity: 0.9;
  animation-name: confetti-fall; animation-timing-function: linear; animation-fill-mode: forwards;
}
@keyframes confetti-fall {
  to { transform: translateY(110vh) rotate(720deg); opacity: 0.4; }
}
/* Same pattern as .song-skeleton below — without the animation, pieces stay
   at their starting top: -20px (off-screen) instead of falling into view, so
   this is a quiet no-op rather than a burst of static color. */
@media (prefers-reduced-motion: reduce) {
  .confetti-piece { animation: none; }
}

.hint { color: var(--text-dim); font-size: 12.5px; margin: 8px 0 0; }

/* My Stats view — same variables as the rest of the app, not a separate
   palette (this used to be a standalone static page with its own light/blue
   design system; retired in favor of a live view that's actually part of
   the app). */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
  margin-bottom: 24px;
}
.stat-tile {
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 14px;
  min-width: 0; overflow: hidden;
}
.stat-value { font-size: 26px; font-weight: 800; font-variant-numeric: proportional-nums; }
.stat-label { color: var(--text-dim); font-size: 12px; margin-top: 2px; line-height: 1.35; }
.stat-value-sm {
  font-size: 13.5px; font-weight: 700; line-height: 1.3;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  display: block;
}

.chart {
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 18px 14px 10px;
  display: flex; align-items: flex-end; gap: 5px; height: 160px;
  margin-bottom: 8px;
}
.bar-col { flex: 1; height: 100%; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; }
.bar { width: 100%; max-width: 26px; background: var(--accent); border-radius: 3px 3px 0 0; min-height: 3px; }
.bar-label { margin-top: 5px; font-size: 10.5px; color: var(--text-dim); }

/* Compact 2-3-wide song grid — for lists that would otherwise run long as
   one-per-row (Most-seen songs, high-history-% songs). */
.song-chip-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px;
  margin-bottom: 8px;
}
.song-chip {
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 10px 12px; min-width: 0;
}
.song-chip-name {
  font-weight: 700; font-size: 12.5px; color: var(--accent-bright);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.song-chip-meta { display: flex; justify-content: space-between; align-items: baseline; margin-top: 4px; gap: 6px; }
.song-chip-stat { font-weight: 800; font-size: 15px; font-variant-numeric: tabular-nums; }
.song-chip-sub { font-size: 10.5px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.venue-list { margin-bottom: 8px; }
.venue-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 2fr auto;
  align-items: center; gap: 10px; padding: 6px 0; font-size: 13.5px;
}
.venue-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.venue-track { background: var(--bg-inset); border-radius: 4px; height: 9px; overflow: hidden; }
.venue-fill { height: 100%; background: var(--accent); border-radius: 4px; }
.venue-count { color: var(--text-dim); font-variant-numeric: tabular-nums; text-align: right; font-size: 12.5px; }
@media (max-width: 480px) {
  /* The 3-column grid crushes the venue name to a sliver on a phone —
     stack name above the bar+count instead of truncating it away. */
  .venue-row { grid-template-columns: 1fr auto; row-gap: 4px; }
  .venue-name { grid-column: 1 / -1; white-space: normal; }
}

footer {
  text-align: center; padding: 20px; color: var(--text-dim); font-size: 12px;
  border-top: 1px solid var(--border);
}
footer a { text-decoration: underline; }

/* ── Auth separator ─────────────────────────────────────────────────────── */
.auth-separator {
  display: flex; align-items: center; gap: 12px; margin: 16px 0; color: var(--text-dim); font-size: 13px;
}
.auth-separator::before, .auth-separator::after {
  content: ''; flex: 1; height: 1px; background: var(--border);
}

/* ── Guest badge ───────────────────────────────────────────────────────────── */
.guest-badge {
  display: inline-flex; align-items: center; gap: 4px;
  background: rgba(255, 180, 0, 0.15); color: var(--warning);
  padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600;
}
