/* ============================================================================
   APP STYLES â€” MidnightCrammer
   ----------------------------------------------------------------------------
   Built only on Wavefront tokens (--color-*, --step-*, --space-*, --type-*) and
   on Wavefront components. Every rule below either lays the app shell out or
   extends an existing component; the comment above each block names the
   component it extends and why the kit's own class was not enough.

   Rules of the language (see vendor/wavefront/design_language.css, top):
   fill not outlines; one accent for the primary action and the active state;
   hover/focus/press answered by a background shift; shadow only on things
   that float; no gradients, no glow; respect reduced motion (:root[data-motion="reduce"]).

   SECTIONS
     01 App shell (drawer + column, docked drawer on wide screens)
     02 Topbar extras (chips as links, player collapse)
     03 View chrome (hero, section headers, empty states)
     04 Subject and course browsing (cards, lesson lists)
     05 Content rendering (prose from the server: code, math, blanks)
     06–09 Elements, player, map, profile
     10 Catalog and accounts (catalog cards, human check, generated password)
     11 Chests, quests, shop (chest cards, quest rows, shop cards, effects)
   ========================================================================= */

/* ============================================================ 01 App shell */
/* Extends .page / .drawer. The drawer is the kit's modal drawer on small
   screens; at >= 1024px app.js adds .drawer-docked and keeps it open, which
   the kit already styles as a sticky, shadowless sidebar. The row below gives
   that docked drawer a column to sit beside. */
.app-shell {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  min-height: 100vh;
}
.app-column {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.app-main { outline: none; }
.app-main:focus-visible { outline: none; }

/* Focus rings follow how the user is moving (web/js/lib/focus.js): they show after Tab or an
   arrow key, and hide after a click or a typed shortcut, which move focus for the learner rather
   than to show them a place. Every ring in the kit is drawn in this one token. Text fields mark
   focus with a fill, not a ring, so they keep it. Forced-colours mode draws its own rings. */
html:not([data-keyboard-nav]) { --focus-ring-color: transparent; }

.app-drawer.drawer-docked { flex: none; }
.app-drawer.drawer-docked .app-drawer-close { display: none; }
/* The hamburger only makes sense when the drawer is an overlay. */
.app-shell.is-docked .app-menu { display: none; }

/* Lessons and reviews take the whole window: no sidebar, no menu button. */
body.is-player .app-drawer { display: none; }
body.is-player .app-menu { display: none; }

/* Brand lockup in the drawer header: a link that does not look like one. */
.app-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--color-on-surface);
  text-decoration: none;
}
.app-brand:hover { text-decoration: none; }
.app-brand-logo { display: block; height: 28px; width: auto; }

/* ======================================================= 02 Topbar extras */
/* Extends .topbar-title so it can be a link home without link styling. */
.app-title { color: var(--color-on-surface); text-decoration: none; }
.app-title:hover { text-decoration: none; }

/* Extends .chip: an anchor used as a status chip (XP, streak). */
.app-chip { text-decoration: none; }
.app-chip:hover { text-decoration: none; }
.app-chip [data-value] { font-variant-numeric: tabular-nums; }
.app-chip.is-done { background: var(--color-primary-soft); color: var(--color-primary-text); }
/* Phones: the chips (XP, streak, coins, a running boost) need the whole bar, so
   the title goes (the drawer has the brand), the chips tighten, and the boost
   shows only its icon and the time left. */
@media (max-width: 480px) {
  .app-title { display: none; }
  .app-topbar .topbar-actions { margin-inline-start: auto; }
  .app-chip { padding-inline: var(--space-2); gap: var(--space-1); }
  .app-boost .app-boost-label, .player-boost .app-boost-label { display: none; }
}

/* On phones the player is full-bleed and the topbar gets out of the way; the
   player draws its own compact header (close button + progress). */
@media (max-width: 720px) {
  body.is-player .app-topbar { display: none; }
}

/* ========================================================== 03 View chrome */
/* Extends .page-hero with the padding a tool (not a landing page) wants. */
.view-hero {
  padding-block: var(--space-6) var(--space-5);
  background: var(--color-surface);
}
.view-hero .page-lede { margin-top: var(--space-2); }
.view-title {
  font-family: var(--font-display);
  font-size: var(--type-heading-1-size);
  line-height: 1.2;
  font-weight: var(--weight-semi);
  margin: var(--space-1) 0 0;
}
.view-body { padding-block: var(--space-5) var(--space-8); }
.view-section + .view-section { margin-top: var(--space-6); }
.view-section-title {
  font-family: var(--font-display);
  font-size: var(--type-heading-3-size);
  font-weight: var(--weight-semi);
  margin: 0 0 var(--space-3);
}
.view-section-title .text-muted { font-weight: var(--weight-regular); }

/* Breadcrumb-style back link in a hero. Extends .link-muted. */
.view-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-on-surface-muted);
  font-size: var(--type-body-2-size);
  text-decoration: none;
}
.view-back:hover { color: var(--color-on-surface); text-decoration: none; }
/* Back link on the left, a small control (e.g. Skip ahead) on the right. */
.view-hero-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
}

@media (max-width: 720px) {
  .view-hero { padding-block: var(--space-5) var(--space-4); }
  .view-body { padding-block: var(--space-4) var(--space-7); }
}

/* ============================================ 04 Subject and course browsing */
/* Extends .card.card-interactive. The whole card is one <a>, so the link reset
   below keeps the card's own text colours. Hover is the kit's fill shift. */
.subject-card,
.course-card {
  color: inherit;
  text-decoration: none;
}
.subject-card:hover,
.course-card:hover { text-decoration: none; color: inherit; }
.subject-card .avatar,
.course-card .avatar { background: var(--color-primary-soft); color: var(--color-primary-text); }

/* A lesson row. Extends .list-item-interactive with a state glyph on the
   left: locked, available or complete. The glyph's colour is the only
   difference between states, apart from the accent on the active frontier. */
.lesson-row { min-height: 56px; }
.lesson-row .list-item-leading .icon { font-size: var(--size-icon); }
.lesson-row[data-state="locked"] { color: var(--color-on-surface-faint); }
.lesson-row[data-state="locked"] .list-item-subtitle { color: var(--color-on-surface-faint); }
.lesson-row[data-state="available"] .list-item-leading { color: var(--color-primary-text); }
.lesson-row[data-state="complete"] .list-item-leading { color: var(--color-success); }
.lesson-row .list-item-trailing .chip { height: 24px; padding-inline: var(--space-2); }

/* Unit header above its lesson list: title plus a small meter. */
.unit-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}
.unit-head .meter { width: 96px; flex: none; }

/* ====================================================== 05 Content rendering */
/* Server-rendered markdown lands inside .prose (the kit's long-form class).
   These rules cover the three things the server emits that the kit does not
   know about: Pygments token classes, KaTeX output and fill-in blanks. */
.prose > :first-child { margin-top: 0; }
.prose > :last-child { margin-bottom: 0; }
.prose img { max-width: 100%; height: auto; border-radius: var(--radius-md); }

/* Pygments. Code colours are drawn from the kit's own palette so that the
   block reads as part of the surface: accent for keywords, the status hues
   for strings and numbers (as text, not fills), muted for comments. */
.code-block .tok-k, .code-block .tok-kd, .code-block .tok-kn, .code-block .tok-kr,
.code-block .tok-kt, .code-block .tok-kc, .code-block .tok-ow { color: var(--color-primary-text); }
.code-block .tok-s, .code-block .tok-s1, .code-block .tok-s2, .code-block .tok-sa,
.code-block .tok-sb, .code-block .tok-sc, .code-block .tok-sd, .code-block .tok-se,
.code-block .tok-sh, .code-block .tok-si, .code-block .tok-sx, .code-block .tok-sr,
.code-block .tok-ss { color: var(--color-success); }
.code-block .tok-m, .code-block .tok-mi, .code-block .tok-mf, .code-block .tok-mh,
.code-block .tok-mo, .code-block .tok-mb, .code-block .tok-il { color: var(--color-warning); }
.code-block .tok-c, .code-block .tok-c1, .code-block .tok-cm, .code-block .tok-cs,
.code-block .tok-ch, .code-block .tok-cp, .code-block .tok-cpf { color: var(--color-on-surface-faint); font-style: italic; }
.code-block .tok-nf, .code-block .tok-fm, .code-block .tok-nc, .code-block .tok-nn,
.code-block .tok-nb, .code-block .tok-bp, .code-block .tok-nd { color: var(--color-on-surface); font-weight: var(--weight-medium); }
.code-block .tok-o, .code-block .tok-p { color: var(--color-on-surface-muted); }
.code-block .tok-err { color: var(--color-danger); }

/* Math placeholders before KaTeX runs (Phase 3 wires KaTeX in). */
.math { font-family: var(--font-mono); }
.math[data-display="1"] { display: block; text-align: center; margin-block: var(--space-3); }

/* A blank inside a prompt, before an element replaces it with an input. */
.blank {
  display: inline-block;
  min-width: 5ch;
  border-bottom: var(--border-width-strong) solid var(--color-border-loud);
  vertical-align: baseline;
  margin-inline: 2px;
}

/* ======================================================= 06 Element support */
/* Classes that elements (elements/<type>/element.js) may use, documented in
   docs/ELEMENTS.md. Each extends a kit component; an element never invents a
   colour, it only picks one of these states. */

/* The prompt of an item: .prose for the markdown, with a little more size. */
.el-prompt { font-size: var(--type-subtitle-size); line-height: 1.6; }
.el-prompt .code-block { font-size: var(--type-body-2-size); }

/* An option list. Extends .choice-group (a column of choices). */
.el-options { display: flex; flex-direction: column; gap: var(--space-2); }

/* One selectable option. Extends .choice-card: the fill shows hover, the
   accent soft fill shows "chosen". Correct / wrong after checking are the
   status soft fills, exactly as the kit's alerts use them. */
.el-option {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--size-control-lg);
  padding: var(--space-3) var(--space-4);
  border: 0;
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  color: var(--color-on-surface);
  font: inherit;
  text-align: start;
  width: 100%;
  cursor: pointer;
  position: relative;
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.el-option > input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.el-option:hover { background: var(--color-surface-hover); }
.el-option:has(> input:checked),
.el-option[aria-pressed="true"] { background: var(--color-primary-soft); }
.el-option:has(> input:focus-visible),
.el-option:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: 2px; }
/* Result fills outrank the "chosen" fill: :has(input:checked) carries the
   specificity of its argument, so the result selectors repeat it. */
.el-option.is-correct, .el-option.is-correct:has(> input:checked),
.el-option.is-correct[aria-pressed="true"] { background: var(--color-success-soft); }
.el-option.is-wrong, .el-option.is-wrong:has(> input:checked),
.el-option.is-wrong[aria-pressed="true"] { background: var(--color-danger-soft); }
.el-option.is-missed  { box-shadow: inset 0 0 0 var(--border-width-strong) var(--color-success); }
.el-option[disabled], .el-option:has(> input:disabled) { cursor: default; }
.el-option.is-locked:hover { background: var(--color-surface-sunken); }
.el-option.is-correct.is-locked:hover { background: var(--color-success-soft); }
.el-option.is-wrong.is-locked:hover { background: var(--color-danger-soft); }
.el-option .el-option-text { flex: 1 1 auto; min-width: 0; }
.el-option .el-option-text > p { margin: 0; }

/* The number key hint on an option. Extends .kbd. */
.el-key {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-xs);
  background: var(--color-surface-hover);
  color: var(--color-on-surface-muted);
  font-size: var(--type-caption-size);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
}
.el-option:has(> input:checked) .el-key,
.el-option[aria-pressed="true"] .el-key { background: var(--color-primary-ground); color: var(--color-on-primary); }
.el-option.is-correct .el-key { background: var(--color-success); color: var(--color-on-success); }
.el-option.is-wrong .el-key { background: var(--color-danger); color: var(--color-on-danger); }
@media (max-width: 720px) { .el-key { display: none; } }

/* Two big side-by-side options (true / false). */
.el-options-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
.el-options-row .el-option { justify-content: center; min-height: 72px; font-size: var(--type-subtitle-size); font-weight: var(--weight-medium); }

/* Typed answer. Extends .input; the result states are the kit's own. */
.el-input-wrap { max-width: 480px; }
.el-input.is-valid { background: var(--color-success-soft); }
.el-input.is-invalid { background: var(--color-danger-soft); }

/* An inline blank inside prose, typed. Extends .input but inline-sized. */
.el-blank {
  display: inline-block;
  width: var(--blank-width, 8ch);
  min-width: 4ch;
  max-width: 100%;
  height: calc(var(--size-control) - 6px);
  padding-inline: var(--space-2);
  margin-inline: 2px;
  vertical-align: baseline;
  border: 0;
  border-bottom: var(--border-width-strong) solid var(--color-border-loud);
  border-radius: var(--radius-xs) var(--radius-xs) 0 0;
  background: var(--color-surface-sunken);
  color: var(--color-on-surface);
  font: inherit;
  text-align: center;
}
.el-blank:focus { outline: none; background: var(--color-surface-hover); border-bottom-color: var(--color-primary); }
.el-blank.is-valid { background: var(--color-success-soft); border-bottom-color: var(--color-success); }
.el-blank.is-invalid { background: var(--color-danger-soft); border-bottom-color: var(--color-danger); }
/* A blank filled from a word bank: a button that holds a word. */
.el-slot { cursor: pointer; }
.el-slot:empty::before { content: '\00a0'; }
.el-slot[aria-pressed="true"] { background: var(--color-primary-soft); border-bottom-color: var(--color-primary); }

/* Word bank. Extends .chip-set + .chip-clickable; a used word fades. */
.el-bank { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-4); }
.el-bank .chip { height: var(--size-control); font-size: var(--type-body-size); cursor: pointer; border: 0; font-family: inherit; }
.el-bank .chip:hover { background: var(--color-surface-hover); color: var(--color-on-surface); }
.el-bank .chip:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: 2px; }
.el-bank .chip.is-used { opacity: var(--opacity-disabled); } /* still a button: pressing it returns the word */

/* The correct answer shown after a miss. Extends .alert-success. */
.el-answer { margin-top: var(--space-3); }

/* ============================================================ 07 Player */
/* The lesson player. A column: head (progress), stage (the item), bar
   (Check). The feedback sheet is the kit's bottom sheet, opened without a
   scrim because the learner should still see their answer behind it. */
.player {
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - var(--size-topbar));
}
@media (max-width: 720px) { .player { min-height: 100vh; } }

/* Extends .topbar-compact: the same height, the same surface. */
.player-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: var(--size-topbar-compact);
  padding-inline: var(--space-3);
  background: var(--color-surface);
  position: sticky;
  top: 0;
  z-index: var(--layer-sticky);
}
@media (min-width: 721px) { .player-head { top: var(--size-topbar); } }
.player-head .button-icon { color: var(--color-on-surface-muted); }
.player-progress { flex: 1 1 auto; }
.player-combo.is-hot { background: var(--color-primary-ground); color: var(--color-on-primary); }

.player-stage { flex: 1 1 auto; padding-block: var(--space-5) var(--space-9); }
.player-item { outline: none; }
.player-counter { margin-bottom: var(--space-2); }

/* The Check bar: sticky at the bottom, a surface, no shadow (nothing floats). */
.player-bar {
  position: sticky;
  bottom: 0;
  z-index: var(--layer-sticky);
  background: var(--color-surface);
  padding-block: var(--space-3);
  padding-bottom: max(var(--space-3), env(safe-area-inset-bottom));
}
.player-check { min-width: 160px; }
@media (max-width: 480px) { .player-check { flex: 1 1 auto; } }

/* Feedback sheet. Extends .sheet-bottom; the fill is the status soft colour
   and the icon carries the hue, exactly as the kit's alerts do. */
.player-sheet { padding: var(--space-5) 0; max-height: 60vh; }
.player-sheet.is-correct { background: var(--color-success-soft); }
.player-sheet.is-wrong   { background: var(--color-danger-soft); }
/* The soft fills are translucent; put the surface under them so text stays readable. */
.player-sheet::before { content: ''; position: absolute; inset: 0; z-index: -1; background: var(--color-surface); }
.player-sheet { position: fixed; }
.player-sheet.is-correct .player-sheet-icon { color: var(--color-success); }
.player-sheet.is-wrong   .player-sheet-icon { color: var(--color-danger); }
.player-sheet-title { font-family: var(--font-display); font-size: var(--type-heading-2-size); font-weight: var(--weight-semi); }
.player-sheet-body { margin-top: var(--space-2); color: var(--color-on-surface-muted); }
.player-sheet-answer { margin-top: var(--space-3); padding: var(--space-3); border-radius: var(--radius-md); background: var(--color-surface-sunken); }
.player-sheet-answer > div > p { margin: 0; }
.player-sheet-answer .code-block { margin: 0; }

/* Intro, retry and summary cards: centred, generous. */
.player-card { text-align: center; padding-block: var(--space-6); }
.player-stats {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
  margin-top: var(--space-5);
  text-align: start;
}
@media (min-width: 721px) { .player-stats { grid-template-columns: repeat(4, 1fr); } }
/* The summary fits one screen: tighter rhythm, a smaller icon, four stats in a row. */
.player[data-state="summary"] .player-stage { padding-block: var(--space-3) var(--space-4); }
.player[data-state="summary"] .player-card { padding-block: var(--space-2); }
.player[data-state="summary"] .player-card > .icon-xl { font-size: 40px; }
.player[data-state="summary"] .player-card > h2 { margin-top: var(--space-1); }
.player[data-state="summary"] .player-card .mt-2 { margin-top: var(--space-1); }
.player[data-state="summary"] .player-card .mt-3,
.player[data-state="summary"] .player-card .mt-4 { margin-top: var(--space-2); }
.player[data-state="summary"] .player-card .mt-5 { margin-top: var(--space-3); }
.player[data-state="summary"] .player-stats { grid-template-columns: repeat(4, 1fr); gap: var(--space-2); margin-top: var(--space-3); }
.player[data-state="summary"] .player-stats .stat { padding: var(--space-2); }
.player[data-state="summary"] .player-section-title { margin-bottom: var(--space-1); }

/* ============================================================ 08 The map */
/* A course as a field of tiles. Tiles are absolutely positioned on a
   tile grid (--x, --y in tile units); edges are SVG hairlines underneath.
   State is fill only: the accent soft fill marks the frontier; completed tiles
   step down the surface ladder as their strength fades.
   Tiles are wider than tall so lesson titles get room to read. Their width
   follows the space the map has (a container query), between a floor that
   still fits a word and a cap past which rows would just look sparse; only
   below the floor (narrow phones) does the field scroll sideways. The inline
   padding is the room the bands overhang the field by, so they never cause
   that scroll themselves. */
.map {
  --map-gap: 10px;
  position: relative;
  container-type: inline-size;
  overflow-x: auto;
  overflow-y: hidden;
  padding: var(--space-4) var(--map-gap) var(--space-6);
}
.map-inner {
  --map-tile-w: clamp(80px, 100cqw / var(--map-cols), 168px);
  --map-tile-h: 104px;
  position: relative;
  width: calc(var(--map-cols) * var(--map-tile-w));
  height: calc(var(--map-rows) * var(--map-tile-h));
  margin-inline: auto;
}
.map-edges {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.map-edges line {
  stroke: var(--color-border-strong);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-dasharray: 6 8;
}
.map-edges line.is-open { stroke: var(--color-border-loud); stroke-dasharray: none; }

/* Unit bands: a sunken region with its title in the header row. */
.map-band {
  position: absolute;
  left: calc(var(--map-gap) * -1);
  right: calc(var(--map-gap) * -1);
  top: calc(var(--y0) * var(--map-tile-h));
  height: calc((var(--y1) - var(--y0)) * var(--map-tile-h) + var(--map-gap));
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  pointer-events: none;
}
.map-band-title {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0;
  padding: var(--space-3) var(--space-4) 0;
  font-family: var(--font-display);
  font-size: var(--type-subtitle-size);
  font-weight: var(--weight-semi);
  line-height: 1.35;
}
/* The header row has room for two lines; a longer title ends in an ellipsis
   (the full title is the heading's tooltip) and the count stays in view. */
.map-band-name {
  min-width: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.map-band-count { flex: none; font-variant-numeric: tabular-nums; }

/* A tile. Extends .card-interactive: fill for state, fill shift for hover. */
.map-tile {
  position: absolute;
  left: calc(var(--x) * var(--map-tile-w) + var(--map-gap) / 2);
  top: calc(var(--y) * var(--map-tile-h) + var(--map-gap) / 2);
  width: calc(var(--map-tile-w) - var(--map-gap));
  height: calc(var(--map-tile-h) - var(--map-gap));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  padding: var(--space-2);
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--step-3);
  color: var(--color-on-surface);
  font: inherit;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--duration-slow) var(--ease-standard), color var(--duration-slow) var(--ease-standard);
}
.map-tile:hover { text-decoration: none; background: var(--color-surface-hover); }
.map-tile:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: 2px; }
.map-tile-head { display: flex; align-items: center; gap: var(--space-1); }
.map-tile-icon { display: flex; }
.map-tile-icon .icon { font-size: var(--size-icon); }
/* Up to three lines, which the tile height is sized for: the title must not
   shrink (a shrunk clamp box cuts a line in half and loses its ellipsis), and
   it is as wide as the tile so a long word wraps instead of poking out. */
.map-tile-title {
  flex: none;
  width: 100%;
  font-size: var(--type-caption-size);
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
  hyphens: auto;
}
.map-tile-strength { font-size: var(--type-overline-size); color: var(--color-on-surface-faint); font-variant-numeric: tabular-nums; }

/* Locked: dim, on the sunken step. */
.map-tile[data-state="locked"] { background: var(--step-2); color: var(--color-on-surface-faint); cursor: default; }
.map-tile[data-state="locked"]:hover { background: var(--step-2); }
/* Locked but reachable ("Skip ahead" is on): still dim, but it answers the pointer. */
.map-tile[data-state="locked"][href] { cursor: pointer; }
.map-tile[data-state="locked"][href]:hover { background: var(--step-3); color: var(--color-on-surface-muted); }
/* Available: the frontier, the only place the accent is spent. */
.map-tile[data-state="available"] { background: var(--color-primary-soft); }
.map-tile[data-state="available"] .map-tile-icon { color: var(--color-primary-text); }
.map-tile[data-state="available"]:hover { background: var(--color-primary-soft); color: var(--color-primary-text); }
/* Complete: strength as fill steps. */
.map-tile[data-step="fresh"]  { background: var(--color-primary-soft); }
.map-tile[data-step="fresh"] .map-tile-icon { color: var(--color-primary-text); }
.map-tile[data-step="solid"]  { background: var(--step-4); }
.map-tile[data-step="fading"] { background: var(--step-3); }
.map-tile[data-step="faint"]  { background: var(--step-2); color: var(--color-on-surface-muted); }
.map-tile[data-step="faint"] .map-tile-icon { color: var(--color-on-surface-faint); }

/* The wave: the finished tile pulses its fill once; each unlocked tile starts
   on the locked fill and changes to the frontier fill after its delay. Pure
   fill changes, no transforms, no glow. Skipped under reduced motion. */
.map-tile.is-source { animation: map-pulse var(--duration-slower) var(--ease-standard) 2; }
.map-tile.is-waking { animation: map-wake 420ms var(--ease-standard) var(--wave-delay, 0ms) both; }
.map-edges line.is-wave { stroke: var(--color-primary); animation: map-edge 600ms var(--ease-standard) 150ms both; }
@keyframes map-pulse { 50% { background-color: var(--color-primary-ground); color: var(--color-on-primary); } }
@keyframes map-wake { from { background-color: var(--step-2); color: var(--color-on-surface-faint); } }
@keyframes map-edge { from { stroke: var(--color-border-strong); } }
:root[data-motion="reduce"] :is(.map-tile.is-source, .map-tile.is-waking, .map-edges line.is-wave) { animation: none; }
@media (max-width: 720px) {
  .map { --map-gap: 8px; }
  /* Narrow tiles: a smaller face and a fourth line, with the height to hold it. */
  .map-inner { --map-tile-h: 116px; }
  .map-tile { padding-inline: var(--space-1); }
  .map-tile-title { font-size: var(--type-overline-size); -webkit-line-clamp: 4; }
}

/* Course header: the coverage meter sits beside the actions. */
.coverage { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-4); max-width: 420px; }
.coverage .meter { flex: 1 1 auto; }
.coverage-value { font-variant-numeric: tabular-nums; color: var(--color-on-surface-muted); font-size: var(--type-body-2-size); white-space: nowrap; }

/* ============================================================ 09 Profile */
/* Head: the goal ring beside the stat tiles. */
.profile-head { display: flex; gap: var(--space-5); align-items: center; margin-top: var(--space-5); flex-wrap: wrap; }
.profile-head .stat { background: var(--color-surface-sunken); padding: var(--space-4); }

/* XP column chart. Extends .meter turned vertical: each column is a track on the
   sunken surface with a fill whose height is the day's XP. */
.xp-chart { background: var(--color-surface); border-radius: var(--radius-lg); padding: var(--space-4); }
.xp-chart-cols { display: grid; grid-template-columns: repeat(30, minmax(0, 1fr)); gap: 3px; height: 120px; align-items: end; }
.xp-col { position: relative; height: 100%; background: var(--color-surface-sunken); border-radius: var(--radius-xs); overflow: hidden; }
.xp-col-fill { position: absolute; left: 0; right: 0; bottom: 0; height: var(--h, 0%); background: var(--color-primary); opacity: .55; border-radius: inherit; }
.xp-col.is-met .xp-col-fill { opacity: 1; }
.xp-col:hover { background: var(--color-surface-hover); }

/* Streak calendar. A heatmap of day cells, one column per week, drawn with the
   surface ladder and the soft fills (no new colour). Extends .month-grid's idea
   at a much smaller size. */
.streak-cal { display: flex; gap: 3px; overflow-x: auto; padding-block: var(--space-1); }
.streak-week { display: flex; flex-direction: column; gap: 3px; }
.streak-day { display: block; width: 14px; height: 14px; border-radius: var(--radius-xs); background: var(--step-2); }
.streak-day[data-level="some"] { background: var(--step-4); }
.streak-day[data-level="half"] { background: var(--color-primary-strong); }
.streak-day[data-level="met"] { background: var(--color-primary); }
.streak-day[data-level="freeze"] { background: var(--color-info-soft); box-shadow: inset 0 0 0 2px var(--color-info); }
.streak-legend { width: 12px; height: 12px; }

.meter-row { display: flex; flex-direction: column; gap: var(--space-1); }

/* Dev gallery. The stage is a plain surface; the result block reuses .code-block. */
.gallery-stage { min-height: var(--size-control); }
.gallery-result { max-height: 280px; overflow: auto; font-size: var(--type-caption-size); }
.gallery-result[data-correct="true"] { box-shadow: inset 3px 0 0 var(--color-success); }
.gallery-result[data-correct="false"] { box-shadow: inset 3px 0 0 var(--color-danger); }

/* Reduced motion: elements animate only through background changes already,
   but any element that adds motion must sit under this guard too. */
:root[data-motion="reduce"] :is(.el-option, .el-blank, .el-bank .chip) { transition: none; }

/* ================================================ 10 Catalog and accounts */
/* A catalog card. Extends .card (not .card-interactive): the card holds two
   controls, the title link and the add button, so the card itself is not a
   link. The link reset keeps the card title's own colour. */
.catalog-card { display: flex; flex-direction: column; }
.catalog-card .avatar { background: var(--color-primary-soft); color: var(--color-primary-text); }
.catalog-card .card-actions { margin-top: auto; }
.catalog-link { color: inherit; text-decoration: none; }
.catalog-link:hover { color: inherit; text-decoration: underline; }
.catalog-search { display: flex; flex-direction: column; gap: var(--space-2); max-width: 480px; }

/* The human check on sign-up and sign-in: a placeholder, or the Turnstile widget. Extends
   .input-group's sunken fill so it reads as a form control; the checkbox is
   the kit's .choice. */
.human-check {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-sunken);
  border-radius: var(--radius-xs);
}
.human-check-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
}
.human-check-note {
  flex-basis: 100%;
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
}
/* The real widget draws its own 300x65 box, so the wrapper only centres it on the fill. */
.human-check-cloudflare { justify-content: center; }
.human-check-widget { max-width: 100%; min-height: 65px; }

/* The generated password. Extends .input-group's sunken fill, but the value is
   wrapping text rather than an input: 32 monospace characters do not fit one
   line on a phone, and a password half out of view cannot be checked. A
   little tracking keeps similar glyphs (l, 1, I) apart. */
.password-reveal {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-sunken);
  border-radius: var(--radius-xs);
}
.password-reveal-value {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--type-body-size);
  letter-spacing: .06em;
  line-height: 1.6;
  word-break: break-all;
  user-select: all;
  background: none;
  padding: 0;
  color: var(--color-on-surface);
}
.password-reveal .button { flex: none; }

/* =============================================== 11 Chests, quests, shop */
/* A chest. Extends .alert's layout (icon, text, action in a
   row) on the sunken surface; once opened, the reward picks the soft fill the
   kit already uses for that kind of thing: the accent soft fill for a boost,
   info for a freeze (as on the streak calendar), warning for coins. */
.chest-list { display: flex; flex-direction: column; gap: var(--space-2); }
.chest {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  text-align: start;
}
.chest-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  background: var(--color-primary-soft);
  color: var(--color-primary-text);
}
.chest-text { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; }
.chest-title { font-weight: var(--weight-semi); }
.chest-subtitle { color: var(--color-on-surface-muted); font-size: var(--type-body-2-size); }
.chest .button { flex: none; }
.chest[data-reward="xp_boost"] { background: var(--color-primary-soft); }
.chest[data-reward="freeze"] { background: var(--color-info-soft); }
.chest[data-reward="freeze"] .chest-icon { background: var(--color-surface); color: var(--color-info); }
.chest[data-reward="coins"] { background: var(--color-warning-soft); }
.chest[data-reward="coins"] .chest-icon { background: var(--color-surface); color: var(--color-warning); }
.chest[data-reward="xp_boost"] .chest-icon { background: var(--color-surface); }
.chest:focus { outline: none; }

/* The full-screen chest opening (web/js/lib/chest-opening.js). A modal
   <dialog> on a doubled scrim: the chest (accent body, warning-gold bands and
   lock) drops in and rattles, the lid bursts off, rays turn behind the reward
   card. data-phase walks enter, rattle, open, done, closing; the reward's
   colours arrive as --loot-color / --loot-on-color. Solid fills only. */
.loot {
  --loot-color: var(--color-primary);
  --loot-on-color: var(--color-on-primary);
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: var(--space-4);
  border: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  background: var(--color-scrim);
  color: var(--color-on-primary);
  text-align: center;
  outline: none;
  animation: loot-fade-in var(--duration-slow) var(--ease-decelerate);
}
.loot::backdrop { background: var(--color-scrim); }
.loot[data-phase="closing"] { opacity: 0; transition: opacity var(--duration-slow) var(--ease-accelerate); }
.loot-head, .loot-foot { position: relative; z-index: var(--layer-raised); }
.loot-head { min-height: 3.5rem; }
.loot-title {
  font-family: var(--font-display);
  font-size: var(--type-display-3-size);
  line-height: var(--type-display-3-line);
  font-weight: var(--weight-semi);
  margin: 0;
}
.loot-subtitle { margin: var(--space-1) 0 0; font-size: var(--type-subtitle-size); }
.loot-scene { position: relative; width: min(360px, 100%); height: min(340px, 48vh); min-height: 260px; }
.loot-foot { min-height: 48px; display: flex; align-items: center; justify-content: center; }
.loot-hint { margin: 0; font-size: var(--type-subtitle-size); opacity: .8; }
.loot-collect { min-width: 160px; }
.loot:not([data-phase="done"]):not([data-phase="closing"]) .loot-collect { display: none; }
.loot[data-phase="open"] .loot-hint, .loot[data-phase="done"] .loot-hint, .loot[data-phase="closing"] .loot-hint { display: none; }
.loot[data-phase="done"] .loot-collect { animation: loot-fade-in var(--duration-slower) var(--ease-decelerate); }

/* The chest: 168 x 140, centred in the scene. */
.loot-chest { position: absolute; left: 50%; top: 50%; width: 168px; height: 140px; margin: -70px 0 0 -84px; }
.loot-box, .loot-lid, .loot-mouth, .loot-lock { position: absolute; display: block; }
.loot-mouth { left: 8px; right: 8px; top: 40px; height: 16px; border-radius: var(--radius-sm); background: var(--color-on-surface); }
.loot-box { left: 0; right: 0; top: 50px; bottom: 0; border-radius: var(--radius-md); background: var(--color-primary); }
.loot-lid {
  left: -4px;
  right: -4px;
  top: 0;
  height: 54px;
  border-radius: var(--radius-pill) var(--radius-pill) var(--radius-sm) var(--radius-sm);
  background: var(--color-primary-hover);
  transform-origin: 50% 100%;
}
.loot-lid::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 8px; border-radius: var(--radius-sm); background: var(--color-warning); }
.loot-band { position: absolute; top: 0; bottom: 0; width: 16px; background: var(--color-warning); }
.loot-band:first-child { left: 26px; }
.loot-band:nth-child(2) { right: 26px; }
.loot-lock { left: 50%; top: 42px; width: 30px; height: 36px; margin-left: -15px; border-radius: var(--radius-sm); background: var(--color-warning); }
.loot-lock::after { content: ""; position: absolute; left: 50%; top: 10px; width: 8px; height: 14px; margin-left: -4px; border-radius: var(--radius-pill); background: var(--color-on-warning); }

/* Rays: twelve solid wedges in the reward colour, turning slowly. */
.loot-rays { position: absolute; left: 50%; top: 50%; width: 0; height: 0; opacity: 0; }
.loot-rays-spin { position: absolute; inset: 0; }
.loot-rays span {
  position: absolute;
  left: -10vmax;
  top: 0;
  width: 20vmax;
  height: 75vmax;
  background: var(--loot-color);
  opacity: .28;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
  transform-origin: 50% 0;
  transform: rotate(calc(var(--i) * 30deg));
}
.loot-flash { position: absolute; left: 50%; top: 50%; width: 160px; height: 160px; margin: -80px 0 0 -80px; border-radius: var(--radius-circle); background: var(--color-surface); opacity: 0; pointer-events: none; }
.loot-fx { position: absolute; inset: 0; pointer-events: none; }
.loot-piece { position: absolute; display: block; width: 8px; height: 14px; border-radius: 2px; }
.loot-piece.is-round { width: 10px; height: 10px; border-radius: var(--radius-circle); }
.loot-coin { width: auto; height: auto; font-size: 28px; line-height: 1; color: var(--color-warning); }

/* The reward card: it floats, so it carries a shadow. */
.loot-card {
  position: absolute;
  left: 50%;
  top: 50%;
  width: min(300px, 100%);
  translate: -50% -50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-6) var(--space-5) var(--space-5);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  color: var(--color-on-surface);
  box-shadow: var(--shadow-12);
}
.loot-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 88px;
  height: 88px;
  margin-bottom: var(--space-2);
  border-radius: var(--radius-circle);
  background: var(--loot-color);
  color: var(--loot-on-color);
}
.loot-card-icon .icon { font-size: 48px; }
.loot-card-title { font-family: var(--font-display); font-size: var(--type-heading-2-size); font-weight: var(--weight-semi); font-variant-numeric: tabular-nums; }
.loot-card-text { color: var(--color-on-surface-muted); font-size: var(--type-body-2-size); }

/* The timeline. */
.loot[data-phase="enter"] .loot-chest { animation: loot-drop 520ms var(--ease-decelerate) both; }
.loot[data-phase="rattle"] .loot-chest { animation: loot-build 980ms ease-in, loot-rattle 300ms 980ms ease-in-out infinite; }
.loot[data-phase="rattle"] .loot-lid { animation: loot-peek 300ms 980ms ease-in-out infinite; }
.loot[data-phase="open"] .loot-chest, .loot[data-phase="done"] .loot-chest, .loot[data-phase="closing"] .loot-chest {
  animation: loot-pop 420ms var(--ease-standard), loot-sink 420ms 520ms var(--ease-accelerate) forwards;
}
.loot[data-phase="open"] .loot-lid, .loot[data-phase="done"] .loot-lid, .loot[data-phase="closing"] .loot-lid { animation: loot-lid-off 520ms cubic-bezier(.2, .9, .3, 1) forwards; }
.loot[data-phase="open"] .loot-flash { animation: loot-flash 560ms var(--ease-decelerate); }
.loot[data-phase="open"] .loot-rays, .loot[data-phase="done"] .loot-rays, .loot[data-phase="closing"] .loot-rays { animation: loot-rays-in 600ms 120ms var(--ease-decelerate) both; }
.loot[data-phase="open"] .loot-rays-spin, .loot[data-phase="done"] .loot-rays-spin, .loot[data-phase="closing"] .loot-rays-spin { animation: loot-spin 16s linear infinite; }
.loot-card { animation: loot-card-in 680ms 320ms cubic-bezier(.2, 1.5, .4, 1) both; }
.loot-card-icon { animation: loot-icon-in 560ms 640ms cubic-bezier(.2, 1.8, .4, 1) both; }

@keyframes loot-fade-in { from { opacity: 0; } }
@keyframes loot-drop {
  0% { transform: translateY(-60vh); }
  65% { transform: translateY(0) scale(1.12, .86); }
  82% { transform: translateY(-10px) scale(.96, 1.05); }
  100% { transform: none; }
}
@keyframes loot-build {
  0%, 100% { transform: none; }
  15% { transform: rotate(-2deg); }
  30% { transform: rotate(2deg); }
  45% { transform: rotate(-4deg) translateY(-2px); }
  60% { transform: rotate(4deg) translateY(-2px); }
  75% { transform: rotate(-6deg) translateY(-4px) scale(1.03); }
  90% { transform: rotate(6deg) translateY(-4px) scale(1.03); }
}
@keyframes loot-rattle {
  0%, 100% { transform: rotate(0) scale(1.05); }
  25% { transform: rotate(-8deg) translateY(-6px) scale(1.05); }
  75% { transform: rotate(8deg) translateY(-6px) scale(1.05); }
}
@keyframes loot-peek {
  0%, 100% { transform: none; }
  50% { transform: translateY(-8px); }
}
@keyframes loot-pop {
  0% { transform: scale(1.05); }
  30% { transform: scale(1.18, .78); }
  65% { transform: scale(.92, 1.12); }
  100% { transform: none; }
}
@keyframes loot-sink { to { transform: translateY(70px) scale(.7); opacity: 0; } }
@keyframes loot-lid-off {
  0% { transform: none; }
  45% { transform: translate(-30px, -170px) rotate(-38deg); }
  100% { transform: translate(-90px, -120px) rotate(-70deg); opacity: 0; }
}
@keyframes loot-flash {
  0% { transform: scale(.2); opacity: .9; }
  100% { transform: scale(3.2); opacity: 0; }
}
@keyframes loot-rays-in { from { opacity: 0; transform: scale(.3); } to { opacity: 1; transform: none; } }
@keyframes loot-spin { to { transform: rotate(360deg); } }
@keyframes loot-card-in {
  from { transform: translateY(90px) scale(.2); opacity: 0; }
  to { transform: none; opacity: 1; }
}
@keyframes loot-icon-in {
  from { transform: scale(0) rotate(-40deg); }
  to { transform: none; }
}

/* Daily quests: a row per quest. Extends .list-item's layout with a meter
   under the title; the chip on the right is the tier, then the chest state. */
.quest-list { display: flex; flex-direction: column; gap: var(--space-2); }
.quest {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  text-align: start;
}
.quest-icon { display: inline-flex; color: var(--color-on-surface-muted); }
.quest.is-done .quest-icon { color: var(--color-success); }
.quest-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.quest-title { font-weight: var(--weight-medium); }
.quest-progress { display: flex; align-items: center; gap: var(--space-3); }
.quest-progress .meter { flex: 1 1 auto; }
.quest-count { font-variant-numeric: tabular-nums; color: var(--color-on-surface-muted); font-size: var(--type-caption-size); min-width: 3.5em; text-align: end; }
.quest-list.is-compact .quest { padding: var(--space-2) var(--space-3); background: var(--color-surface-sunken); }
.quest-list.is-compact .quest-title { font-size: var(--type-body-2-size); }

/* The summary's extra sections: chests, quests, the next-lesson chance. */
.player-section-title { font-size: var(--type-subtitle-size); font-weight: var(--weight-semi); margin-bottom: var(--space-2); text-align: start; }
.player-next-chance { display: flex; align-items: center; justify-content: center; gap: var(--space-1); }
.player-boost [data-value], .app-boost [data-value] { font-variant-numeric: tabular-nums; }

/* Shop cards. Extends .card; the item in use carries the accent soft fill
   (the kit's "active" state). */
.shop-item { display: flex; flex-direction: column; }
.shop-item .card-actions { margin-top: auto; }
.shop-item.is-equipped { background: var(--color-primary-soft); }
.shop-item .avatar { background: var(--color-primary-soft); color: var(--color-primary-text); }
.shop-item.is-equipped .avatar { background: var(--color-surface); }
.shop-rules { padding-inline-start: var(--space-5); color: var(--color-on-surface-muted); }
.shop-rules li + li { margin-top: var(--space-2); }

/* Effect preview: a sample answer on the sunken surface, clipped so the
   effect stays inside the card. */
.fx-stage {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 120px;
  margin-inline: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
}
.fx-sample {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-success-soft);
  color: var(--color-on-surface);
  font-weight: var(--weight-semi);
}
.fx-sample .icon { color: var(--color-success); }

/* Correct-answer effects (web/js/player/effects.js). A layer that floats over
   everything, takes no pointer events and is hidden from assistive tech. The
   pieces are solid token fills: no gradients, no glow, no outlines. */
.fx-layer { position: fixed; inset: 0; pointer-events: none; z-index: var(--layer-toast); overflow: hidden; }
.fx-layer.is-contained { position: absolute; z-index: var(--layer-raised); }
.fx-piece { position: absolute; display: block; pointer-events: none; }
.fx-ripple { width: 48px; height: 48px; border-radius: var(--radius-circle); background: var(--color-primary-soft); }
.fx-confetti { width: 7px; height: 12px; border-radius: 2px; }
.fx-confetti.is-round { width: 9px; height: 9px; border-radius: var(--radius-circle); }
.fx-stamp { font-size: 96px; width: auto; height: auto; color: var(--color-success); line-height: 1; }
.fx-flash { border-radius: var(--radius-md); background: var(--color-success-soft); }

/* Reduced motion: the opening holds still; the chest waits, then the card replaces it. */
:root[data-motion="reduce"] :is(.loot, .loot *) { animation: none !important; transition: none !important; }
:root[data-motion="reduce"] :is(.loot[data-phase="done"] .loot-chest, .loot[data-phase="closing"] .loot-chest, .loot-rays) { display: none; }

/* Round two: more effects, celebrations, combo meters and
   progress bars. Same rules: solid token fills, no gradients, no glow. */
.fx-pixel { width: 8px; height: 8px; border-radius: 1px; }
.fx-star { font-size: 22px; width: auto; height: auto; line-height: 1; color: var(--color-warning); }
.fx-spark { width: 7px; height: 7px; border-radius: var(--radius-circle); }
.fx-trophy { font-size: 120px; width: auto; height: auto; line-height: 1; color: var(--color-warning); }

/* Combo meters. Each extends the player's combo chip (.chip.player-combo). */
.player-combo .combo-flame { transition: font-size var(--duration-fast) var(--ease-standard); }
.player-combo[data-style="flame"][data-level="1"] .combo-flame { font-size: 20px; color: var(--color-warning); }
.player-combo[data-style="flame"][data-level="2"] .combo-flame { font-size: 24px; color: var(--color-warning); }
.combo-blocks { display: inline-flex; gap: 2px; }
.combo-block { width: 5px; height: 12px; border-radius: 1px; background: var(--color-surface-hover); }
.combo-block.is-on { background: currentColor; }
.combo-meter { position: relative; display: inline-block; width: 40px; height: 6px; border-radius: var(--radius-pill); background: var(--color-surface-hover); overflow: hidden; }
.combo-meter-fill { position: absolute; inset: 0 auto 0 0; width: var(--value, 0%); background: currentColor; border-radius: inherit; transition: width var(--duration-fast) var(--ease-standard); }
.player-combo.is-hot .combo-block:not(.is-on), .player-combo.is-hot .combo-meter { background: rgba(var(--rgb-surface), .35); }

/* Progress bars. Extend the kit's .progress-linear: segments and dots drop its
   track for one fill per item; the runner keeps it and rides the end. */
.player-progress[data-skin="segments"], .player-progress[data-skin="dots"] {
  display: flex;
  align-items: center;
  gap: 3px;
  background: none;
  overflow: visible;
}
.pskin-seg { flex: 1 1 0; height: 8px; border-radius: var(--radius-xs); background: var(--color-surface-hover); transition: background-color var(--duration-fast) var(--ease-standard); }
.pskin-dot { flex: 0 0 auto; width: 10px; height: 10px; border-radius: var(--radius-circle); background: var(--color-surface-hover); transition: background-color var(--duration-fast) var(--ease-standard); }
.player-progress[data-skin="dots"] { justify-content: space-between; }
.pskin-seg.is-done, .pskin-dot.is-done { background: var(--color-primary); }
.player-progress[data-skin="runner"] { overflow: visible; }
.pskin-runner {
  position: absolute;
  left: var(--value, 0%);
  bottom: 2px;
  transform: translateX(-50%);
  font-size: 22px;
  color: var(--color-primary-text);
  transition: left var(--duration-slow) var(--ease-standard);
}
:root[data-motion="reduce"] :is(.pskin-runner, .pskin-seg, .pskin-dot, .combo-meter-fill, .player-combo .combo-flame) { transition: none; }

/* Shop previews of a combo meter or a progress bar: the sample sits in the
   middle of the effect stage. */
.skin-sample { width: 70%; }
.skin-sample.progress-linear { margin-top: var(--space-4); }

/* Streak tools: the wager's progress and the repair offer inside their cards. */
.tool-status { margin-top: var(--space-2); color: var(--color-on-surface-muted); font-size: var(--type-body-2-size); }
