:root {
  /* fonds */
  --bg: #1c1420;
  --bg-elev: #251a2d;
  --bg-elev2: #2e2036;

  /* texte */
  --ink: #f4ede2;
  --ink-dim: #b9a7c2;
  --ink-faint: #7d6c88;

  /* accents — neon 80s pass */
  --amber: #ff2fd4;
  --amber-dim: #7a1f66;
  --amber-wash: rgba(255, 47, 212, 0.06);
  --amber-wash-strong: rgba(255, 47, 212, 0.12);
  --amber-border: rgba(255, 47, 212, 0.4);
  --amber-glow: rgba(255, 47, 212, 0.55);
  --coral: #ff2f56;
  --teal: #00fff2;
  --teal-wash: rgba(0, 255, 242, 0.06);
  --teal-border: rgba(0, 255, 242, 0.4);
  --neon-yellow: #faff00;
  --neon-green: #39ff14;

  --line: rgba(244, 237, 226, 0.10);
  --radius: 14px;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html { -webkit-text-size-adjust: 100%; }

body {
  font-family: 'Inter', system-ui, sans-serif;
  color: var(--ink);
  margin: 0;
  background:
    radial-gradient(circle at 15% 0%, rgba(255, 47, 212, 0.16), transparent 40%),
    radial-gradient(circle at 85% 100%, rgba(0, 255, 242, 0.13), transparent 45%),
    var(--bg);
  /* Le jeu n'a aucun texte qu'on ait besoin de sélectionner/copier : sans
     ça, un double-tap un peu rapide sur un mot (fréquent en pleine partie,
     ex. le badge "TITRE"/"ARTISTE"/"ANNÉE" de .stage-tag) le sélectionne et
     Android/Chrome ouvre sa barre contextuelle "Rechercher..." par-dessus
     le jeu. Les champs de saisie restent sélectionnables (voir ci-dessous). */
  -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
}
input, textarea { -webkit-user-select: text; user-select: text; -webkit-touch-callout: default; }

.stage {
  max-width: 520px;
  margin: 0 auto;
  padding: max(36px, env(safe-area-inset-top)) max(20px, env(safe-area-inset-right)) 80px
           max(20px, env(safe-area-inset-left));
}

h1.display {
  font-family: 'Bricolage Grotesque', sans-serif;
  font-size: 34px;
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.01em;
  margin: 0 0 6px;
}

p.sub {
  font-size: 15px;
  font-weight: 400;
  color: var(--ink-dim);
  line-height: 1.5;
  margin: 0 0 24px;
}

.eyebrow {
  font-family: 'JetBrains Mono', monospace;
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  display: block;
  margin-bottom: 8px;
}

h2 {
  font-family: 'Bricolage Grotesque', sans-serif;
  font-size: 22px;
  font-weight: 700;
  margin: 0 0 12px;
}

h3 {
  font-family: 'Bricolage Grotesque', sans-serif;
  font-size: 16px;
  font-weight: 700;
  margin: 20px 0 10px;
}

/* buttons */
.btn {
  font-family: 'Inter', sans-serif;
  font-size: 16px;
  border-radius: 10px;
  padding: 14px 20px;
  cursor: pointer;
  min-height: 48px;
  /* display/text-align/text-decoration here (not just on <button>, which
     gets inline-block for free) so an <a class="btn ..."> — e.g. #showSoloBtn
     linking to /solo/ — renders identically: width:100% below only takes
     effect on a non-inline box. */
  display: inline-block;
  text-align: center;
  text-decoration: none;
  box-sizing: border-box;
}
.btn:active { transform: scale(0.98); }
.btn:disabled { opacity: .45; cursor: not-allowed; }

.btn.is-loading {
  position: relative;
  color: transparent !important;
  pointer-events: none;
}
.btn.is-loading::after {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  width: 18px; height: 18px;
  margin: -9px 0 0 -9px;
  border-radius: 50%;
  border: 2px solid rgba(28, 20, 32, 0.35);
  border-top-color: #1c1420;
  animation: btn-spin .7s linear infinite;
}
.btn-ghost.is-loading::after {
  border: 2px solid rgba(244, 237, 226, 0.25);
  border-top-color: var(--ink);
}
@keyframes btn-spin { to { transform: rotate(360deg); } }

.btn-primary {
  background: var(--amber);
  color: #1c1420;
  font-weight: 600;
  border: none;
  width: 100%;
}
.btn-primary:hover { filter: brightness(1.08); }

.btn-ghost {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--ink);
  width: 100%;
}
.btn-ghost:hover { border-color: var(--ink-faint); }

/* Lien "quitter l'app, retour à l'accueil vitrine" — distinct d'un retour
   "dans" le flux (qui reste un .btn.btn-ghost pleine largeur sous l'action
   principale de l'étape). Toujours placé avant la .card, jamais dedans, et
   identique sur les trois points d'entrée (join/multi/solo.html) : voir
   CLAUDE.md "Mode solo"/nav-audit. Texte discret plutôt que bouton, pour ne
   pas rivaliser visuellement avec les vraies actions de l'écran. */
.back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink-dim);
  text-decoration: none;
  margin: 0 0 12px;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}
.back-link:hover { color: var(--ink); }

/* narrower, non-highlighted variant — e.g. "c'est nul" sat next to
   #nextBtn in .host-control-actions, where a second full-width/amber
   button would compete with the primary action instead of sitting quietly
   beside it */
.btn-compact {
  width: auto;
  flex: 0 0 auto;
  padding: 10px 14px;
  font-size: 14px;
  min-height: 40px;
}

/* cards */
.card {
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 24px;
  margin: 0 0 20px;
}

/* fields */
input.field {
  background: var(--bg-elev2);
  border: 1px solid var(--line);
  color: var(--ink);
  font-family: 'JetBrains Mono', monospace;
  font-size: 16px;
  letter-spacing: .06em;
  padding: 14px 16px;
  border-radius: 10px;
  width: 100%;
  min-height: 48px;
}
input.field.code-input { text-transform: uppercase; }
input.field.field-text { font-family: 'Inter', sans-serif; letter-spacing: normal; text-transform: none; }
input.field:focus { outline: 2px solid var(--amber); outline-offset: 1px; }
input.field::placeholder { color: var(--ink-faint); }

.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.row > * { flex: 1 1 auto; }

/* backstage pass */
.pass { background: var(--bg-elev); border: 1px solid var(--line); border-radius: 16px; overflow: hidden; margin-bottom: 20px; }
.pass-top { padding: 22px 24px 18px; text-align: center; }
.pass-code { font-family: 'JetBrains Mono', monospace; font-size: 44px; font-weight: 700; letter-spacing: .12em; color: var(--amber); }
.perforation { border-top: 2px dashed var(--ink-faint); opacity: .5; margin: 0 20px; position: relative; }
.perforation::before, .perforation::after {
  content: ''; position: absolute; top: -11px; width: 22px; height: 22px;
  background: var(--bg); border-radius: 50%;
}
.perforation::before { left: -31px; }
.perforation::after { right: -31px; }
.pass-bottom { padding: 20px 24px 26px; display: flex; flex-direction: column; align-items: center; gap: 12px; }
#qrcode { background: #fff; padding: 10px; border-radius: 8px; }
#qrcode img, #qrcode canvas { display: block; max-width: 100%; height: auto; }
.qr-clickable { cursor: pointer; }
.qr-clickable:active { transform: scale(0.97); }

/* floating QR button */
.qr-fab {
  position: fixed; bottom: max(20px, env(safe-area-inset-bottom)); right: 20px;
  width: 56px; height: 56px; border-radius: 50%;
  background: var(--amber); color: #1c1420; border: none;
  font-size: 24px; display: flex; align-items: center; justify-content: center;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); cursor: pointer; z-index: 46;
  min-height: 0; padding: 0;
}
.qr-fab:active { transform: scale(0.95); }

/* host control tile: the audio player pinned below the round-progress bar
   at the TOP of the screen, next-track/"c'est nul" actions kept pinned at
   the BOTTOM (still the natural thumb spot for the button pressed once per
   track) — so it never competes with the host's own answer UI
   (#playing/#bonusItem, see .anchored-bottom below) for that same
   thumb-reachable space. #hostControlBar itself (see index.html) isn't
   positioned — display: contents lets its two children (the <audio>,
   .host-control-actions) lay out independently against the viewport while
   still toggling hidden/shown together as one element in organizer.js. */
.host-control-bar { display: contents; }

.host-control-bar audio {
  position: fixed; left: 0; right: 0; z-index: 43;
  top: calc(54px + env(safe-area-inset-top)); /* below .round-progress —
    the two are always shown together while a track is in play, see
    organizer.js; round-progress's own top padding already covers the
    inset once, so this only adds it back on top of the 54px baseline */
  max-width: 520px; margin: 0 auto;
  padding: 10px max(20px, env(safe-area-inset-right)) 10px
           max(20px, env(safe-area-inset-left));
  box-sizing: border-box; width: 100%;
  background: var(--bg-elev);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.host-control-actions {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
  display: flex; gap: 8px;
  max-width: 520px; margin: 0 auto;
  padding: 14px max(20px, env(safe-area-inset-right)) max(14px, env(safe-area-inset-bottom))
           max(20px, env(safe-area-inset-left));
  background: var(--bg-elev);
  border-top: 1px solid var(--line);
  box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.35);
}
.host-control-actions #nextBtn { flex: 1; }
.host-control-actions .btn { margin: 0; }

/* extra room below the round-progress + audio bar stack so it never covers
   content. The compound selector outranks .has-round-progress alone
   (further below) regardless of rule order, so round-progress on its own
   still reserves just its own height once the host bar hides (e.g. during
   lobby). */
body.has-round-progress.has-host-bar .stage { padding-top: calc(120px + env(safe-area-inset-top)); }
/* extra room above the pinned actions bar — only reserved while it's
   actually showing something (reveal), never while the host's own QCM/
   bonus item is anchored to the bottom (see .anchored-bottom below): the
   actions bar only appears once #playing/#bonusItem have already hidden,
   so the two never compete for the same space and don't need to stack. */
body.has-host-actions .stage { padding-bottom: 110px; }

/* interactive cards — join form, answer choices, bonus item — pinned to the
   bottom of the screen — the thumb-reachable zone on a phone — instead of
   sitting in normal flow near the top. Used both by the player screen and
   by the organizer screen when the host is also playing along (host QCM,
   host bonus item) — identical treatment on both, nothing organizer-
   specific needed since .host-control-bar no longer sits at the bottom.
   Mobile only: on a desktop browser (mouse, no thumb zone) these stay in
   normal flow, same as any other .card. See syncAnchoredBody() in
   player.js/organizer.js (via answer-ui.js's makeVisibility) for the
   visibility bookkeeping. */
@media (max-width: 600px) {
  .anchored-bottom {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 44;
    max-width: 520px; margin: 0 auto;
    max-height: 70vh; overflow-y: auto;
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.35);
    padding: 20px max(20px, env(safe-area-inset-right)) max(20px, env(safe-area-inset-bottom))
             max(20px, env(safe-area-inset-left));
  }
  /* on a phone the QCM/bonus item is the thing being interacted with, so
     give it roughly the bottom half of the screen and make the choices
     themselves bigger/easier to tap, instead of the same size as on
     desktop (see .choice / .choices above) */
  .anchored-bottom {
    min-height: 50vh; max-height: 65vh;
    display: flex; flex-direction: column; justify-content: center;
  }
  .anchored-bottom .choice { padding: 20px 20px; font-size: 19px; min-height: 60px; }
  .anchored-bottom .choices { gap: 14px; }
  /* On the player screen, .stage-tag is a direct flex child of
     .anchored-bottom and stretches full-width for free. On the organizer
     screen it sits inside #hostQuestion (a plain wrapper div needed to
     toggle between the QCM and the "waiting" state within #playing) —
     that wrapper isn't itself a flex container, so the stretch stops
     there and the tag shrinks to its content. Making #hostQuestion a
     flex column too restores the same full-width look. */
  #hostQuestion { display: flex; flex-direction: column; }
  /* room above the pinned card so it never covers content sitting above it
     in normal flow (intro text, Deezer attribution link, etc.). #playing/
     #bonusItem and .host-control-actions never show at the same time (see
     organizer.js), so there's no case where this card needs to stack above
     the actions bar too — plain bottom: 0 always. */
  body.has-anchored-card .stage { padding-bottom: 300px; }
}

/* "activate sound" prompt — floats at the bottom like a toast, but bumped
   above any anchored-bottom card (see above) so it never sits on top of
   the answer choices. */
.unmute-btn {
  position: fixed; bottom: max(20px, env(safe-area-inset-bottom));
  left: 50%; transform: translateX(-50%); z-index: 50;
}
@media (max-width: 600px) {
  body.has-anchored-card .unmute-btn { bottom: 300px; }
}

/* round progress bar — "morceau x/x", shown as a filling bar rather than
   plain text, pinned atop the screen while a round (playing/bonus/reveal)
   is in flight. Same markup/ids/behavior for the organizer and the
   players (see round-progress.js) — no reason for these to differ. */
.round-progress {
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  padding: 10px max(16px, env(safe-area-inset-right)) 8px max(16px, env(safe-area-inset-left));
  padding-top: max(10px, env(safe-area-inset-top));
  background: var(--bg-elev);
  border-bottom: 1px solid var(--line);
}
.round-progress-row { display: flex; align-items: center; gap: 10px; }
.round-progress-info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.round-progress .round-progress-label { margin: 0; font-size: 12px; color: var(--ink-dim); text-align: center; }
.round-progress .round-progress-track {
  height: 6px; border-radius: 999px; background: var(--bg-elev2);
  border: 1px solid var(--line); overflow: hidden;
}
.round-progress .round-progress-fill {
  height: 100%; border-radius: 999px; background: var(--amber);
  transition: width .3s ease;
}
/* QR button pinned to the left of the progress bar, replacing the floating
   pastille while a round is in flight (organizer + players) — see
   round-progress.js. Same round/amber shape as the old .qr-fab, with a
   small inline QR-code glyph instead of the 📱 emoji (the one icon in the
   system besides the item glyphs/Deezer mark, drawn rather than borrowed
   from a font, since no emoji reads as "QR code"). */
.round-progress-qr-btn {
  flex-shrink: 0; width: 32px; height: 32px; border-radius: 50%;
  background: var(--amber); color: #1c1420; border: none; padding: 0;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
}
.round-progress-qr-btn:active { transform: scale(0.95); }
/* room above the pinned progress bar so it never covers the question card,
   mirroring body.has-host-bar's padding-bottom trick for the host */
body.has-round-progress .stage { padding-top: 54px; }
/* keep item toasts from sitting on top of the pinned progress bar */
body.has-round-progress .item-toast { top: calc(54px + max(16px, env(safe-area-inset-top))); }

/* fullscreen QR modal */
.qr-modal {
  position: fixed; inset: 0; background: rgba(16, 10, 20, 0.92);
  display: flex; align-items: center; justify-content: center;
  z-index: 50; padding: 20px;
}
.qr-modal-content { text-align: center; max-width: 340px; width: 100%; }
.qr-modal-content .pass-code { margin: 6px 0 20px; }
.qr-modal-content #qrModalQr {
  background: #fff; padding: 16px; border-radius: 12px; display: inline-block; margin-bottom: 16px;
}
.qr-modal-content #qrModalQr img, .qr-modal-content #qrModalQr canvas { display: block; width: 100%; height: auto; }

/* equalizer */
.eq { display: flex; align-items: flex-end; gap: 4px; height: 40px; width: fit-content; margin: 0 auto 18px; }
.eq span { width: 6px; border-radius: 3px; background: var(--amber); transition: height .2s linear, background .2s linear; }
.eq.urgent span { background: var(--coral); }

/* stage tag */
.stage-tag {
  font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--teal);
  border: 1px solid var(--teal-border);
  padding: 4px 10px; border-radius: 999px;
  display: inline-block;
}

/* my-sessions (homepage list of rooms this browser created/joined) */
.my-sessions { margin-top: 22px; text-align: left; }
.my-sessions-list { list-style: none; padding: 0; margin: 0; }
.session-row { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 14px 16px; border: 1px solid var(--line); border-radius: 10px; cursor: pointer; margin-bottom: 8px; }
.session-row:hover { border-color: var(--teal); }
.session-row .name { font-weight: 500; }
.session-row .meta { color: var(--ink-faint); font-size: 13px; margin-top: 2px; }
.session-row .meta:has(.leader-pill) { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.session-row .role-tag { font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--teal); border: 1px solid var(--teal-border); padding: 3px 8px; border-radius: 999px; flex-shrink: 0; }

/* clickable rows (playlists) */
.playlist-row { padding: 14px 16px; border: 1px solid var(--line); border-radius: 10px; cursor: pointer; margin-bottom: 8px; }
.playlist-row:hover { border-color: var(--teal); }
.playlist-row.selected { border-color: var(--amber); background: var(--amber-wash); }
.playlist-row .name { font-weight: 500; }
.playlist-row .meta { color: var(--ink-faint); font-size: 13px; margin-top: 2px; }

/* stage-check (checkbox row) */
.stage-check {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px; border: 1px solid var(--line); border-radius: 10px; margin-bottom: 8px; cursor: pointer;
}
.stage-check:has(input:checked) { border-color: var(--amber); background: var(--amber-wash); }
.stage-check input { width: auto; min-height: auto; margin: 0; accent-color: var(--amber); }

/* QCM choice buttons */
.choice {
  padding: 16px 18px; border-radius: 10px; border: 1px solid var(--line); background: var(--bg-elev2);
  font-family: 'Inter', sans-serif; font-weight: 500; font-size: 17px; color: var(--ink);
  width: 100%; text-align: left; cursor: pointer; min-height: 52px;
  /* The button owns the touch gesture: a press that drifts vertically must not
     be stolen by page scroll and lost (see onTap in webapp/shared/tap.js).
     Also kills the 300ms tap delay, double-tap zoom, iOS long-press callout
     and text selection on a fast tap. */
  touch-action: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
.choice:hover { border-color: var(--amber); }
.choice:disabled { opacity: .6; cursor: not-allowed; }
.choice.correct { border-color: var(--teal); background: rgba(0, 255, 242, 0.12); }
.choice.wrong { border-color: var(--coral); background: rgba(255, 47, 86, 0.12); }
.choice.choice-selected { border-color: var(--amber); background: var(--amber-wash); }
/* Tap-registered feedback: a bright amber flash on the picked QCM button,
   set by answer-ui.js and removed on animationend (so it never fights the
   correct/wrong colours at reveal). opacity override keeps it visible even
   though the buttons lock (:disabled) synchronously with the tap. */
.choice.choice-flash, .choice.choice-flash:disabled { opacity: 1; border-color: var(--amber); }
.choice.choice-flash { animation: choiceFlash .4s ease-out; }
@keyframes choiceFlash {
  0% { background: var(--amber); color: var(--bg-elev); }
  100% { background: var(--amber-wash); }
}
.choices { display: grid; gap: 10px; margin-top: 16px; }

/* Mode solo (voir web/solo.html, webapp/solo.js) */
.session-row-disabled { opacity: .5; cursor: not-allowed; pointer-events: none; }
.score-row-self { background: var(--amber-wash); border-radius: 8px; padding-left: 6px; }

/* "Partie en cours" : distingue la ligne d'état actif du reste des lignes
   neutres de la liste (playlists, mes scores). */
.session-row-active { border-color: var(--teal-border); background: var(--teal-wash); }

/* "↓ Voir N de plus" : replie #soloPlaylistsList/#soloMyScoresList à 5
   lignes une fois qu'elles peuvent dépasser un écran — voir
   appendShowMore dans solo.js et le composant ShowMoreList du design
   system. Bouton discret, cohérent avec le reste des libellés mono. */
.show-more-row { list-style: none; }
.show-more-btn {
  display: block; width: 100%; text-align: center; background: transparent; border: none;
  cursor: pointer; padding: 10px 0 4px; margin-top: 2px;
  font-family: 'JetBrains Mono', monospace; font-size: 12.5px; letter-spacing: .02em;
  color: var(--ink-faint);
}
.show-more-btn:hover { color: var(--ink-dim); }

/* ChallengeStat : score-clé (à battre, ou obtenu) sorti de la liste dans un
   encart dédié — voir handoff/solo_readability du design system. */
.challenge-stat {
  text-align: center; background: var(--bg-elev2); border: 1px solid var(--line);
  border-radius: 10px; padding: 18px 16px; margin: 0 0 20px;
}
.challenge-stat-label {
  font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-faint); display: block; margin-bottom: 8px;
}
.challenge-stat-score {
  font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 40px;
  color: var(--amber); text-shadow: 0 0 10px var(--amber-glow); line-height: 1;
}
.challenge-stat-score .unit { font-size: 16px; font-weight: 500; }
.challenge-stat-name { margin-top: 8px; color: var(--ink); font-size: 14px; }

/* RankedScoreList : médailles sur le podium, tag "Toi" + fond ambre sur la
   ligne du joueur plutôt qu'un fond à peine visible (remplace
   .score-row-self ci-dessus sur les écrans passés à ce composant). */
.ranked-score-list .score-line { align-items: center; gap: 10px; padding: 12px 14px;
  border-bottom: none; border-radius: 10px; margin-bottom: 2px; }
.ranked-score-list .score-line.score-row-self {
  background: var(--amber-wash); border: 1px solid var(--amber-border); padding: 12px 14px;
}
.ranked-score-list .medal {
  width: 26px; flex-shrink: 0; font-family: 'JetBrains Mono', monospace; font-size: 13px;
  color: var(--ink-faint); text-align: center;
}
.ranked-score-list .self-tag {
  margin-left: 8px; font-family: 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: .05em;
  text-transform: uppercase; color: var(--amber); border: 1px solid var(--amber-border);
  border-radius: 999px; padding: 1px 6px;
}

/* LeaderPill : version compacte de ChallengeStat pour une ligne de liste
   dense — remplace la phrase "🏆 182 pts — Léa" noyée dans .meta. */
.leader-pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: 'JetBrains Mono', monospace; font-size: 10.5px; letter-spacing: .02em;
  background: var(--amber-wash-strong); color: var(--amber); border: 1px solid var(--amber-border);
  padding: 3px 9px; border-radius: 999px; white-space: nowrap;
}

/* "Mon classement" : rang du joueur sur chaque playlist plutôt que son
   simple meilleur score — voir handoff/solo_readability. */
.rank-row-king { border-color: var(--amber-border); background: var(--amber-wash); }
.rank-row-king .rank-status {
  font-family: 'JetBrains Mono', monospace; font-weight: 700; color: var(--amber);
  text-shadow: 0 0 10px var(--amber-glow);
}
.rank-row .rank-status { font-family: 'JetBrains Mono', monospace; font-weight: 700; color: var(--ink); }
.rank-row .rank-gap { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--coral); margin-top: 2px; }

/* "Mes scores" : toute la tuile affiche/masque le classement (voir
   toggleSoloRankList dans solo.js) — le bouton "Rejouer", quand présent,
   est une zone cliquable à part (stopPropagation), pas le déclencheur du
   classement. */
.solo-score-row { cursor: pointer; }
.solo-score-row-main { flex: 1; min-width: 0; }
.solo-score-row-side { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex-shrink: 0; text-align: right; }
.solo-rank-row-wrap { margin: -4px 0 8px; }
.solo-rank-list { padding-left: 8px; }

/* year slider */
.year-slider { text-align: center; padding: 8px 4px 4px; }
/* "Valider" — robust tap (see .choice / webapp/shared/tap.js). The slider
   thumb keeps its own touch-action so dragging it still works. */
.year-slider button { touch-action: manipulation; -webkit-touch-callout: none; user-select: none; -webkit-user-select: none; }
.year-value {
  font-family: 'JetBrains Mono', monospace; font-size: 48px; font-weight: 700;
  color: var(--amber); letter-spacing: .04em; margin-bottom: 18px;
}
.year-slider input[type="range"] {
  width: 100%; height: 6px; border-radius: 999px; background: var(--bg-elev2);
  appearance: none; -webkit-appearance: none; cursor: pointer; accent-color: var(--amber);
}
.year-slider input[type="range"]::-webkit-slider-thumb {
  appearance: none; -webkit-appearance: none; width: 26px; height: 26px; border-radius: 50%;
  background: var(--amber); border: 3px solid var(--bg-elev); box-shadow: 0 0 0 1px var(--line);
  cursor: pointer;
}
.year-slider input[type="range"]::-moz-range-thumb {
  width: 26px; height: 26px; border-radius: 50%; background: var(--amber);
  border: 3px solid var(--bg-elev); box-shadow: 0 0 0 1px var(--line); cursor: pointer;
}
.year-bounds {
  display: flex; justify-content: space-between; margin-top: 8px;
  font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--ink-faint);
}
/* Untouched state: the thumb has to sit somewhere, but greyed out (and the
   "?" placeholder instead of a number) makes clear no guess has been made
   yet, instead of looking like a pre-picked answer. Cleared on first drag
   (see answer-ui.js). */
.year-slider.unset .year-value { color: var(--ink-faint); }
.year-slider.unset input[type="range"] { opacity: .5; }
.year-slider.unset input[type="range"]::-webkit-slider-thumb { background: var(--ink-faint); }
.year-slider.unset input[type="range"]::-moz-range-thumb { background: var(--ink-faint); }

/* score lines */
.score-line { display: flex; justify-content: space-between; align-items: flex-start; padding: 14px 0; border-bottom: 1px solid var(--line); font-size: 14px; gap: 10px; }
.score-line:last-child { border-bottom: none; }
.score-line .pts { font-family: 'JetBrains Mono', monospace; color: var(--amber); font-weight: 700; flex-shrink: 0; }
.score-list { list-style: none; padding: 0; margin: 0; }

/* Round medal: highlights this song's top scorers without disturbing the
   general ranking order the list is still sorted by. */
.round-medal { margin-right: 6px; font-size: 15px; }

/* Mario Kart mode: item effects panel, kept visually separate from the
   blind-test score list (not squeezed inline as pills) so long "X touche Y
   avec Z" lines never risk overflowing narrow screens — each line is a
   full-width block that wraps normally. */
.item-effects-panel {
  margin-top: 16px; padding-top: 14px; border-top: 1px dashed var(--line);
}
.item-effects-panel h3 { margin-bottom: 8px; }
.item-effects-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 6px; }
.item-effects-list li {
  background: var(--bg-elev2); border-radius: 10px;
  padding: 8px 12px; font-size: 13px; line-height: 1.45;
  overflow-wrap: anywhere; word-break: break-word;
  display: flex; justify-content: space-between; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
.item-effects-list .effect-text { flex: 1 1 auto; min-width: 0; }
.item-effects-list .effect-pts {
  font-family: 'JetBrains Mono', monospace; font-weight: 700; flex-shrink: 0;
}
.item-effects-list .effect-pts.positive { color: var(--teal); }
.item-effects-list .effect-pts.negative { color: var(--coral); }

/* per-stage point breakdown on reveal */
.stage-details { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.stage-detail {
  font-family: 'JetBrains Mono', monospace; font-size: 10.5px; letter-spacing: .02em;
  border-radius: 999px; padding: 3px 9px; white-space: nowrap;
  background: var(--bg-elev2); border: 1px solid var(--line); color: var(--ink-dim);
}
.stage-detail.correct { border-color: var(--teal); color: var(--teal); }
.stage-detail.wrong { border-color: var(--coral); color: var(--coral); opacity: .7; }
.stage-detail .rank { color: var(--amber); }
.stage-detail .delay { color: var(--ink-faint); }

/* badges */
.badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.badge {
  font-family: 'JetBrains Mono', monospace; font-size: 10.5px; letter-spacing: .03em;
  background: var(--amber-wash-strong); color: var(--amber);
  border: 1px solid var(--amber-border);
  padding: 3px 9px; border-radius: 999px; white-space: nowrap;
  animation: badge-pop .5s cubic-bezier(.34, 1.56, .64, 1) backwards;
  cursor: pointer;
}
.badge:nth-child(1) { animation-delay: 0s; }
.badge:nth-child(2) { animation-delay: .1s; }
.badge:nth-child(3) { animation-delay: .2s; }
.badge:nth-child(4) { animation-delay: .3s; }
.badge:nth-child(n+5) { animation-delay: .4s; }
@keyframes badge-pop {
  0% { transform: scale(0) rotate(-12deg); opacity: 0; }
  65% { transform: scale(1.15) rotate(4deg); opacity: 1; }
  100% { transform: scale(1) rotate(0); opacity: 1; }
}

/* player chip */
.player-chip { display: flex; align-items: center; gap: 10px; padding: 10px 14px; background: var(--bg-elev2); border-radius: 10px; font-size: 14px; margin-bottom: 8px; }
.player-chip.selected { outline: 2px solid var(--amber); background: var(--amber-wash-strong); }
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--teal); flex-shrink: 0; }
.player-list { list-style: none; padding: 0; margin: 0; }

.muted { color: var(--ink-dim); font-size: 0.9rem; }
.warning-note { color: var(--amber); font-size: 0.9rem; margin-top: 6px; }

/* discreet attribution required by Deezer's developer guidelines */
.deezer-attribution {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 18px auto 4px;
  padding: 4px 10px;
  background: #fff;
  border-radius: 999px;
  width: fit-content;
  opacity: 0.85;
  text-decoration: none;
  transition: opacity 0.15s ease;
}
.deezer-attribution:hover { opacity: 1; }
.deezer-attribution span { color: #0F0D13; font-size: 11px; font-weight: 500; }
.deezer-attribution img { height: 12px; width: auto; display: block; }

/* Only shown while creating/joining a game — hidden once a round is
   actually playing (organizer: #playing/#reveal/#bonusItem/#final,
   player: #question/#waitingOthers/#bonusItem/#reveal/#final). */
.stage:has(#playing:not(.hidden)) .deezer-attribution,
.stage:has(#reveal:not(.hidden)) .deezer-attribution,
.stage:has(#bonusItem:not(.hidden)) .deezer-attribution,
.stage:has(#final:not(.hidden)) .deezer-attribution,
.stage:has(#question:not(.hidden)) .deezer-attribution,
.stage:has(#waitingOthers:not(.hidden)) .deezer-attribution {
  display: none;
}
.hidden { display: none !important; }
audio { width: 100%; margin: 12px 0; }

ul { padding-left: 0; }

/* countdown overlay before a track starts */
.countdown-overlay {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle at 50% 45%, var(--bg-elev2), var(--bg) 70%);
}
.countdown-number {
  font-family: 'Bricolage Grotesque', sans-serif;
  font-weight: 800;
  font-size: min(40vw, 220px);
  line-height: 1;
  color: var(--amber);
  text-shadow: 0 0 60px var(--amber-glow);
  animation: countdown-pop 1s cubic-bezier(.2, 1.4, .4, 1);
}
@keyframes countdown-pop {
  0% { transform: scale(0.3); opacity: 0; }
  35% { transform: scale(1.15); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}

/* Mario Kart mode: item pill on the joueur's held-item screen */
.bonus-item-icon {
  text-align: center;
  animation: badge-pop .5s cubic-bezier(.34, 1.56, .64, 1) backwards;
}
.bonus-item-icon img { width: 64px; height: 64px; }

/* Mario Kart mode: full-screen "item collected" slot-machine reveal */
.item-pickup-content { text-align: center; }
.item-pickup-icon { line-height: 1; }
.item-pickup-icon img {
  width: min(35vw, 160px); height: min(35vw, 160px);
  filter: drop-shadow(0 8px 24px rgba(0, 0, 0, 0.35));
}
.item-pickup-label {
  font-family: 'Bricolage Grotesque', sans-serif;
  font-weight: 800; font-size: 26px; color: var(--amber);
  margin-top: 14px;
  transition: color .2s ease;
}
/* each spin tick: a quick flicker so cycling through random icons reads as
   "drawing", not just text swapping instantly */
.item-pickup-icon-tick { animation: item-pickup-tick .09s linear; }
@keyframes item-pickup-tick {
  0% { transform: scale(0.85) translateY(-6px); opacity: 0.5; filter: blur(1px); }
  100% { transform: scale(1) translateY(0); opacity: 1; filter: blur(0); }
}
/* landing on the real item: a bigger, bouncier pop than the spin ticks */
.item-pickup-icon-settle { animation: item-pickup-pop .6s cubic-bezier(.2, 1.4, .4, 1); }
@keyframes item-pickup-pop {
  0% { transform: scale(0.6) rotate(-14deg); }
  55% { transform: scale(1.3) rotate(6deg); }
  100% { transform: scale(1) rotate(0); }
}

/* Mario Kart mode: flavor announcement toast when an item is used */
/* stack of item-used/item-held toasts — each notification gets its own
   entry appended below the previous ones instead of replacing it, so a
   burst of item events all stay readable rather than clobbering each other */
.item-toast {
  position: fixed; top: max(16px, env(safe-area-inset-top)); left: 50%; transform: translateX(-50%);
  z-index: 70; max-width: min(92vw, 420px);
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  pointer-events: none;
}
.item-toast-entry {
  background: var(--bg-elev2); border: 1px solid var(--line); border-radius: 999px;
  padding: 10px 18px; font-size: 14px; text-align: center;
  box-shadow: 0 8px 24px rgba(0,0,0,0.35);
  animation: toast-in .35s cubic-bezier(.2, 1.4, .4, 1);
}
.item-toast-entry.fade-out { animation: toast-out .4s ease forwards; }
@keyframes toast-in {
  0% { transform: translateY(-30px); opacity: 0; }
  100% { transform: translateY(0); opacity: 1; }
}
@keyframes toast-out {
  0% { transform: translateY(0); opacity: 1; }
  100% { transform: translateY(-30px); opacity: 0; }
}

/* final ranking: the one place --neon-yellow shows up, reserved for
   celebration rather than the everyday accent/correct/wrong roles */
#final h2 { color: var(--neon-yellow); }

/* wordmark: DS logo mark sat inline with the domain text in #intro */
.wordmark-logo { height: 1em; width: auto; vertical-align: -0.1em; margin-right: 4px; }
/* the domain text picks up the logo mark's own two colours (cyan hook,
   yellow dot) rather than the customizable --amber accent, so it always
   reads as "the logo, spelled out" regardless of the player's highlight
   colour choice */
.wordmark-text {
  background: linear-gradient(90deg, var(--teal), var(--neon-yellow));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Landing (accueil, #intro/#home) — the one screen laid out wider than
   the rest of the app: everything else lives inside .stage (520px,
   mobile-first — the game itself is played on a phone), but a landing
   page reads better with room to breathe on a laptop/desktop, and it
   collapses to a single column via flex-wrap well before 520px anyway.
   #landingNav/#intro/#home sit as siblings *before* .stage in the
   markup, not inside it, purely to escape that width constraint — see
   web/index.html. */
.landing-wrap { max-width: 1080px; margin: 0 auto; padding: 0 20px; }

.landing-nav { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding-top: 28px; padding-bottom: 24px; flex-wrap: wrap; }
.landing-nav-brand { display: flex; align-items: center; gap: 12px; min-width: 0; overflow: hidden; }
.landing-nav-brand img { height: 32px; width: auto; flex-shrink: 0; }
.landing-nav-brand .wordmark-text { font-size: 21px; font-weight: 700; font-family: 'Bricolage Grotesque', sans-serif; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* .btn defaults to width:100% (see .btn-primary/.btn-ghost) — this sits
   inline next to the brand instead, so it needs its own width. */
.btn-nav { width: auto; flex-shrink: 0; padding: 12px 18px; white-space: nowrap; }
.landing-nav-actions { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
/* On a narrow phone, "quizik.fr" + "Rejoindre une partie" side
   by side don't fit without truncating the domain name (confirmed: it
   was getting cut off with an ellipsis on mobile). flex-wrap above lets
   the row drop below the brand instead — the brand keeps the full width
   to itself and never needs to shrink. */
@media (max-width: 480px) {
  .landing-nav-brand { flex-basis: 100%; overflow: visible; }
  .landing-nav-brand .wordmark-text { overflow: visible; text-overflow: clip; }
}

/* Pastille de couleur de highlight, à droite avec "Rejoindre une partie"
   — ouvre un mini sélecteur de .accent-swatch (voir web/index.html). Un
   simple rond plein plutôt qu'un .btn : ce n'est pas une action de jeu,
   juste un réglage discret. Dans le fil (pas position:fixed) : elle
   défile avec le reste de #landingNav plutôt que de rester accrochée à
   l'écran. */
.landing-accent-picker-wrap { position: relative; flex-shrink: 0; }
.landing-accent-dot {
  width: 32px; height: 32px; border-radius: 50%; padding: 0; flex-shrink: 0;
  border: 2px solid var(--line); background-color: var(--amber); cursor: pointer;
}
.landing-accent-dot:hover { border-color: var(--ink-faint); }
.landing-accent-dot:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
.accent-swatches.landing-accent-picker {
  position: absolute; top: calc(100% + 10px); right: 0; z-index: 20;
  margin-top: 0; background: var(--bg-elev); border: 1px solid var(--line);
  border-radius: 12px; padding: 10px 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, .35);
}

.landing-hero { max-width: 640px; margin: 0 auto; padding: 12px 20px 40px; text-align: center; }
.landing-hero .eyebrow { text-align: center; }
.landing-hero .eq { margin-top: 8px; }

.landing-sessions { max-width: 520px; margin: 0 auto 32px; }

.landing-cards { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 56px; }
.landing-cards .card { flex: 1 1 340px; min-width: 0; margin: 0; display: flex; flex-direction: column; }
.landing-card-emoji { font-size: 36px; margin-bottom: 16px; }
.landing-card-tags { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 20px; }
.landing-cards .card > .btn { margin-top: auto; }

.landing-section-title { text-align: center; margin-bottom: 20px; }
.landing-section-title .eyebrow { text-align: center; }
.landing-section-title h2 { margin-bottom: 0; }

.landing-steps { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 32px; }
.landing-steps .card { flex: 1 1 240px; min-width: 0; margin: 0; }
.landing-step-num { font-family: 'JetBrains Mono', monospace; font-size: 13px; color: var(--amber); text-shadow: 0 0 10px var(--amber-glow); }

.landing-mariokart p { max-width: 520px; margin: 0 auto 20px; }
.landing-mariokart-icons { display: flex; justify-content: center; gap: 20px; flex-wrap: wrap; }
.landing-mariokart-icons img { width: 48px; height: 48px; filter: drop-shadow(0 8px 20px rgba(0, 0, 0, .35)); }

/* accent swatches: lets a player pick their preferred highlight colour,
   persisted in localStorage (see ACCENT_PRESETS in organizer.js/player.js).
   Deliberately separate from .choice — round, no label, selection shown as
   a ring rather than a filled/bordered rectangle. */
.accent-swatches { display: flex; gap: 8px; margin-top: 16px; justify-content: center; }
/* inline beside the pseudo field (organizer #setup, player #entry): no
   longer a full-width centred row, and must not stretch like the row's
   other flex children */
.row .accent-swatches { flex: 0 0 auto; margin-top: 0; }
.accent-swatch {
  width: 28px; height: 28px; border-radius: 50%; border: 2px solid transparent;
  padding: 0; cursor: pointer; background-color: var(--swatch-color);
}
.accent-swatch:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
.accent-swatch.selected { border-color: var(--swatch-color); box-shadow: 0 0 0 3px var(--bg-elev), 0 0 0 4px var(--swatch-color); }

/* share-link: a share URL shown as plain text (join/short link next to a
   QR code, or in "mes scores"/end-of-game) that's actually clickable —
   see webapp/shared/share.js. Underline signals it's tappable without
   turning it into something that looks like a real hyperlink (it's never
   navigated to, only shared/copied). */
.share-link { cursor: pointer; text-decoration: underline dotted; text-underline-offset: 3px; }
.share-link-copied { color: var(--teal); text-decoration: none; }

/* --- Lobby solo : onglets de découverte (voir #soloTabs dans solo.html) ---
   Les valeurs viennent du design system Claude Design ("Solo — Découvrir
   des défis"), traduites vers les tokens de cette feuille (--line,
   --ink-faint, --amber...) : le DS nomme les siens autrement
   (--border-hairline, --text-muted, --radius-control...) et aucun n'existe
   ici, donc les importer tels quels donnerait des var() vides. */
/* Grille 2×2 à toutes les largeurs, pas une rangée de quatre : la carte du
   lobby est trop étroite même sur desktop pour aligner les quatre libellés
   sans les tronquer à l'ellipse, et les laisser passer à la ligne tout
   seuls donnait un 3+1 en drapeau qui se lit comme un accident de mise en
   page. Deux par deux tient partout et reste lisible au pouce. */
.solo-tabs { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin: 6px 0 16px; }
.solo-tab {
  padding: 10px 8px; min-height: 44px;
  border: 1px solid var(--line); border-radius: 10px; background: transparent;
  color: var(--ink-faint); font-family: inherit; font-size: 12.5px; font-weight: 600;
  cursor: pointer; text-align: center; white-space: nowrap;
}
.solo-tab:hover { color: var(--ink-dim); border-color: var(--teal-border); }
.solo-tab-active,
.solo-tab-active:hover { border-color: var(--amber); background: var(--amber-wash); color: var(--amber); }

/* --- Roulette du tirage "Surprise" (#soloRouletteOverlay) --- */
.solo-roulette { text-align: center; padding: 0 24px; max-width: 420px; }
.solo-roulette-title {
  font-family: 'Bricolage Grotesque', sans-serif; font-weight: 800;
  font-size: clamp(22px, 7vw, 30px); color: var(--amber); margin: 10px 0 4px;
}
/* Le gagnant se distingue du défilé : encre pleine plutôt qu'ambre, et une
   animation qui "claque" au lieu de glisser. */
.solo-roulette-settled .solo-roulette-title {
  color: var(--ink); font-size: clamp(24px, 7vw, 32px);
  animation: solo-roulette-pop .45s cubic-bezier(.2, 1.6, .4, 1);
}
.solo-roulette-ticking .solo-roulette-title { animation: solo-roulette-tick .09s linear; }
.solo-roulette-score { margin: 10px 0 22px; color: var(--amber); font-family: 'JetBrains Mono', monospace; font-size: 13px; min-height: 18px; }
.solo-roulette-close {
  margin-top: 14px; background: none; border: none; color: var(--ink-faint);
  font-family: inherit; font-size: 14px; cursor: pointer; text-decoration: underline;
}
@keyframes solo-roulette-tick {
  0% { transform: scale(.85) translateY(-6px); opacity: .5; filter: blur(1px); }
  100% { transform: scale(1) translateY(0); opacity: 1; filter: blur(0); }
}
@keyframes solo-roulette-pop {
  0% { transform: scale(.6) rotate(-14deg); }
  55% { transform: scale(1.3) rotate(6deg); }
  100% { transform: scale(1) rotate(0); }
}
/* Le défilé de la roulette est une animation décorative pure : rien à
   perdre à la couper pour qui demande moins de mouvement. */
@media (prefers-reduced-motion: reduce) {
  .solo-roulette-ticking .solo-roulette-title,
  .solo-roulette-settled .solo-roulette-title { animation: none; }
}
