/* ============================================================
   KNOWLEDGE HUNT — components
   ------------------------------------------------------------
   Aesthetic: sticker-book arcade. Chunky strokes, hard offset
   shadows (no blur), oversized display type that tilts and
   breaks its box, speech bubbles as the structural motif,
   trading cards for the collection, and a high-score board
   that behaves like an arcade cabinet.

   All colours/fonts come from tokens.css. Never hard-code a
   colour here: it would break skinning.
   ============================================================ */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html { -webkit-text-size-adjust: 100%; }

body {
  font-family: var(--font-body);
  background: var(--bg);
  color: var(--fg);
  min-height: 100vh;
  min-height: 100dvh;
  overflow-x: hidden;
  transition: background 0.4s var(--ease-out), color 0.4s var(--ease-out);
  -webkit-tap-highlight-color: transparent;
}

/* Atmosphere: two soft colour pools + a grain wash over the flat background. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    /* --pool-rgb, falling back to the primary. A skin whose primary is not also
       its ambient hue overrides it — see `knowledge`, whose buttons are
       aubergine on an indigo page. */
    radial-gradient(60% 45% at 12% 8%, rgb(var(--pool-rgb, var(--primary-rgb)) / 0.22), transparent 70%),
    radial-gradient(55% 40% at 88% 92%, rgb(var(--accent-rgb) / 0.18), transparent 70%);
}

body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: var(--grain);
  opacity: 0.14;
  mix-blend-mode: overlay;
}

::selection { background: var(--primary); color: var(--primary-contrast); }

/* ---- Floating background decor (skin-driven) ---- */
.decor-bg {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  overflow: hidden;
}

.decor-item {
  position: absolute;
  animation: floatDecor 20s ease-in-out infinite;
}

.decor-item--blob {
  width: 30px;
  height: 40px;
  border-radius: var(--radius-blob);
}

.decor-item--glyph {
  font-family: var(--font-display);
  font-size: 2.2rem;
  font-weight: 700;
  line-height: 1;
  user-select: none;
}

/* Speech bubble with a tail; --decor-color is set per element by theme.js */
.decor-item--bubble {
  width: 44px;
  height: 38px;
  background: var(--decor-color, currentColor);
  border-radius: 16px 16px 16px 5px;
}

.decor-item--bubble::after {
  content: '';
  position: absolute;
  left: 4px;
  bottom: -11px;
  border-style: solid;
  border-width: 12px 12px 0 0;
  border-color: var(--decor-color, currentColor) transparent transparent transparent;
}

@keyframes floatDecor {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  25% { transform: translateY(-30px) rotate(10deg); }
  50% { transform: translateY(-15px) rotate(-5deg); }
  75% { transform: translateY(-40px) rotate(8deg); }
}

/* ---- Page container ---- */
.page {
  position: relative;
  z-index: 1;
  max-width: 600px;
  margin: 0 auto;
  padding: 20px 20px 116px; /* room for the bottom nav */
  min-height: 100vh;
  min-height: 100dvh;
}

.page--wide { max-width: 1200px; padding-bottom: 40px; }
.page--full {
  max-width: 100%;
  padding: 32px 32px 40px;
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}

/* ---- Typography ---- */
h1, h2, h3 {
  font-family: var(--font-display);
  color: var(--fg);
  line-height: 1.05;
  letter-spacing: -0.02em;
}

h2 { font-size: 1.5rem; }
h3 { font-size: 1.15rem; }

.title-main {
  font-size: clamp(2.4rem, 12vw, 4rem);
  font-weight: 800;
  text-align: center;
  text-transform: uppercase;
  line-height: 0.92;
  letter-spacing: -0.03em;
  margin-bottom: 10px;
  color: var(--primary-strong);
  text-wrap: balance;
  overflow-wrap: break-word; /* long session titles must not overflow */
}

.title-sub {
  text-align: center;
  color: var(--fg-muted);
  font-weight: 600;
  font-size: 1rem;
  margin-bottom: 28px;
  text-wrap: balance;
}

.eyebrow {
  font-family: var(--font-display);
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--fg-muted);
}

.muted { color: var(--fg-muted); }
.tabnum { font-variant-numeric: tabular-nums; }

/* ---- Brand mark: three speech bubbles, the poster's language ---- */
.brand-mark {
  width: 138px;
  margin: 0 auto 18px;
  display: block;
  color: var(--ink); /* the SVG strokes use currentColor */
}

.brand-mark svg { width: 100%; height: auto; overflow: visible; display: block; }

.brand-mark .bub {
  transform-box: fill-box;
  transform-origin: center;
  animation: bubBob 4s ease-in-out infinite;
}

.brand-mark .bub:nth-of-type(1) { fill: var(--primary); animation-delay: 0s; }
.brand-mark .bub:nth-of-type(2) { fill: var(--accent); animation-delay: 0.45s; }
.brand-mark .bub:nth-of-type(3) { fill: var(--highlight); animation-delay: 0.9s; }

@keyframes bubBob {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-7px) rotate(-3deg); }
}

/* ---- Cards ---- */
.card {
  position: relative;
  background: var(--card);
  border-radius: var(--radius-lg);
  padding: 26px;
  border: var(--stroke) solid var(--ink);
  box-shadow: var(--hard);
}

.card--tilt { transform: rotate(-1.2deg); }

/* Speech-bubble card: the question container */
.bubble-card {
  position: relative;
  background: var(--card);
  border: var(--stroke) solid var(--ink);
  border-radius: 26px 26px 26px 8px;
  padding: 26px 24px;
  box-shadow: var(--hard);
}

.bubble-card::before,
.bubble-card::after {
  content: '';
  position: absolute;
  left: 22px;
  bottom: calc(-1 * (18px + var(--stroke)));
  width: 0;
  height: 0;
  border-style: solid;
  border-width: calc(18px + var(--stroke)) calc(20px + var(--stroke)) 0 0;
  border-color: var(--ink) transparent transparent transparent;
}

.bubble-card::after {
  bottom: -18px;
  left: 25px;
  border-width: 18px 20px 0 0;
  border-color: var(--card) transparent transparent transparent;
}

/* ---- Buttons ---- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 14px 26px;
  border: var(--stroke) solid var(--ink);
  border-radius: var(--radius-pill);
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  cursor: pointer;
  text-decoration: none;
  box-shadow: var(--hard-sm);
  transition: transform 0.16s var(--spring), box-shadow 0.16s var(--spring), background 0.2s;
}

/* Hover effects live behind `@media (hover: hover) and (pointer: fine)`, all of
   them, without exception. A touch browser leaves :hover stuck on whatever was
   under the last tap, and this app rebuilds lists in place — the quiz recreates
   its three answer buttons for every question, so the slot the player had just
   tapped came back lit up on the next one. A phone has no pointer to hover
   with; it should never see a hover style at all. */
@media (hover: hover) and (pointer: fine) {
  .btn:hover:not(:disabled) { transform: translate(-2px, -2px); box-shadow: var(--hard); }
}
.btn:active:not(:disabled) { transform: translate(2px, 2px); box-shadow: var(--hard-press); }

.btn:disabled { opacity: 0.5; cursor: not-allowed; box-shadow: var(--hard-press); }

.btn--primary { background: var(--primary); color: var(--primary-contrast); }
.btn--accent { background: var(--accent); color: var(--on-bright); }
.btn--highlight { background: var(--highlight); color: var(--on-bright); }
.btn--danger { background: var(--danger); color: var(--on-bright); }
.btn--outline { background: var(--card); color: var(--fg); }
.btn--ghost {
  background: transparent;
  color: var(--fg-muted);
  border-color: transparent;
  box-shadow: none;
}
@media (hover: hover) and (pointer: fine) {
  .btn--ghost:hover:not(:disabled) {
    color: var(--fg);
    background: rgb(var(--primary-rgb) / 0.1);
    box-shadow: none;
    transform: none;
  }
}

.btn--sm { padding: 8px 16px; font-size: 0.82rem; border-width: 2px; }
.btn--lg { padding: 18px 34px; font-size: 1.15rem; }
.btn--block { width: 100%; }

/* ---- Answer buttons ---- */
.answers-list { display: flex; flex-direction: column; gap: 12px; margin-top: 22px; }

.answer-btn {
  --answer-face: var(--card);
  width: 100%;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 15px 18px;
  background: var(--answer-face);
  border: var(--stroke) solid var(--ink);
  border-radius: 18px;
  box-shadow: var(--hard-sm);
  font-family: var(--font-body);
  font-size: 1.02rem;
  font-weight: 700;
  color: var(--fg);
  cursor: pointer;
  text-align: left;
  transition: transform 0.16s var(--spring), box-shadow 0.16s var(--spring), background 0.25s;
  animation: answerIn 0.45s var(--spring) both;
}

.answer-btn:nth-child(1) { animation-delay: 0.05s; }
.answer-btn:nth-child(2) { animation-delay: 0.12s; }
.answer-btn:nth-child(3) { animation-delay: 0.19s; }

@keyframes answerIn {
  from { opacity: 0; transform: translateX(-14px) rotate(-1deg); }
  to { opacity: 1; transform: none; }
}

@media (hover: hover) and (pointer: fine) {
  .answer-btn:hover:not(:disabled) { transform: translate(-3px, -3px); box-shadow: var(--hard); }
}
.answer-btn:active:not(:disabled) { transform: translate(2px, 2px); box-shadow: var(--hard-press); }

.answer-btn .answer-label {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  border-radius: 12px;
  border: 2px solid var(--ink);
  background: var(--primary-soft);
  color: var(--fg);
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 800;
  transition: background 0.25s, color 0.25s, transform 0.3s var(--spring);
}

@media (hover: hover) and (pointer: fine) {
  .answer-btn:hover:not(:disabled) .answer-label {
    background: var(--primary);
    color: var(--primary-contrast);
    transform: rotate(-8deg) scale(1.06);
  }
}

.answer-btn.correct {
  --answer-face: var(--ok-soft);
  border-color: var(--ok);
  animation: popCorrect 0.5s var(--spring);
}
.answer-btn.correct .answer-label { background: var(--ok); color: var(--on-bright); }

.answer-btn.wrong {
  --answer-face: var(--danger-soft);
  animation: shake 0.45s ease;
}
.answer-btn.wrong .answer-label { background: var(--danger); color: var(--on-bright); }

.answer-btn.reveal-correct {
  --answer-face: var(--ok-soft);
  border-color: var(--ok);
  box-shadow: var(--hard-sm);
  animation: nudge 0.6s var(--spring);
}

.answer-btn:disabled { cursor: default; pointer-events: none; }
.answer-btn.dimmed { opacity: 0.45; }

@keyframes popCorrect {
  0% { transform: scale(1); }
  40% { transform: scale(1.05) rotate(-1deg); }
  100% { transform: scale(1); }
}

@keyframes nudge {
  0%, 100% { transform: none; }
  50% { transform: translateX(6px); }
}

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-9px) rotate(-1deg); }
  40% { transform: translateX(9px) rotate(1deg); }
  60% { transform: translateX(-5px); }
  80% { transform: translateX(5px); }
}

/* ---- Question ---- */
.question-text {
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 5.5vw, 1.6rem);
  font-weight: 700;
  line-height: 1.18;
  letter-spacing: -0.015em;
  color: var(--fg);
  text-wrap: balance;
}

/* Progress pips: one speech bubble per question in the station */
.pips {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}

.pip {
  width: 30px;
  height: 26px;
  border-radius: 9px 9px 9px 3px;
  border: 2px solid var(--ink);
  background: var(--card);
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-size: 0.72rem;
  font-weight: 800;
  color: var(--fg-muted);
  transition: all 0.3s var(--spring);
}

.pip--done { background: var(--ok); color: var(--on-bright); }
.pip--miss { background: var(--danger); color: var(--on-bright); }
.pip--current {
  background: var(--primary);
  color: var(--primary-contrast);
  transform: scale(1.22) rotate(-5deg);
  box-shadow: var(--hard-sm);
}

/* ---- Timer ring ---- */
/* ---- Question timer ----
   A slim horizontal bar rather than a ring: the ring cost ~120px of height and
   pushed the answers below the fold on a phone. */
.qtimer {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 14px;
}

.qtimer__track {
  flex: 1;
  height: 14px;
  border: 2px solid var(--ink);
  border-radius: var(--radius-pill);
  background: var(--card);
  overflow: hidden;
}

.qtimer__fill {
  height: 100%;
  width: 100%;
  background: var(--primary);
  border-radius: var(--radius-pill);
  transform-origin: left center;
  transition: transform 0.12s linear, background 0.3s;
}

.qtimer__fill.urgent { background: var(--danger); }

.qtimer__num {
  flex-shrink: 0;
  min-width: 2.4ch;
  text-align: right;
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--fg);
  transition: color 0.3s;
}

.qtimer__num.urgent { color: var(--danger); animation: pulse 0.9s ease-in-out infinite; }

/* Header row: station name on the left, language chips on the right */
.quiz-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

/* The rest of the quiz tightens up to match. */
#quizScreen .station-chip { margin: 0; }
#quizScreen .pips { margin-bottom: 12px; }
#quizScreen .bubble-card { padding: 18px 20px; }
#quizScreen .answers-list { margin-top: 20px; gap: 10px; }
#quizScreen .q-media { max-width: 320px; margin-bottom: 12px; }
#quizScreen .q-media img,
#quizScreen .q-media video { max-height: min(190px, 24vh); }

@keyframes pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.14); }
}

/* ---- Session ticket strip ---- */
/* ---- Top row: language, session, and on the home page the help button ----
   Shared by the home and quiz pages, so the whole app has one header.

   The help button is `position: fixed`, so a row that sits beside one cannot
   simply stretch to the page width — it would slide underneath and the clock
   would be clipped. Hence the modifier: the padding is the help button (38px)
   plus a gap, and both sit on the same 20px grid as the page padding so their
   centres line up. A row with no help button (the quiz) takes the full width. */
.topbar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.topbar--help { padding-right: 46px; }

.topbar .session-banner { margin-bottom: 0; flex: 0 1 auto; min-width: 0; }

/* ---- Language flag ---- */
.langflag { position: relative; flex-shrink: 0; }

.langflag__btn {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 2px solid var(--ink);
  border-radius: 12px;
  background: var(--card);
  box-shadow: var(--hard-sm);
  cursor: pointer;
  transition: transform 0.15s var(--spring), box-shadow 0.15s var(--spring);
}

.langflag__btn:active { transform: translate(2px, 2px); box-shadow: var(--hard-press); }

.langflag__btn svg {
  display: block;
  width: 22px;
  height: 15px;
  border: 1.5px solid var(--ink);
  border-radius: 3px;
}

.langflag__menu {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px;
  background: var(--paper);
  border: var(--stroke) solid var(--ink);
  border-radius: 16px;
  box-shadow: var(--hard);
  animation: fadeInUp 0.22s var(--spring) both;
}

/* Explicit: a display rule on the element itself would beat [hidden]. */
.langflag__menu[hidden] { display: none; }

.langflag__opt {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 14px 7px 8px;
  border: 2px solid transparent;
  border-radius: 11px;
  background: none;
  font-family: var(--font-display);
  font-size: 0.85rem;
  font-weight: 700;
  white-space: nowrap;
  color: var(--fg);
  cursor: pointer;
}

.langflag__opt svg {
  display: block;
  width: 22px;
  height: 15px;
  border: 1.5px solid var(--ink);
  border-radius: 3px;
  flex-shrink: 0;
}

@media (hover: hover) and (pointer: fine) {
  .langflag__opt:hover { background: var(--bg-elev); }
}
.langflag__opt.on { border-color: var(--ink); background: var(--primary-soft); }

/* The account half of the menu. Ruled off rather than merged: this menu is the
   language switcher first, and the two subjects should not read as one list. */
.langflag__sep {
  height: 2px;
  margin: 6px 4px;
  background: var(--border);
}

.langflag__head {
  padding: 2px 8px 4px;
  font-family: var(--font-display);
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--fg);
  opacity: 0.7;
}

/* No flag to align against, so the label takes the space the SVG would. */
.langflag__act { padding-left: 14px; }

.session-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: nowrap;
  padding: 8px 14px;
  margin-bottom: 18px;
  background: var(--card);
  border: 2px solid var(--ink);
  border-radius: var(--radius-pill);
  box-shadow: var(--hard-sm);
  font-size: 0.85rem;
}

.session-banner .session-name {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--fg);
  min-width: 0;
}

.session-banner .session-name span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.session-banner .contest-clock {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-display);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--fg);
  flex-shrink: 0;
}

.session-banner .contest-clock.urgent { color: var(--danger); animation: throb 1s ease-in-out infinite; }

.live-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--ok);
  border: 1.5px solid var(--ink);
  flex-shrink: 0;
  animation: ping 1.8s ease-out infinite;
}

@keyframes ping {
  0% { box-shadow: 0 0 0 0 rgb(var(--primary-rgb) / 0.55); }
  100% { box-shadow: 0 0 0 11px rgb(var(--primary-rgb) / 0); }
}

/* ---- Bottom navigation ----
   Five slots, two either side of the scan button, so scanning sits dead centre
   and stays the obvious action from every screen. */
.tabbar {
  position: fixed;
  bottom: 14px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 1px;
  /* 4px, not 6: the eight pixels the bar gives back are what let "Classement"
     stay whole at 9.6px on a 375px phone. */
  padding: 4px;
  background: var(--card);
  border: var(--stroke) solid var(--ink);
  border-radius: var(--radius-pill);
  box-shadow: var(--hard);
  width: min(460px, calc(100vw - 20px));
}

/* Two equal halves, one either side of the scan button. This is what keeps the
   button the exact centre of the bar: sized from their content the halves came
   out 83px and 121px, and the button sat 19px left of where it belongs. */
.tabbar__side {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 1px;
}

/* `flex: 1 1 auto`, not `1 1 0`: with a zero basis every item took an equal
   share whatever its word, so "Collection" needed 62px in a 57px slot on a
   375px phone and came out "COLLECTIO…" while "Accueil" sat in 57px it did not
   need. Sizing from the content lets the short word in a half hand its slack to
   the long one. What is left is the halves themselves being too narrow on a
   small phone, and that is what the two font steps below are for — measured in
   all three languages from 320px up. A fourth language has to be measured too:
   nothing here fails loudly. */
.tabbar__item {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 2px;
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--fg-muted);
  font-family: var(--font-display);
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  text-decoration: none;
  cursor: pointer;
  transition: color 0.2s, background 0.2s, transform 0.2s var(--spring);
}

.tabbar__item svg { width: 20px; height: 20px; }
.tabbar__item span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
@media (hover: hover) and (pointer: fine) {
  .tabbar__item:hover { color: var(--fg); transform: translateY(-2px); }
}

.tabbar__item.active {
  background: var(--primary);
  color: var(--primary-contrast);
}

.tabbar__scan {
  position: relative;
  flex: 0 0 auto;
  width: 72px;
  height: 72px;
  /* 2px rather than 6px either side: the eight pixels go to the labels, which
     need them more than the button does. */
  margin: -26px 2px 0;
  border-radius: 50%;
  border: var(--stroke) solid var(--ink);
  background: var(--accent);
  /* --ink, not --on-bright: on the sunset and emerald accents --on-bright
     drops to ~2:1 and the label disappears. --ink reads in every skin. */
  color: var(--ink);
  display: grid;
  place-items: center;
  box-shadow: var(--hard);
  cursor: pointer;
  transition: transform 0.2s var(--spring), box-shadow 0.2s var(--spring);
  animation: scanPulse 2.6s ease-out infinite;
}

/* Bigger than the tab icons: it is the only thing in the button now. */
.tabbar__scan svg { width: 34px; height: 34px; }

/* The halo rides on the shadow so it never affects layout. */
@keyframes scanPulse {
  0%, 100% { box-shadow: var(--hard), 0 0 0 0 rgb(var(--accent-rgb) / 0.45); }
  70% { box-shadow: var(--hard), 0 0 0 18px rgb(var(--accent-rgb) / 0); }
}

@media (hover: hover) and (pointer: fine) {
  .tabbar__scan:hover { transform: translateY(-4px) rotate(-6deg); animation: none; }
}
.tabbar__scan:active { transform: translateY(2px); box-shadow: var(--hard-press); animation: none; }

.tabbar__badge {
  position: absolute;
  top: -3px;
  right: -3px;
  min-width: 19px;
  height: 19px;
  padding: 0 4px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--ink);
  background: var(--highlight);
  color: var(--on-bright);
  font-family: var(--font-display);
  font-size: 0.62rem;
  font-weight: 800;
  display: grid;
  place-items: center;
}

.tabbar__item { position: relative; }

/* ---- Stands map ----
   An image the operator has drawn the QR positions onto. A landscape rectangle:
   the frame is the same shape on every phone, so what he lines up while
   authoring is what the player sees, and it leaves the whole home page on one
   screen. Full screen swaps the ratio for the viewport — same element, same
   handlers, only bigger. */
.map-block { margin: 4px 0; }

.map-frame {
  position: relative;
  /* Only a placeholder until the image loads: fitCard() in js/home.js then
     sets the card's pixel size. */
  aspect-ratio: 3 / 2;
  overflow: hidden;
  /* Not --paper. The card is now as wide as the column while the plan keeps its
     own proportions, so there are bands beside it whenever the two differ — and
     a band of --paper next to a light map is the seam this whole viewer was
     rewritten to remove. js/home.js samples the image's own corner into
     --map-ground, so the bands are the map's own background whatever the skin. */
  background: var(--map-ground, var(--paper));
  border: var(--stroke) solid var(--ink);
  border-radius: 22px;
  box-shadow: var(--hard);
  /* Overridden from JS: none once zoomed in, so the pan gesture is only taken
     when it does something. */
  touch-action: pan-y;
  cursor: grab;
  user-select: none;
  animation: fadeInUp 0.5s var(--spring) both;
  animation-delay: 0.15s;
}

.map-block.is-zoomed .map-frame { cursor: move; }

.map-frame img {
  position: absolute;
  top: 0;
  left: 0;
  max-width: none;          /* the transform sizes it, not the layout */
  transform-origin: 0 0;
  -webkit-user-drag: none;
}

.map-zoom { position: absolute; right: 10px; bottom: 10px; display: flex; gap: 6px; }

/* Controls sit on the map itself, so they carry their own border and shadow to
   stay legible over any image. */
.map-btn {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-weight: 800;
  line-height: 1;
  color: var(--ink);
  background: var(--card);
  border: var(--stroke) solid var(--ink);
  border-radius: 11px;
  box-shadow: var(--hard-sm);
  cursor: pointer;
  transition: transform 0.1s var(--spring), box-shadow 0.1s var(--spring);
}

.map-btn:active { transform: translate(2px, 2px); box-shadow: var(--hard-press); }

.map-btn svg { display: block; width: 18px; height: 18px; }

.map-btn--full { position: absolute; top: 10px; right: 10px; }

.map-hint { margin-top: 6px; font-size: 0.76rem; color: var(--fg-muted); text-align: center; }

/* Short screens — an iPhone SE is 667px tall — were 40-odd pixels over. Taking
   them back from the banner and the hint is better than scrolling the map, and
   the zoom controls still show what the map does. */
@media (max-height: 700px) {
  .app-logo--home { max-height: 96px; }
  .map-hint { display: none; }
}

/* ---- Podium screen ----
   Shown to a slot's top three once it is over. It is also the proof a winner
   shows at the stand, so the ticket block is deliberately plain and legible in
   a screenshot: no gradients over text, no decoration that could be mistaken
   for a value. */
.winner { text-align: center; padding: 8px 0 4px; }

.winner__medal {
  font-size: 4.2rem;
  line-height: 1;
  animation: popCorrect 0.6s var(--spring) both;
}

.winner__title {
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 7vw, 2rem);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  margin: 6px 0 2px;
  color: var(--fg);
}

.winner__lucky {
  font-family: var(--font-display);
  font-size: 1.02rem;
  font-weight: 700;
  color: var(--primary-strong);
  text-wrap: balance;
  margin-bottom: 16px;
}

.winner__ticket {
  text-align: left;
  background: var(--card);
  border: var(--stroke) solid var(--ink);
  border-radius: 18px;
  box-shadow: var(--hard);
  padding: 4px 16px;
  transform: rotate(-0.6deg);
}

.winner__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding: 9px 0;
  border-bottom: 2px dashed var(--border);
}

.winner__row:last-child { border-bottom: none; }

/* --fg, not --fg-muted: this block is read off a screenshot by someone handing
   out a prize, and muted small caps on a saturated card sits near 3:1. */
.winner__row span {
  font-family: var(--font-display);
  font-size: 0.68rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--fg);
  opacity: 0.72;
  flex-shrink: 0;
}

.winner__row b {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 800;
  color: var(--fg);
  text-align: right;
  overflow-wrap: anywhere;
}

.winner__proof {
  margin: 18px 0 0;
  padding: 13px 15px;
  background: var(--highlight-soft);
  border: 2px dashed var(--ink);
  border-radius: 16px;
  font-weight: 700;
  font-size: 0.92rem;
  color: var(--fg);
  text-wrap: balance;
}

.winner__howto { margin-top: 8px; font-size: 0.74rem; color: var(--fg-muted); }

/* ---- Footer band ----
   Deliberately unstyled: full width, natural proportions, no border or shadow.
   Whatever it needs to say is in the image, so framing it would only fight it. */
.home-footer {
  display: block;
  width: 100%;
  height: auto;
  margin: 14px 0 0;
  border-radius: 14px;
}

.map-block.map--full {
  position: fixed;
  inset: 0;
  z-index: 90;              /* over the tab bar, under the walkthrough and gate */
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 14px;
  background: var(--bg);
}

/* The stage is the room the card may take; the card is centred in it, so an
   image whose proportions do not fill the width sits in the middle with page
   ground either side rather than with bands inside its own border. */
.map-stage { display: grid; place-items: center; }

.map-block.map--full .map-stage {
  flex: 1;
  min-height: 0;            /* let it shrink inside the flex column */
  display: grid;
  place-items: center;
}

/* Full screen: the card fills the stage and the map covers the card (fitCard()
   and reset() in js/home.js). It used to be built to the image's own
   proportions so the frame hugged the plan, which was how bands inside the
   border were avoided before --map-ground existed — and it left a square plan
   displayed at half the height a phone had to offer. */
.map-block.map--full .map-frame {
  aspect-ratio: auto;
  animation: none;
}

/* The page behind must not scroll under a full-screen map, and the help button
   sits exactly where the close button lands. */
body.map-locked { overflow: hidden; }
body.map-locked .help-btn { display: none; }

/* ---- Language switcher ---- */
.lang-switcher { display: flex; gap: 6px; justify-content: center; margin-bottom: 16px; }

.lang-btn {
  padding: 6px 14px;
  border: 2px solid var(--ink);
  border-radius: var(--radius-pill);
  background: var(--card);
  font-family: var(--font-display);
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--fg);
  cursor: pointer;
  transition: all 0.2s var(--spring);
}

@media (hover: hover) and (pointer: fine) {
  .lang-btn:hover { transform: translateY(-2px); }
}
.lang-btn.active {
  background: var(--fg);
  color: var(--bg);
  box-shadow: var(--hard-sm);
}

/* ---- Inputs ---- */
.input, select.input, textarea.input {
  width: 100%;
  padding: 14px 18px;
  border: var(--stroke) solid var(--ink);
  border-radius: 16px;
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 600;
  color: var(--fg);
  background: var(--card);
  outline: none;
  transition: box-shadow 0.18s var(--spring), transform 0.18s var(--spring);
}

.input:focus { box-shadow: var(--hard); transform: translate(-2px, -2px); }
.input::placeholder { color: var(--fg-muted); font-weight: 500; }

label {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.85rem;
  color: var(--fg);
  margin-bottom: 6px;
}

.form-group { margin-bottom: 16px; }
.form-row { display: flex; gap: 12px; flex-wrap: wrap; }
.form-row > * { flex: 1; min-width: 160px; }

textarea.input { resize: vertical; min-height: 64px; line-height: 1.45; }

/* ---- Result / score ---- */
.score-display { text-align: center; padding: 18px 0; animation: fadeInUp 0.5s var(--ease-out); }

.result-icon {
  font-size: 4.4rem;
  line-height: 1;
  animation: iconPop 0.6s var(--spring) both;
}

@keyframes iconPop {
  from { opacity: 0; transform: scale(0.3) rotate(-20deg); }
  to { opacity: 1; transform: none; }
}

.result-msg {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 8vw, 2.4rem);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -0.02em;
  color: var(--ok);
  margin: 6px 0;
}

.result-msg.wrong { color: var(--danger); }

.score-number {
  font-family: var(--font-display);
  font-size: clamp(3.4rem, 18vw, 5rem);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 0.9;
  color: var(--primary-strong);
  -webkit-text-stroke: 2px var(--ink);
  paint-order: stroke fill;
}

.score-label {
  font-family: var(--font-display);
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--fg-muted);
  margin-top: 6px;
}

/* ---- Trading card (station reward / collection) ---- */
.tcard {
  position: relative;
  background: var(--card);
  border: var(--stroke) solid var(--ink);
  border-radius: 22px;
  box-shadow: var(--hard);
  overflow: hidden;
  transition: transform 0.28s var(--spring), box-shadow 0.28s var(--spring);
}

/* Holographic sweep across unlocked cards */
.tcard--unlocked::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(115deg, transparent 30%, rgb(255 255 255 / 0.42) 47%, transparent 62%);
  background-size: 250% 250%;
  animation: holo 5.5s ease-in-out infinite;
}

@keyframes holo {
  0%, 100% { background-position: 120% 0; }
  50% { background-position: -30% 0; }
}

.tcard__top {
  padding: 16px 18px;
  background: var(--primary);
  color: var(--primary-contrast);
  border-bottom: var(--stroke) solid var(--ink);
  display: flex;
  align-items: center;
  gap: 12px;
}

.tcard__icon {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  font-size: 1.5rem;
  border-radius: 13px;
  border: 2px solid var(--ink);
  background: var(--card);
}

.tcard__name {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1.1;
}

.tcard__body { padding: 18px; }

.tcard__title {
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-weight: 800;
  color: var(--fg);
  line-height: 1.12;
  margin-bottom: 10px;
  text-wrap: balance;
}

.tcard__text { color: var(--fg); line-height: 1.55; font-size: 0.96rem; }
.tcard__text p + p { margin-top: 0.7em; }

.tcard__img {
  width: 100%;
  max-height: 190px;
  object-fit: cover;
  display: block;
  border-bottom: var(--stroke) solid var(--ink);
}

.tcard__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 18px;
  border-top: 2px dashed var(--ink);
  font-family: var(--font-display);
  font-size: 0.76rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--fg-muted);
}

/* Reveal: the card flips in when a station is completed */
.tcard--reveal { animation: cardReveal 0.85s var(--spring) both; }

@keyframes cardReveal {
  0% { opacity: 0; transform: perspective(900px) rotateY(-92deg) scale(0.85); }
  60% { opacity: 1; }
  100% { opacity: 1; transform: perspective(900px) rotateY(0) scale(1); }
}

/* ---- Collection album ---- */
.album-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}

.album-count {
  font-family: var(--font-display);
  font-size: clamp(2.2rem, 11vw, 3rem);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--primary-strong);
  -webkit-text-stroke: 2px var(--ink);
  paint-order: stroke fill;
}

.album-bar {
  height: 16px;
  border: 2px solid var(--ink);
  border-radius: var(--radius-pill);
  background: var(--primary-soft);
  overflow: hidden;
  margin: 14px 0 22px;
}

.album-bar__fill {
  height: 100%;
  background: var(--primary);
  border-right: 2px solid var(--ink);
  border-radius: var(--radius-pill);
  width: 0;
  transition: width 1s var(--spring);
}

.album-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 14px;
}

.album-item {
  position: relative;
  aspect-ratio: 3 / 4;
  border-radius: 18px;
  border: var(--stroke) solid var(--ink);
  padding: 14px 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  text-align: center;
  cursor: pointer;
  overflow: hidden;
  background: var(--card);
  box-shadow: var(--hard-sm);
  animation: cardIn 0.5s var(--spring) both;
  transition: transform 0.26s var(--spring), box-shadow 0.26s var(--spring);
}

.album-item:nth-child(1) { animation-delay: 0.04s; }
.album-item:nth-child(2) { animation-delay: 0.08s; }
.album-item:nth-child(3) { animation-delay: 0.12s; }
.album-item:nth-child(4) { animation-delay: 0.16s; }
.album-item:nth-child(5) { animation-delay: 0.2s; }
.album-item:nth-child(6) { animation-delay: 0.24s; }
.album-item:nth-child(n + 7) { animation-delay: 0.28s; }

@keyframes cardIn {
  from { opacity: 0; transform: translateY(20px) rotate(-3deg) scale(0.94); }
}

@media (hover: hover) and (pointer: fine) {
  .album-item--unlocked:hover { transform: translate(-3px, -4px) rotate(-1.5deg); box-shadow: var(--hard-lg); }
}

.album-item--locked {
  border-style: dashed;
  background: transparent;
  box-shadow: none;
  cursor: default;
  color: var(--fg-muted);
}

.album-item__icon {
  width: 58px;
  height: 58px;
  display: grid;
  place-items: center;
  font-size: 1.9rem;
  line-height: 1;
  border-radius: 17px;
  border: 2px solid var(--ink);
  background: var(--primary-soft);
  transition: transform 0.3s var(--spring);
}

@media (hover: hover) and (pointer: fine) {
  .album-item--unlocked:hover .album-item__icon { transform: rotate(-8deg) scale(1.06); }
}

.album-item--locked .album-item__icon {
  background: transparent;
  border-style: dashed;
  border-color: var(--border);
  opacity: 0.6;
}

.album-item__name {
  font-family: var(--font-display);
  font-size: 0.82rem;
  font-weight: 700;
  line-height: 1.15;
  color: var(--fg);
}

.album-item--locked .album-item__name { color: var(--fg-muted); }

.album-item__meta {
  font-family: var(--font-display);
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--fg-muted);
}

/* Corner check badge — a diagonal ribbon gets clipped by overflow:hidden */
.album-item__badge {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 2px solid var(--ink);
  background: var(--ok);
  color: var(--on-bright);
  font-family: var(--font-display);
  font-size: 0.78rem;
  font-weight: 800;
  animation: badgePop 0.5s var(--spring) both 0.35s;
}

@keyframes badgePop {
  from { opacity: 0; transform: scale(0.3) rotate(-40deg); }
}

/* ---- Confetti ---- */
.confetti-piece {
  position: fixed;
  width: 10px;
  height: 10px;
  z-index: 1000;
  pointer-events: none;
  border: 1.5px solid var(--ink);
  animation: confettiFall 2s ease-out forwards;
}

@keyframes confettiFall {
  0% { opacity: 1; transform: translateY(0) rotateZ(0deg); }
  100% { opacity: 0; transform: translateY(105vh) rotateZ(760deg); }
}

/* ============================================================
   LEADERBOARD
   ============================================================
   The board wears the session's own skin, like every other page. It used to
   have a palette of its own — a dark "arcade cabinet" stage with a neon glowing
   title, scanlines over the glass, and gold/silver/bronze hardcoded outside the
   skin — which read as a different app one tap from the home page and, with the
   podium avatars added, simply had too much colour in it at once.

   The rule now: **the avatars are the colour**. They are 3D, saturated and
   three of them sit at the top of the screen, so everything around them is
   built from the skin's own tokens and kept quiet. The medals are one hue in
   three weights rather than three metals.

   It still doubles as the projection screen, which is why the type stays huge
   and the layout stays centred. */
.lb-body {
  background: var(--bg);
  color: var(--fg);
  min-height: 100vh;
  font-family: var(--font-body);
}

.lb-body .page { max-width: 100%; padding-bottom: 32px; }

.lb-header { text-align: center; padding: 18px 0 12px; }

/* Exactly the home page's .title-main treatment — flat --primary-strong, no
   shadow — so the two big titles in this app are the same object. The glow and
   its 4s pulse are gone; so is the hard offset shadow I first put in their
   place, because --ink and --primary-strong are both dark in every skin and the
   result was a muddy double image rather than a sticker edge. */
.lb-title {
  font-family: var(--font-display);
  font-size: clamp(2.4rem, 7vw, 5rem);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -0.03em;
  line-height: 0.95;
  color: var(--primary-strong);
  overflow-wrap: break-word;
}

/* The board doubles as a projection screen — the mobile nav would be noise. */
@media (min-width: 900px) {
  .lb-body .tabbar { display: none; }
  .lb-body .page { padding-bottom: 32px; }
}

/* --fg at 0.7, never --fg-muted: measured on --paper that token sits at 3.5:1
   in emerald and 4.4:1 in studentefoire, and this line carries the slot name. */
.lb-subtitle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 10px;
  font-family: var(--font-display);
  font-size: 0.95rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: rgb(var(--fg-rgb) / 0.7);
}

.lb-clock {
  font-family: var(--font-display);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--fg);
  background: var(--card);
  padding: 3px 12px;
  border: 2px solid var(--ink);
  border-radius: var(--radius-pill);
}

/* The last minutes are the one place a colour change earns its keep. */
.lb-clock.urgent { color: var(--danger); border-color: var(--danger); animation: throb 1s ease-in-out infinite; }
.lb-clock.over { color: rgb(var(--fg-rgb) / 0.45); border-color: rgb(var(--fg-rgb) / 0.25); }

.final-badge {
  padding: 4px 14px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--ink);
  background: var(--primary);
  color: var(--primary-contrast);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 0.8rem;
  letter-spacing: 0.12em;
}

/* Podium */
/* The floor. The blocks have no bottom border — they are steps rising out of
   the ground — which worked when they were bright shapes on a dark stage and
   looked like three truncated rectangles once everything went light. One ink
   line under all three puts them back on something. */
.podium {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 14px;
  padding: 26px 0 0;
  /* Same width as .rankings, so the floor line ends where the standings begin
     rather than running edge to edge and reading as a page divider. */
  max-width: 880px;
  margin: 0 auto 34px;
  border-bottom: var(--stroke) solid var(--ink);
}

.podium-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 210px;
  animation: slideUp 0.7s var(--spring) both;
}

.podium-slot:nth-child(1) { animation-delay: 0.25s; }
.podium-slot:nth-child(2) { animation-delay: 0.05s; }
.podium-slot:nth-child(3) { animation-delay: 0.45s; }

.podium-crown {
  font-size: 2rem;
  line-height: 1;
  margin-bottom: -6px;
  animation: crownFloat 2.6s ease-in-out infinite;
}

@keyframes crownFloat {
  0%, 100% { transform: translateY(0) rotate(-6deg); }
  50% { transform: translateY(-9px) rotate(6deg); }
}

/* The player's avatar, and the reason the rest of this page went quiet. Round
   and ringed so it reads as a portrait; white behind it because the art is
   drawn on white and a skin-coloured disc would show through the edges. */
.podium-face {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  margin-bottom: 9px;
  border: var(--stroke) solid var(--ink);
  box-shadow: var(--hard-sm);
  background: #fff;
  object-fit: cover;
}

.podium-slot.gold .podium-face { width: 120px; height: 120px; }

/* No avatar file for this animal, or no avatar at all: the initials take the
   same disc rather than leaving a hole in the podium. */
.podium-face--initials {
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: 800;
  color: var(--ink);
  background: rgb(var(--primary-rgb) / 0.35);
}

.podium-name {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 800;
  margin-bottom: 2px;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-transform: uppercase;
}

.podium-score {
  font-family: var(--font-display);
  font-size: 1.7rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--primary-strong);
}

/* One hue, three weights. Gold/silver/bronze were three colours from outside
   the skin, on a page that already had a glowing title and three saturated
   avatars on it — the height of the block and the number on it say the same
   thing without adding two more palettes. */
.podium-block {
  width: 100%;
  margin-top: 10px;
  border: var(--stroke) solid var(--ink);
  border-bottom: none;
  border-radius: 14px 14px 0 0;
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-size: 2.4rem;
  font-weight: 800;
  color: var(--ink);
}

.podium-slot.gold .podium-block { height: 132px; background: var(--primary); color: var(--primary-contrast); }
.podium-slot.silver .podium-block { height: 96px; background: rgb(var(--primary-rgb) / 0.5); }
.podium-slot.bronze .podium-block { height: 70px; background: rgb(var(--primary-rgb) / 0.26); }

@keyframes slideUp {
  from { opacity: 0; transform: translateY(48px); }
}

/* Rankings */
.rankings { max-width: 880px; margin: 0 auto; }

.rank-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 18px;
  background: var(--card);
  border: 2px solid var(--ink);
  border-radius: 16px;
  box-shadow: var(--hard-sm);
  margin-bottom: 9px;
  animation: fadeInUp 0.45s var(--ease-out) both;
  transition: transform 0.3s var(--spring), box-shadow 0.3s;
}

@media (hover: hover) and (pointer: fine) {
  .rank-row:hover { transform: translate(-2px, -2px); box-shadow: var(--hard); }
}

.rank-pos {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 800;
  width: 2.2ch;
  text-align: right;
  color: rgb(var(--fg-rgb) / 0.55);
  font-variant-numeric: tabular-nums;
}

.rank-name {
  flex: 1;
  min-width: 0;
  font-family: var(--font-display);
  font-size: 1.02rem;
  font-weight: 700;
  text-transform: uppercase;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rank-stats {
  font-size: 0.8rem;
  color: rgb(var(--fg-rgb) / 0.6);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.rank-score {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 800;
  color: var(--primary-strong);
  font-variant-numeric: tabular-nums;
  min-width: 3.5ch;
  text-align: right;
}

.rank-delta {
  font-size: 0.72rem;
  font-weight: 800;
  width: 3.4ch;
  animation: deltaUp 0.4s var(--spring) both;
}

.rank-delta--up { color: var(--ok); }
.rank-delta--down { color: var(--danger); opacity: 0.8; }
.rank-delta--flat { color: rgb(var(--fg-rgb) / 0.25); }

@keyframes deltaUp {
  0% { transform: translateY(9px) scale(0.6); opacity: 0; }
  100% { transform: none; opacity: 1; }
}

.update-flash { animation: flashRow 0.9s var(--ease-out); }

@keyframes flashRow {
  0% { background: rgb(var(--primary-rgb) / 0.28); }
  100% { background: var(--card); }
}

.score-pop { animation: scorePop 0.6s var(--spring); }

@keyframes scorePop {
  0% { transform: scale(1); }
  45% { transform: scale(1.32); }
  100% { transform: scale(1); }
}

.lb-waiting {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 42vh;
  text-align: center;
  color: rgb(var(--fg-rgb) / 0.55);
  font-family: var(--font-display);
  font-size: 1.3rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.lb-waiting .waiting-icon {
  font-size: 4.4rem;
  margin-bottom: 20px;
  animation: wobble 2.4s ease-in-out infinite;
}

/* Ticker of the top scorers. Kept — it fills the projection screen between two
   score changes — but in the page's own ink rather than in neon. */
.marquee {
  overflow: hidden;
  border-block: 2px solid rgb(var(--fg-rgb) / 0.14);
  padding: 9px 0;
  margin-bottom: 22px;
}

.marquee__track {
  display: flex;
  gap: 44px;
  width: max-content;
  animation: marquee 26s linear infinite;
  font-family: var(--font-display);
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: rgb(var(--fg-rgb) / 0.55);
  white-space: nowrap;
}

.marquee__track b { color: var(--primary-strong); }

@keyframes marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* ============================================================
   ADMIN
   ============================================================ */
.admin-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 22px;
}

.admin-header h1 { margin: 0; font-size: clamp(1.6rem, 4vw, 2.2rem); text-transform: uppercase; }
.admin-actions { display: flex; gap: 8px; flex-wrap: wrap; }

.tabs {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 22px;
}

.tab-btn {
  padding: 10px 18px;
  border: 2px solid var(--ink);
  border-radius: var(--radius-pill);
  background: var(--card);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--fg);
  cursor: pointer;
  transition: all 0.2s var(--spring);
}

@media (hover: hover) and (pointer: fine) {
  .tab-btn:hover { transform: translateY(-2px); box-shadow: var(--hard-sm); }
}
.tab-btn.active { background: var(--primary); color: var(--primary-contrast); box-shadow: var(--hard-sm); }

.tab-content { display: none; animation: fadeInUp 0.3s var(--ease-out); }
.tab-content.active { display: block; }

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(136px, 1fr));
  gap: 12px;
  margin-bottom: 24px;
}

.stat-box {
  background: var(--card);
  border: var(--stroke) solid var(--ink);
  border-radius: 18px;
  padding: 16px;
  text-align: center;
  box-shadow: var(--hard-sm);
}

.stat-value {
  font-family: var(--font-display);
  font-size: 2.1rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--primary-strong);
  line-height: 1;
}

.stat-label {
  font-family: var(--font-display);
  font-size: 0.72rem;
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-top: 6px;
}

/* Station list (admin) */
.station-card {
  background: var(--card);
  border: var(--stroke) solid var(--ink);
  border-radius: 20px;
  box-shadow: var(--hard-sm);
  margin-bottom: 16px;
  overflow: hidden;
}

.station-card__head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  background: var(--primary-soft);
  border-bottom: 2px solid var(--ink);
  flex-wrap: wrap;
}

.station-card__icon {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  font-size: 1.3rem;
  border: 2px solid var(--ink);
  border-radius: 12px;
  background: var(--card);
  flex-shrink: 0;
}

.station-card__name {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.05rem;
  flex: 1;
  min-width: 140px;
}

.station-card__body { padding: 14px 18px; }

.qlist { display: flex; flex-direction: column; gap: 8px; }

.qrow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 2px solid var(--border);
  border-radius: 12px;
  background: var(--paper);
  flex-wrap: wrap;
}

.qrow__n {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 0.8rem;
  color: var(--fg-muted);
  width: 22px;
  flex-shrink: 0;
}

.qrow__text { flex: 1; min-width: 180px; font-size: 0.92rem; font-weight: 600; }
.qrow__text small { display: block; color: var(--fg-muted); font-weight: 500; margin-top: 2px; }

.lang-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--ink);
  font-family: var(--font-display);
  font-size: 0.68rem;
  font-weight: 800;
  margin-right: 4px;
}

.lang-badge.has { background: var(--ok); color: var(--on-bright); }
.lang-badge.missing { background: transparent; color: var(--fg-muted); border-color: var(--border); }

.actions-cell { display: flex; gap: 6px; flex-wrap: wrap; }

.correct-marker { color: var(--ok); font-weight: 800; margin-left: 4px; }

/* Session cards */
.session-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: 16px;
}

.session-card {
  background: var(--card);
  border: var(--stroke) solid var(--ink);
  border-radius: 20px;
  padding: 18px;
  box-shadow: var(--hard-sm);
  display: flex;
  flex-direction: column;
  gap: 11px;
}

.session-card.is-running { box-shadow: var(--hard); border-color: var(--primary); }

.session-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}

.session-card__title { font-family: var(--font-display); font-weight: 800; font-size: 1.05rem; }

.session-card__meta {
  font-size: 0.82rem;
  color: var(--fg-muted);
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.session-card__clock {
  font-family: var(--font-display);
  font-size: 1.7rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--primary-strong);
}

.session-card__clock.urgent { color: var(--danger); }
.session-card__actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: auto; }

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border: 2px solid var(--ink);
  border-radius: var(--radius-pill);
  font-family: var(--font-display);
  font-size: 0.68rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  white-space: nowrap;
}

.badge--draft { background: var(--border); color: var(--fg); }
.badge--running { background: var(--ok); color: var(--on-bright); }
.badge--scheduled { background: var(--highlight); color: var(--on-bright); }
.badge--ended, .badge--closed { background: var(--danger); color: var(--on-bright); }

/* Theme picker */
.theme-picker { display: flex; gap: 10px; flex-wrap: wrap; }

.theme-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 12px;
  border: var(--stroke) solid var(--ink);
  border-radius: 16px;
  background: var(--card);
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--fg);
  transition: all 0.2s var(--spring);
}

@media (hover: hover) and (pointer: fine) {
  .theme-option:hover { transform: translateY(-3px); }
}
.theme-option.active { box-shadow: var(--hard-sm); background: var(--primary-soft); }
.theme-option input { display: none; }
.theme-option .theme-hero { font-size: 1.4rem; line-height: 1; }

.theme-swatch { display: flex; gap: 3px; }
.theme-swatch span { width: 14px; height: 14px; border: 1.5px solid var(--ink); border-radius: 4px; }

/* Modal */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgb(0 0 0 / 0.55);
  backdrop-filter: blur(5px);
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px;
  animation: fadeIn 0.2s ease;
  overflow-y: auto;
}

.modal {
  background: var(--paper);
  border: var(--stroke) solid var(--ink);
  border-radius: 22px;
  padding: 26px;
  width: 100%;
  max-width: 700px;
  max-height: 88vh;
  overflow-y: auto;
  box-shadow: var(--hard-lg);
  animation: modalIn 0.35s var(--spring);
  margin: auto;
}

.modal h2 { margin-bottom: 18px; text-transform: uppercase; }

.modal-footer {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  margin-top: 22px;
  padding-top: 16px;
  border-top: 2px dashed var(--ink);
  flex-wrap: wrap;
}

.fieldset {
  border: 2px solid var(--ink);
  border-radius: 16px;
  padding: 16px;
  margin-bottom: 16px;
  background: var(--card);
}

.fieldset > legend {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 0.78rem;
  padding: 2px 10px;
  border: 2px solid var(--ink);
  border-radius: var(--radius-pill);
  background: var(--highlight);
  color: var(--on-bright);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* QR grid */
.qr-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
  gap: 16px;
  margin-top: 18px;
}

.qr-card {
  background: #fff;
  border: var(--stroke) solid var(--ink);
  border-radius: 18px;
  padding: 14px;
  text-align: center;
  box-shadow: var(--hard-sm);
}

.qr-card svg { max-width: 100%; height: auto; }

.qr-card .qr-label {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.86rem;
  /* The card is deliberately white for scanning and printing, so this ink is
     fixed rather than themed. */
  color: #222;
  margin-top: 8px;
}

.qr-card .qr-sub { font-size: 0.74rem; color: #777; margin-top: 2px; }

/* Login overlay */
.login-overlay {
  position: fixed;
  inset: 0;
  z-index: 500;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 24px;
  text-align: center;
}

.login-overlay .login-box { width: 100%; max-width: 330px; }
.login-overlay .login-error { color: var(--danger); font-weight: 700; margin-top: 12px; min-height: 22px; }

/* ---- Session logo ---- */
.app-logo {
  display: block;
  max-width: min(320px, 78vw);
  max-height: 118px;
  width: auto;
  margin: 0 auto 16px;
  object-fit: contain;
  animation: fadeInUp 0.6s var(--spring) both;
}

/* The home page has room for a wider banner than the entry overlay does. */
.app-logo--home { max-width: min(420px, 94vw); max-height: 130px; margin-bottom: 10px; }

.lb-logo { max-height: 92px; max-width: min(400px, 66vw); margin-bottom: 10px; }

.logo-preview { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

.logo-preview img {
  max-height: 56px;
  max-width: 180px;
  background: var(--paper);
  border: 2px solid var(--ink);
  border-radius: 12px;
  padding: 6px;
}

/* ---- Centred screens & notices ---- */
.screen-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 72vh;
  text-align: center;
  animation: fadeInUp 0.55s var(--ease-out);
}

.notice { text-align: center; padding: 32px 16px; animation: fadeInUp 0.5s var(--ease-out); }
.notice .icon { font-size: 4rem; margin-bottom: 14px; animation: wobble 2.6s ease-in-out infinite; }
.notice h2 { margin-bottom: 10px; text-transform: uppercase; }

@keyframes wobble {
  0%, 100% { transform: rotate(-5deg); }
  50% { transform: rotate(5deg); }
}

/* ---- QR scanner ---- */
.scanner-shell {
  width: 100%;
  max-width: 380px;
  margin: 0 auto;
  border: var(--stroke) solid var(--ink);
  border-radius: 22px;
  box-shadow: var(--hard);
  overflow: hidden;
  background: #000;
}

#qr-reader { width: 100%; border: none !important; }
#qr-reader video { display: block; width: 100% !important; }
#qr-reader__scan_region { min-height: 240px; }
#qr-reader__dashboard { padding: 8px !important; background: var(--card); }

#qr-reader__dashboard button {
  font-family: var(--font-display) !important;
  font-weight: 700 !important;
  background: var(--primary) !important;
  color: var(--primary-contrast) !important;
  border: 2px solid var(--ink) !important;
  padding: 9px 18px !important;
  border-radius: var(--radius-pill) !important;
  cursor: pointer !important;
}

.scan-hint { color: var(--fg-muted); margin-top: 12px; font-size: 0.9rem; font-weight: 600; }

/* ---- Toast ---- */
.toast {
  position: fixed;
  bottom: 96px;
  left: 50%;
  transform: translateX(-50%);
  padding: 13px 22px;
  background: var(--primary);
  color: var(--primary-contrast);
  border: var(--stroke) solid var(--ink);
  border-radius: var(--radius-pill);
  font-family: var(--font-display);
  font-weight: 700;
  box-shadow: var(--hard);
  animation: toastIn 0.35s var(--spring), toastOut 0.3s ease 2.6s forwards;
  z-index: 200;
  max-width: calc(100vw - 32px);
  text-align: center;
}

.toast.error { background: var(--danger); color: var(--on-bright); }

@keyframes toastIn { from { opacity: 0; transform: translate(-50%, 26px) scale(0.9); } }
@keyframes toastOut { to { opacity: 0; transform: translate(-50%, 16px); } }

.spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid rgb(0 0 0 / 0.25);
  border-top-color: currentColor;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* ---- Radio group ---- */
.radio-group { display: flex; gap: 12px; flex-wrap: wrap; }

.radio-option { display: flex; align-items: center; gap: 6px; cursor: pointer; font-weight: 700; }
.radio-option input[type='radio'] { accent-color: var(--primary); width: 18px; height: 18px; }

/* ============================================================
   Entry gate — language, then nickname
   Injected by js/theme.js on a first visit, so every page can
   assume a player has both before it renders anything.
   ============================================================ */
.gate {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: var(--bg);
  overflow-y: auto;
  display: grid;
  place-items: center;
  padding: 26px 20px calc(26px + env(safe-area-inset-bottom));
  animation: fadeIn 0.3s var(--ease-out);
}

/* Two steps down, and both boundaries come from where French actually stops
   fitting rather than from a round number. The binding pair is always
   "Classement" + "Sessions": it wants 126px at 9.6px, and half the bar is
   (viewport - 107) / 2, so 9.6px needs 375px of viewport to keep real slack.
   Below that, 8.8px; below 344px, 8px with the tracking dropped as well, which
   is the last few pixels a 320px phone needs. Verified every 4px from 320 to
   460 in the three languages: no label clipped, and the scan button stays the
   exact centre of the bar. */
@media (max-width: 374px) {
  .tabbar__item { font-size: 0.55rem; padding: 8px 1px; letter-spacing: 0.02em; }
}

@media (max-width: 343px) {
  .tabbar__item { font-size: 0.5rem; letter-spacing: 0; }
}

/* The page behind must not scroll under the gate on iOS. */
body.gate-open { overflow: hidden; }

.gate--out { animation: gateOut 0.36s var(--ease-out) forwards; }
@keyframes gateOut { to { opacity: 0; transform: scale(1.03); } }

.gate__inner { width: 100%; max-width: 380px; }
.gate__mark { width: 108px; margin: 0 auto 14px; }
.gate__logo:empty { display: none; }
/* The overlay is narrow — it does not inherit the home banner's width. */
.gate__logo-img { max-height: 92px; max-width: min(260px, 62vw); margin-bottom: 14px; }

.gate__step { animation: fadeInUp 0.4s var(--spring) both; }

.gate__dots { display: flex; justify-content: center; gap: 7px; margin-bottom: 16px; }

.gate__dots i {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 2px solid var(--ink);
  background: var(--card);
  transition: background 0.25s, transform 0.25s var(--spring);
}

.gate__dots i.on { background: var(--primary); transform: scale(1.15); }

.gate__title {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 8vw, 2.3rem);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -0.02em;
  line-height: 1;
  text-align: center;
  color: var(--primary-strong);
  text-wrap: balance;
}

.gate__sub {
  text-align: center;
  color: var(--fg-muted);
  font-weight: 600;
  font-size: 0.92rem;
  margin: 10px 0 22px;
  text-wrap: balance;
}

.gate__langs { display: flex; flex-direction: column; gap: 12px; }

.gate__lang {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  padding: 15px 20px;
  background: var(--card);
  border: var(--stroke) solid var(--ink);
  border-radius: 20px;
  box-shadow: var(--hard);
  cursor: pointer;
  color: var(--fg);
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  transition: transform 0.2s var(--spring), box-shadow 0.2s var(--spring), background 0.2s;
  animation: stepIn 0.5s var(--spring) both;
}

.gate__lang:nth-child(1) { animation-delay: 0.06s; transform: rotate(-1deg); }
.gate__lang:nth-child(2) { animation-delay: 0.13s; transform: rotate(0.8deg); }
.gate__lang:nth-child(3) { animation-delay: 0.2s; transform: rotate(-0.6deg); }

@media (hover: hover) and (pointer: fine) {
  .gate__lang:hover { transform: translate(-2px, -3px) rotate(-1.5deg); box-shadow: var(--hard-lg); }
}
.gate__lang:active { transform: translate(2px, 2px); box-shadow: var(--hard-press); }
.gate__lang.active { background: var(--primary); color: var(--primary-contrast); }

.gate__flag {
  width: 46px;
  height: 46px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--primary-strong);
  background: var(--paper);
  border: 2px solid var(--ink);
  border-radius: 14px;
}

.gate__lang.active .gate__flag { color: var(--primary-strong); }

.gate__card {
  background: var(--card);
  border: var(--stroke) solid var(--ink);
  border-radius: var(--radius-lg);
  box-shadow: var(--hard);
  padding: 22px;
  transform: rotate(-0.6deg);
}

.gate__back,
.gate__have-code,
.gate__restore-back { display: block; margin-inline: auto; }

/* The avatar that comes with a drawn name. Circular and ringed so it reads as a
   portrait rather than a picture, and hidden outright when the file is missing —
   an incomplete set should degrade quietly, not leave a broken frame. */
.gate__avatar {
  display: block;
  width: 132px;
  height: 132px;
  margin: 0 auto 12px;
  border: var(--stroke) solid var(--ink);
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--hard-sm);
  object-fit: cover;
}

.gate__avatar.hidden { display: none; }

/* ---- Generated nickname ----
   The drawn name stands where the text field would be, so the step keeps its
   shape between the two modes and nothing jumps when the operator switches. */
.gate__name {
  padding: 14px 12px;
  border: 2px dashed var(--ink);
  border-radius: 14px;
  background: var(--paper);
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 800;
  text-align: center;
  color: var(--fg);
}

.gate__name.hidden { display: none; }

/* ---- Backup code ---- */
.gate__code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-align: center;
  text-transform: uppercase;
}

.gate__err {
  margin-top: 10px;
  min-height: 1.2em;
  font-size: 0.82rem;
  text-align: center;
  color: var(--danger);
}

.gate__err:empty { display: none; }

/* ---- The badge screen ----
   The badge is the screen, not a thumbnail beside a larger copy of the code:
   what the player has to walk away with is the image, so it gets the room.

   Height-capped rather than width-capped. The card is drawn 1000x1320, so at
   the overlay's full 380px it stands 502px tall and pushes the two buttons off
   the screen — and this is the one screen whose buttons must always be
   reachable, because the acknowledgement is the only way out of it.

   The budget is what is *left*, not a flat share of the viewport: the title,
   the body and the two buttons come to a constant ~365px whatever the phone,
   so a flat 52vh fitted a 844px screen and pushed the acknowledgement 49px
   below the fold on a 667px one. 52vh stays as the ceiling, so the badge does
   not swell to fill a tall screen. Measured in German, the longest body. */
.code-badge {
  display: block;
  max-width: 100%;
  max-height: min(52vh, calc(100vh - 380px));
  /* Where it is supported, the dynamic viewport: 100vh on mobile Safari counts
     the space under the URL bar, which is exactly the space the button needs. */
  max-height: min(52dvh, calc(100dvh - 380px));
  width: auto;
  height: auto;
  margin: 6px auto 18px;
  border: var(--stroke) solid var(--ink);
  border-radius: 16px;
  box-shadow: var(--hard);
  transform: rotate(-1deg);
  background: var(--card);
}

.code-badge.hidden { display: none; }

/* Each language breaks the title where it wants to; the string carries the
   newline (STRINGS in js/theme.js) and this is what honours it. */
.code-gate .gate__title { white-space: pre-line; }

/* Why the acknowledgement is greyed out. Disappears the moment it is not. */
.code-hint {
  margin-top: 10px;
  font-size: 0.78rem;
  text-align: center;
  color: var(--fg);
  opacity: 0.85;
}

.code-hint:empty { display: none; }

/* ---- Answer chips: the three profile questions ----
   Buttons, not a dropdown: a select needs a first entry, and any first entry
   reads as a default someone already chose. With chips nothing is selected
   until a finger lands on one, which is exactly the state the Continue button
   is reading. */
.chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 9px;
}

.chip {
  /* An equal basis rather than `auto`, so a row of two is a row of two equal
     halves. Content-sized chips made every row a different shape. */
  flex: 1 1 44%;
  min-width: 44%;
  padding: 12px 14px;
  border: var(--stroke) solid var(--ink);
  border-radius: 14px;
  background: var(--paper);
  box-shadow: var(--hard-sm);
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--fg);
  cursor: pointer;
  transition: transform 0.18s var(--spring), box-shadow 0.18s var(--ease-out),
    background 0.18s, color 0.18s;
}

@media (hover: hover) and (pointer: fine) {
  .chip:hover { transform: translate(-2px, -2px); box-shadow: var(--hard); }
}

.chip:active { transform: translate(2px, 2px); box-shadow: var(--hard-press); }

.chip.on {
  background: var(--primary);
  color: var(--primary-contrast);
  transform: translate(2px, 2px);
  box-shadow: var(--hard-press);
}

/* ============================================================
   First-run walkthrough — three cards, one at a time
   ============================================================ */
.walk {
  position: fixed;
  inset: 0;
  z-index: 280;
  display: grid;
  place-items: center;
  padding: 24px 20px calc(24px + env(safe-area-inset-bottom));
  background: rgb(0 0 0 / 0.55); /* neutral, like .modal-overlay — a tinted
                                    scrim washes the page in the skin's hue */
  backdrop-filter: blur(4px);
  animation: fadeIn 0.28s var(--ease-out);
}

.walk--out { animation: fadeIn 0.28s var(--ease-out) reverse forwards; }

.walk__box {
  width: 100%;
  max-width: 350px;
  background: var(--card);
  border: var(--stroke) solid var(--ink);
  border-radius: var(--radius-lg);
  box-shadow: var(--hard-lg);
  padding: 30px 24px 22px;
  text-align: center;
  transform: rotate(-1deg);
}

.walk__icon {
  width: 92px;
  height: 92px;
  margin: 0 auto 16px;
  display: grid;
  place-items: center;
  font-size: 2.9rem;
  background: var(--primary-soft);
  border: var(--stroke) solid var(--ink);
  border-radius: 26px;
  box-shadow: var(--hard-sm);
  animation: popCorrect 0.5s var(--spring);
}

.walk__n {
  font-family: var(--font-display);
  font-size: 0.7rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--fg-muted);
}

.walk__title {
  font-family: var(--font-display);
  font-size: 1.6rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -0.02em;
  line-height: 1.05;
  margin: 4px 0 8px;
  color: var(--primary-strong);
  text-wrap: balance;
}

.walk__text { color: var(--fg-muted); font-weight: 600; font-size: 0.95rem; text-wrap: balance; }

.walk__dots { display: flex; justify-content: center; gap: 7px; margin: 20px 0 16px; }

.walk__dots i {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 2px solid var(--ink);
  background: var(--card);
  cursor: pointer;
  transition: background 0.25s, transform 0.25s var(--spring);
}

.walk__dots i.on { background: var(--accent); transform: scale(1.2); }

.walk__skip { display: block; margin: 10px auto 0; }

/* A quiet way back into the walkthrough once it has been dismissed. */
.help-btn {
  position: fixed;
  /* Must sit on exactly the same inset as `.page`'s padding — that is what puts
     it on the top row's centre line. It changes at 620px, so this does too. */
  top: 20px;
  right: 20px;
  z-index: 55;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 2px solid var(--ink);
  background: var(--card);
  color: var(--fg);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1rem;
  cursor: pointer;
  box-shadow: var(--hard-sm);
  transition: transform 0.2s var(--spring);
}

@media (hover: hover) and (pointer: fine) {
  .help-btn:hover { transform: translateY(-2px) rotate(-8deg); }
}

/* ============================================================
   Question media — optional image or short clip
   ============================================================ */
.q-media {
  width: 100%;
  max-width: 380px;
  margin: 0 auto 16px;
  border: var(--stroke) solid var(--ink);
  border-radius: 20px;
  box-shadow: var(--hard);
  overflow: hidden;
  background: var(--paper);
  line-height: 0;
  animation: fadeInUp 0.45s var(--spring) both;
}

.q-media img,
.q-media video {
  display: block;
  width: 100%;
  max-height: 240px;
  object-fit: contain;
  background: var(--paper);
}

/* ============================================================
   Leaderboard — the viewer's own position
   ============================================================ */
.me-card {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 22px 0 8px;
  padding: 15px 18px;
  background: var(--card);
  /* Dashed, still: it is the one row that is about the viewer rather than about
     the standings, and the dash says "yours" without a second colour. */
  border: 2px dashed var(--ink);
  border-radius: 18px;
  animation: fadeInUp 0.5s var(--ease-out) both;
}

.me-card__label {
  font-family: var(--font-display);
  font-size: 0.68rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--primary-strong);
}

.me-card__name {
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-weight: 800;
  text-transform: uppercase;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.me-card__grow { flex: 1; min-width: 0; }

/* Your own line in the standings, wherever it lands. */
.rank-row--me {
  background: rgb(var(--primary-rgb) / 0.16);
}

/* The viewer's own avatar, beside their row. One image, not ninety-seven. */
.me-face {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 2px solid var(--ink);
  background: #fff;
  object-fit: cover;
}

.me-face.hidden { display: none; }

/* Rendered by leaderboard.js rather than CSS `content`, so it translates. */
.you-badge {
  margin-left: 8px;
  padding: 2px 7px;
  border-radius: var(--radius-pill);
  background: var(--primary);
  color: var(--primary-contrast);
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  vertical-align: middle;
}

/* ============================================================
   Sessions page — the winners of every slot
   ============================================================ */
.sess-list { display: flex; flex-direction: column; gap: 16px; }

.sess-card {
  background: var(--card);
  border: var(--stroke) solid var(--ink);
  border-radius: var(--radius-lg);
  box-shadow: var(--hard);
  padding: 18px;
  animation: fadeInUp 0.5s var(--ease-out) both;
}

.sess-card--live { border-color: var(--accent); box-shadow: var(--hard), 0 0 0 4px rgb(var(--accent-rgb) / 0.2); }

.sess-card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}

.sess-card__name {
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  color: var(--fg);
}

.sess-card__meta { font-size: 0.8rem; color: var(--fg-muted); font-weight: 600; margin-bottom: 14px; }

.sess-podium { display: flex; flex-direction: column; gap: 8px; }

.sess-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  background: var(--paper);
  border: 2px solid var(--ink);
  border-radius: 14px;
}

.sess-row--1 { background: var(--warm); color: var(--on-bright); transform: rotate(-0.6deg); }
.sess-row--2 { background: var(--highlight-soft); color: var(--on-bright); }
.sess-row--3 { background: var(--accent-soft); color: var(--on-bright); }

.sess-row__medal { font-size: 1.3rem; flex-shrink: 0; }

.sess-row__name {
  flex: 1;
  min-width: 0;
  font-family: var(--font-display);
  font-weight: 800;
  text-transform: uppercase;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sess-row__score {
  font-family: var(--font-display);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

.sess-empty { font-size: 0.88rem; color: var(--fg-muted); font-weight: 600; padding: 6px 2px; }

/* ---- Shared keyframes ---- */
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes modalIn { from { opacity: 0; transform: scale(0.94) translateY(14px); } }
@keyframes fadeInUp { from { opacity: 0; transform: translateY(18px); } }

/* ---- Utilities ---- */
.text-center { text-align: center; }
.mt-8 { margin-top: 8px; } .mt-16 { margin-top: 16px; }
.mt-24 { margin-top: 24px; } .mt-32 { margin-top: 32px; }
.mb-8 { margin-bottom: 8px; } .mb-16 { margin-bottom: 16px; } .mb-24 { margin-bottom: 24px; }
.gap-12 { gap: 12px; }
.flex { display: flex; } .flex-col { flex-direction: column; } .flex-wrap { flex-wrap: wrap; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; } .justify-center { justify-content: center; }
.hidden { display: none !important; }
.table-scroll { overflow-x: auto; }

/* ---- Responsive ---- */
@media (max-width: 620px) {
  .page { padding: 16px 16px 112px; }
  .help-btn { top: 16px; right: 16px; }
  .page--full { padding: 18px 14px 28px; }
  .card, .bubble-card { padding: 20px 18px; }
  .podium { gap: 7px; padding: 18px 0 26px; }
  .podium-slot { width: 106px; }
  .podium-name { font-size: 0.85rem; }
  .podium-score { font-size: 1.15rem; }
  .podium-avatar { width: 50px; height: 50px; font-size: 1.25rem; border-radius: 15px; }
  .podium-crown { font-size: 1.5rem; }
  .podium-slot.gold .podium-block { height: 92px; font-size: 1.7rem; }
  .podium-slot.silver .podium-block { height: 68px; font-size: 1.5rem; }
  .podium-slot.bronze .podium-block { height: 50px; font-size: 1.3rem; }
  .rank-row { padding: 11px 13px; gap: 8px; }
  .rank-pos { width: 34px; font-size: 1rem; }
  .rank-name { font-size: 0.95rem; }
  .rank-stats { display: none; }
  .rank-score { font-size: 1.1rem; min-width: 62px; }
  .rank-delta { width: 22px; font-size: 0.7rem; }
  .modal { padding: 18px; border-radius: 18px; }
  .album-grid { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 11px; }
}

/* ---- Print QR codes ---- */
@media print {
  body { background: #fff !important; }
  body::before, body::after { display: none !important; }
  .no-print, .decor-bg, .tabbar, .tabs, .admin-header { display: none !important; }
  .qr-card { break-inside: avoid; box-shadow: none; border: 1px solid #ccc; }
  .qr-grid { grid-template-columns: repeat(3, 1fr); }
}
