/* Road to SSL — design tokens & components (see docs/design-reference HANDOFF §10).
   Mobile-first, big tap targets, one thing per screen, high contrast. */
:root {
  --bg-app: #0c0e11;
  --bg-card: #14171c;
  --bg-inset: #0c0e11;
  --bg-good-tint: #121d10; /* same tint, rotated onto the brand hue with the accent */
  --bg-warn-tint: #141210;
  --bg-bad-tint: #25100f;

  --border: #232830;
  --border-good: #224a1c; /* ditto */
  --border-warn: #3a2f14;
  --border-bad: #4a1c1c;

  --text: #f4f6f8;
  --text-body: #e7ebf0;
  --text-muted: #8b929c;
  --text-dim: #6b7280;

  /* Brand green (Chinthaka, M1 — #55). The accent travels app-wide rather than living
     only inside the mark: --green is the "you're good, carry on" end of the palette rule
     (red is reserved for the stop-signal), so the logo and that signal have to be the
     same colour or the rule stops reading. The character's own fill is a step deeper
     (#54934d) and belongs to the art, not to the UI. Retired: #35d07f, the mint that
     shipped until 2026-08-15. */
  --green: #5dbc50;
  --green-ink: #04110a;
  --amber: #f5b73d;
  --red: #ff5a5f;
  --red-ink: #1a0507;
  --teal: #3fb8c9; /* calm exhale color for the breather's box-breathing pacer */

  --r-card: 15px;
  --r-chip: 10px;

  /* Type system (Chinthaka, M1 — #55): Inter carries body, tagline and button text;
     Barlow Bold is the wordmark, which only works as a display face BECAUSE the running
     text moved off Barlow — otherwise the lockup reads as another button label.
     --font-cond keeps the headings, timers and numerals: his spec doesn't cover them,
     and where Barlow Semi Condensed lands is still an open question with him. */
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-display: "Barlow", system-ui, sans-serif;
  --font-cond: "Barlow Semi Condensed", "Barlow", system-ui, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg-app);
  color: var(--text-body);
  font-family: var(--font-body);
  font-size: 17px;
  -webkit-text-size-adjust: 100%;
}

/* tap-app, not a document: links are never underlined */
a { text-decoration: none; }

/* Tap-app, not a document, part 2: double-tap NEVER means zoom, anywhere — fast tap
   sequences (the +1/−1 drill flow) land between buttons too, so scoping this to .btn
   wasn't enough. `manipulation` keeps pinch-zoom fully available (it's not `none`),
   so accessibility zoom survives; only the double-tap gesture is retired. */
html { touch-action: manipulation; }

.app-shell {
  position: relative;  /* anchor the absolute language flag to the app content */
  max-width: 520px;
  margin: 0 auto;
  min-height: 100vh;   /* fallback */
  min-height: 100dvh;  /* dynamic viewport: keeps bottom buttons above the mobile browser chrome */
  padding: 18px 18px calc(28px + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 18px;
}

h1 { font-family: var(--font-cond); font-weight: 700; font-size: 1.8rem; margin: 0; color: var(--text); }
h2 { font-family: var(--font-cond); font-weight: 700; margin: 0; color: var(--text); }
p { line-height: 1.45; margin: 0; }

.muted { color: var(--text-muted); }
.small { font-size: 0.9rem; }
.center { text-align: center; }
.footnote { color: var(--text-dim); font-size: 0.82rem; margin-top: 8px; }
.lede { color: var(--text-body); font-size: 1.05rem; }
.notice { color: var(--green); font-weight: 600; }

.eyebrow {
  font-family: var(--font-cond);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text-dim);
  margin: 0;
}
.eyebrow.good { color: var(--green); }
.eyebrow.warn { color: var(--amber); }
.eyebrow.bad { color: var(--red); }
.eyebrow.dark { color: rgba(4, 17, 10, 0.75); }

.q-head { display: flex; flex-direction: column; gap: 6px; }
.q-title { font-size: 1.5rem; line-height: 1.15; }
.q-title.big { font-size: 1.75rem; }
.q-title .qual { font-family: var(--font-body); font-size: 1rem; color: var(--text-muted); font-weight: 500; }

.group-label {
  font-family: var(--font-cond);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-muted);
  margin: 4px 0;
}
.group-label.good { color: var(--green); }
.group-label.bad { color: var(--red); }

.screen { display: flex; flex-direction: column; gap: 18px; flex: 1; }
/* #ranked_live is the turbo target wrapping the ranked screen's live region. It must be
   layout-transparent so its children (banner, prompt, trail, bottom stack) remain flex
   children of .screen — restoring the 18px gaps and `.stack.bottom { margin-top: auto }`
   that the wrapper otherwise breaks. */
#ranked_live { display: contents; }

/* --- Onboarding first-run flow ------------------------------------------- */
/* language flag, top-right of home only since #76 (no menu button) */
.lang-toggle { position: absolute; top: 12px; right: 10px; z-index: 50; }
/* borderless flag — cleaner (founder preference) */
.lang-toggle-btn { background: none; border: none; padding: 4px; width: 34px; height: 34px; font-size: 22px; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center; }
/* splash */
.splash { justify-content: space-between; }
.splash-hero { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 22px; position: relative; }
/* The aspirational avatar as a shadow behind the logo (see onboarding/_splash_shade).
   Grayscale + dimmed = a destination, not a trophy; the mask fades it into the backdrop
   so it can be cropped to head/shoulders without a hard edge. */
.splash-shade { position: absolute; inset: 0; display: flex; justify-content: center; align-items: flex-start; overflow: hidden; pointer-events: none; z-index: 0; }
.splash-shade img {
  width: var(--shade-w, 90%); height: 100%; object-fit: contain; object-position: top center;
  filter: grayscale(1) brightness(var(--shade-bright, .55)) contrast(1.15);
  opacity: var(--shade-alpha, .5);
  -webkit-mask: linear-gradient(to bottom, #000 var(--shade-keep, 55%), transparent 100%);
          mask: linear-gradient(to bottom, #000 var(--shade-keep, 55%), transparent 100%);
}
/* the lockup sits above the shade */
.splash-hero .splash-lockup { position: relative; z-index: 1; }
/* Mark, wordmark and tagline are ONE block (Chinthaka, 2026-08-19). The gap is tight on
   purpose: the tagline describes the lockup, so it has to be inside its gravity — the
   hero's own 22px gap would read as a separate paragraph that happens to be nearby. */
.splash-lockup { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.splash-tagline { margin: 0; }
/* Logo placement over the shade. `top` lets the figure own the middle of the screen;
   `center` keeps the original centred lockup; `over` sits it low, across the figure. */
.splash-hero--top    { justify-content: flex-start; padding-top: 6%; }
.splash-hero--over   { justify-content: flex-end; padding-bottom: 12%; }
/* The desaturated aura behind the shade figure (see onboarding/_splash_shade). */
.splash-shade--aura .aura { mix-blend-mode: screen; }
.splash-shade--aura img { position: relative; z-index: 1; }
/* A quieter primary CTA — the splash shouldn't be shouting before anything has happened. */
.btn.primary.quiet {
  background: transparent; color: var(--green); border-color: var(--green);
  border-width: 1.5px; font-weight: 600;
}
.btn.calm { min-height: 56px; font-size: 1.05rem; font-weight: 600; }
/* dev-only: side-by-side splash variants at phone width (/dev/splash) */
.dev-splash-grid { display: flex; flex-wrap: wrap; gap: 22px; margin-top: 18px; }
.dev-splash-panel { margin: 0; width: 300px; }
.dev-splash-frame {
  height: 600px; overflow: hidden; border-radius: 18px; border: 1px solid var(--border);
  background: var(--bg); display: flex;
}
.dev-splash-frame .screen { flex: 1; display: flex; flex-direction: column; padding: 18px; }
/* dev-only: app-icon bake-off (/dev/icon). The candidate has to survive a light tab strip
   and a dark one, so the grounds are hardcoded browser chrome colours, not app tokens. */
/* The shell is capped at phone width; a side-by-side bake-off needs the desk. `:has` is
   already a hard requirement of the layout (`allow_browser versions: :modern`). */
.app-shell:has(.dev-icons) { max-width: 1100px; }
.dev-icon-picker { display: flex; flex-wrap: wrap; gap: 10px; margin: 14px 0 22px; }
/* Scoped past `.btn { width: 100% }`, which is declared further down the file. */
.dev-icons .dev-icon-pick {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  width: auto; flex: none; min-height: 44px; padding: 8px 14px; font-size: 0.95rem;
}
.dev-icon-card .dev-icon-pick { margin-top: auto; }
.dev-icon-pick[aria-pressed="true"] { border-color: var(--green); color: var(--green); font-weight: 700; }
.dev-icon-grid { display: flex; flex-wrap: wrap; gap: 22px; }
.dev-icon-card {
  margin: 0; width: 320px; display: flex; flex-direction: column; gap: 12px;
  padding: 16px; border: 1px solid var(--border); border-radius: var(--r-card);
  background: var(--bg-card);
}
.dev-icon-card figcaption { line-height: 1.4; }
.dev-tabstrip { display: flex; gap: 4px; padding: 6px 6px 0; border-radius: 8px 8px 0 0; overflow: hidden; }
.dev-tabstrip.light { background: #dee1e6; }
.dev-tabstrip.dark { background: #202124; }
.dev-tab {
  display: flex; align-items: center; gap: 6px; min-width: 0; flex: 1;
  padding: 6px 8px; border-radius: 8px 8px 0 0; font-size: 12px; line-height: 1;
  white-space: nowrap; overflow: hidden;
}
.dev-tabstrip.light .dev-tab { color: #3c4043; }
.dev-tabstrip.dark .dev-tab { color: #e8eaed; }
.dev-tabstrip.light .dev-tab.active { background: #fff; }
.dev-tabstrip.dark .dev-tab.active { background: #35363a; }
.dev-tab.ghost { opacity: 0.7; }
.dev-tab-dot { width: 12px; height: 12px; border-radius: 50%; background: currentColor; opacity: 0.35; flex: none; }
.dev-icon-sizes { padding: 10px 12px; border-radius: var(--r-card); }
.dev-icon-sizes.dark { background: #0c0e11; color: #8b93a1; }
.dev-icon-sizes.light { background: #f1f3f4; color: #5f6368; }
.dev-icon-ground { display: block; margin-bottom: 6px; opacity: 0.8; }
.dev-icon-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 10px; }
.dev-icon-size { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.dev-swatches { display: flex; flex-wrap: wrap; gap: 18px; margin-top: 12px; }
.dev-swatch { margin: 0; width: 160px; }
.dev-swatch-chip {
  display: block; height: 64px; border-radius: var(--r-card); margin-bottom: 8px;
  border: 1px solid var(--border);
}
/* --- Logo lockup (shared/_logo): mark + wordmark -------------------------- */
.logo { display: flex; align-items: center; justify-content: center; gap: 0.38em; font-size: var(--logo-size, 2.4rem); }
.logo--stacked { flex-direction: column; gap: 0.22em; }
.logo-mark { width: 1.05em; height: auto; color: var(--green); flex: none; }
.logo--stacked .logo-mark { width: 1.5em; }
/* The sensei is a face, not a glyph — four bars read at 1.5em, a muzzle and a pair of
   closed eyes do not. It also brings its own palette, so it takes no colour from here.

   Stacked, the WORDMARK is the wider of the two (Chinthaka's proportion, ~1.3×). It is
   load-bearing, not taste: the word is what a stranger reads, and a mark wider than the
   name it sits on turns the lockup into a picture with a caption. Measured against the
   real render, not eyeballed — see the ratio note in dev/splash. */
.logo-mark--sensei { width: 2.4em; }
.logo--stacked .logo-mark--sensei { width: 3em; }
.logo-word {
  font-family: var(--font-display); font-weight: 700; font-size: 1em; line-height: 0.95;
  text-align: center; letter-spacing: 0.01em; margin: 0;
}
/* The whole wordmark in brand green — Chinthaka's splash. It only works on a near-black
   ground; anywhere the lockup sits on a card, use the plain or DOJO-only treatment. */
.logo-word--green { color: var(--green); }
/* `accent: "dojo"` has been offered by the partial since the torii and never had a rule
   to land on — .hero-title .accent is scoped to the home heading, not the lockup. */
.logo-word .accent { color: var(--green); }

/* --- Sensei beacon (#132) — the mascot doubles as the door to /sensei; a glowing dot
   rides over its top-right while there's a message the player hasn't opened yet. --- */
.hero-lockup-link { display: inline-block; position: relative; color: inherit; text-decoration: none; }
/* Just outside the right ear tip: at 6%/18% the dot sat ON the ear, whose inner is the same
   green, and vanished (checked in Chrome at 2.6rem, 2026-09-18). */
.sensei-beacon { position: absolute; top: 0; right: 6%; }
.sensei-dot {
  display: block; width: 12px; height: 12px; border-radius: 50%;
  background: var(--green); box-shadow: 0 0 10px var(--green);
  animation: sensei-pulse 2s ease-in-out infinite;
}
@keyframes sensei-pulse { 0%, 100% { opacity: 0.55; transform: scale(0.85); } 50% { opacity: 1; transform: scale(1); } }
@media (prefers-reduced-motion: reduce) {
  .sensei-dot { animation: none; }
}
/* language pick + play-style choices */
.btn.lang { display: flex; align-items: center; gap: 12px; justify-content: flex-start; text-align: left; }
.lang-flag { font-size: 22px; }
.btn.lang .soon { margin-left: auto; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-dim); }
.btn.lang.disabled { opacity: 0.5; cursor: default; }
.card-choice { text-align: left; }
/* pc-link code */
.pc-code-card { padding: 22px; border-radius: var(--r-card); border: 1px solid var(--border); background: var(--bg-card); text-align: center; display: flex; flex-direction: column; gap: 8px; }
.pc-code { font-family: var(--font-cond); font-weight: 700; font-size: 2.4rem; letter-spacing: 0.12em; color: var(--green); }
/* the top-level form in a screen stacks its fields/labels/submit with the same rhythm
   (keeps the submit button from butting up against the last field) */
.screen > form { display: flex; flex-direction: column; gap: 16px; }
.field { display: flex; flex-direction: column; gap: 8px; }
.stack { display: flex; flex-direction: column; gap: 10px; }
.stack.bottom { margin-top: auto; }
.row { display: flex; gap: 10px; }
.row > * { flex: 1; }
/* A `button_to` renders a FORM around the button, so the form is the flex child and the
   button inside it kept its own content height: a label that wrapped to two lines (the
   ladder's "MISS · STREAK RESETS", longer still in es) grew only its own button and the
   drill's two taps came out different heights (#70). Making the form a flex container
   stretches the button to the row. Two taps that mean opposite things must not read as a
   hierarchy. */
.row > form.button_to, .row > form.tally-exit-form { display: flex; }

/* --- Buttons -------------------------------------------------------------- */
form.button_to { width: 100%; margin: 0; }
/* The drill's exit forms (#87) are form_with, not button_to — same footprint so the
   next/wrap pair still splits the row evenly. */
form.tally-exit-form { width: 100%; margin: 0; }

.btn {
  display: flex; align-items: center; justify-content: center;
  width: 100%; min-height: 60px; padding: 15px 18px;
  border-radius: var(--r-card); border: 1.5px solid var(--border);
  background: var(--bg-card); color: var(--text-body);
  font-family: var(--font-body); font-size: 1.1rem; font-weight: 600;
  text-decoration: none; cursor: pointer;
  /* Rapid tapping (the +1/−1 drill flow, W/L logging) must read as taps, not gestures:
     manipulation kills double-tap-to-zoom ON THE BUTTON ONLY (pinch-zoom stays available
     everywhere for accessibility), and no-select stops fast taps highlighting the label. */
  touch-action: manipulation;
  -webkit-user-select: none; user-select: none;
}
.btn.primary { background: var(--green); color: var(--green-ink); border-color: var(--green); font-weight: 700; }
.btn.danger { background: var(--red); color: var(--red-ink); border-color: var(--red); font-weight: 700; }
.btn.ghost { background: transparent; color: var(--text-muted); }
/* discouraged action (queue against the coach's call): muted card, red text */
/* Declining the breather is a choice, not a bad outcome: red stays reserved for the
   stop-signal and for LOSS, so the override reads muted rather than alarmed (#92). */
.btn.override { background: var(--bg-card); border-color: var(--border); color: var(--text-muted); font-weight: 600; }
.btn.win { background: var(--green); color: var(--green-ink); border-color: var(--green); font-family: var(--font-cond); letter-spacing: 0.05em; font-weight: 700; }
.btn.loss { background: var(--red); color: var(--red-ink); border-color: var(--red); font-family: var(--font-cond); letter-spacing: 0.05em; font-weight: 700; }
.btn.one-more { background: var(--bg-good-tint); color: var(--green); border-color: var(--green); font-weight: 700; }
.btn.done { background: #e7ebf0; color: #0c0e11; border-color: #e7ebf0; font-weight: 700; }
.btn.big { min-height: 88px; font-size: 1.4rem; font-weight: 700; }

/* Logged-game trail (pills) */
.trail { display: flex; gap: 8px; flex-wrap: wrap; }
.pill { font-family: var(--font-cond); letter-spacing: 0.06em; font-weight: 700; font-size: 0.9rem; padding: 8px 16px; border-radius: var(--r-chip); border: 1.5px solid var(--border); }
.pill-win { color: var(--green); border-color: var(--border-good); background: var(--bg-good-tint); }
.pill-loss { color: var(--red); border-color: var(--border-bad); background: var(--bg-bad-tint); }
/* Ongoing sensor match — neutral/amber (distinct from win/loss), a pulsing dot so the
   screen doesn't read as idle mid-match. Resolves into a pill-win/pill-loss on MatchEnded. */
.pill-live { display: inline-flex; align-items: center; gap: 7px; color: var(--amber); border-color: var(--border-warn); background: var(--bg-warn-tint); }
.pill-live-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--amber); box-shadow: 0 0 8px var(--amber); animation: live-pulse 2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .pill-live-dot { animation: none; opacity: 1; }
  .pill-live { border-style: dashed; } /* static distinct style stands in for the pulse */
}
/* A game that kicked off but does not count toward the ranked window (casual, extras,
   training, unknown playlist) — or an abandoned game, muted regardless of playlist.
   Deliberately calm: switching to Rumble after a skid must read as the cure it is,
   not as more of the same (#100 step 4). */
.pill-muted { color: var(--text-muted); border-color: var(--border); background: transparent; }

/* --- Fed session's live timeline (#100 step 4) ---------------------------- */
.timeline { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.timeline li { display: flex; align-items: center; gap: 10px; }
.timeline-time { font-family: var(--font-cond); font-size: 0.8rem; color: var(--text-muted); min-width: 40px; }
/* Reuses the banner's own .nudge-info/.nudge-warn/.nudge-stop for color — "whatever
   the banner does for a given severity is right here too" (decision 3). */
.timeline-nudge { display: inline-block; padding: 6px 10px; border-radius: var(--r-chip); font-weight: 600; font-size: 0.85rem; }
.timeline-reset, .timeline-arena { color: var(--text-muted); font-style: italic; font-size: 0.9rem; }

/* --- Home ----------------------------------------------------------------- */
.home { display: flex; flex-direction: column; gap: 18px; flex: 1; }
.hero { padding-top: 8px; }
/* The home hero is the real lockup (shared/_logo, sensei above a green wordmark), and
   the whole hero centres around it as the splash does — eyebrow and tagline included. */
.hero { text-align: center; }
.hero-lockup { margin: 6px 0 14px; }
.hero-tagline { margin-inline: auto; }
.hero-tagline { color: var(--text-muted); max-width: 30ch; }
.home-actions { margin-top: auto; display: flex; flex-direction: column; gap: 12px; }

.card {
  display: flex; flex-direction: column; gap: 3px;
  width: 100%; text-align: left; text-decoration: none;
  padding: 18px 20px; border-radius: var(--r-card);
  border: 1.5px solid var(--border); background: var(--bg-card);
  color: var(--text-body); font-family: var(--font-body); cursor: pointer;
}
.card-title { font-family: var(--font-cond); font-weight: 700; font-size: 1.4rem; color: var(--text); }
.card-title.small { font-size: 1.05rem; color: var(--text-body); }
.card-sub { color: var(--text-muted); font-size: 0.92rem; }
.card-primary { background: var(--green); border-color: var(--green); }
.card-primary .card-title { color: var(--green-ink); }
.card-primary .card-sub { color: rgba(4, 17, 10, 0.72); }
.card-cooldown { background: var(--bg-warn-tint); border-color: var(--border-warn); }
.card-cooldown .card-title { color: var(--amber); }
/* Calm, not a problem state (#100 step 5): no warn tint, just a quieter title. */
.card-connected .card-title { color: var(--text-muted); }
.card-tertiary { flex-direction: row; align-items: center; justify-content: space-between; background: transparent; }
.card-tertiary .chev { color: var(--text-dim); font-size: 1.3rem; }

/* First-run welcome (#110) — a PC player told, once, that there is a connection to
   make. An explanation, not a promo: quiet teal border, no fill, sits above the
   coach's .card-quest. Three rows, one title line + one why line each. */
.welcome-block { display: flex; flex-direction: column; gap: 14px; padding: 18px 20px; border-radius: var(--r-card); border: 1.5px solid var(--teal); }
.welcome-title { margin: 0; font-family: var(--font-cond); font-weight: 700; color: var(--text); font-size: 1.05rem; }
.welcome-rows { display: flex; flex-direction: column; gap: 12px; }
.welcome-row { display: flex; flex-direction: column; gap: 2px; color: inherit; text-decoration: none; }
.welcome-row-title { font-family: var(--font-cond); font-weight: 700; color: var(--text); font-size: 0.98rem; }
.welcome-row-sub { color: var(--text-dim); font-size: 0.85rem; }
.welcome-row-link .welcome-row-title { color: var(--teal); }
/* `.welcome-dismiss` classes the <button> button_to renders; the wrapping <form> still
   needs the same `form.button_to` width override the chip-row/admin-row buttons use
   (the app-wide default is full-width). */
.welcome-block form.button_to { width: auto; align-self: flex-end; margin: 0; }
.welcome-dismiss { padding: 6px 4px; border: none; background: transparent; color: var(--text-dim); font-family: var(--font-body); font-size: 0.85rem; text-decoration: underline; cursor: pointer; }

/* --- Wizard header -------------------------------------------------------- */
.wiz-header { display: flex; flex-direction: column; gap: 12px; }
/* reserve the top-right corner for the persistent language flag so the clock / X-of-12
   progress never sits under it */
.wiz-top { display: flex; align-items: center; gap: 12px; padding-right: 38px; }
.wiz-back { text-decoration: none; color: var(--text-muted); font-size: 1.6rem; line-height: 1;
  width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;
  border-radius: 11px; background: var(--bg-card); border: 1px solid var(--border); }
.wiz-titles { flex: 1; }
.wiz-title { font-family: var(--font-cond); font-weight: 700; color: var(--text); font-size: 1.05rem; }
.wiz-count { font-family: var(--font-cond); font-weight: 600; color: var(--green); font-size: 0.9rem; }
.wiz-progress { height: 3px; background: var(--bg-inset); border-radius: 2px; overflow: hidden; }
.wiz-progress-fill { height: 100%; background: var(--green); border-radius: 2px; transition: width 0.3s; }

/* --- The three "you" tabs (shared/_you_tabs): skill card / profile / history ------ */
.you-tabs { display: flex; gap: 8px; margin-top: 12px; }
.you-tab {
  flex: 1; display: flex; align-items: center; justify-content: center; min-height: 42px;
  border-radius: 11px; border: 1px solid var(--border); background: var(--bg-card);
  font-size: 1.25rem; line-height: 1; text-decoration: none; color: var(--text-body);
}
.you-tab--current { border-color: var(--green); background: var(--bg-good-tint); }

/* --- Option rows (one-tap scales) ---------------------------------------- */
.options-list { display: flex; flex-direction: column; gap: 10px; }
.option-row {
  display: flex; align-items: center; gap: 14px;
  width: 100%; min-height: 62px; padding: 12px 18px;
  border-radius: var(--r-card); border: 1.5px solid var(--border);
  background: var(--bg-card); color: var(--text-body);
  font-family: var(--font-body); font-size: 1.15rem; font-weight: 600;
  text-align: left; cursor: pointer;
}
.option-emoji { font-size: 1.5rem; width: 28px; text-align: center; }

/* --- Warm-up -------------------------------------------------------------- */
.warm-seg { display: flex; gap: 6px; }
.warm-seg .seg { flex: 1; height: 3px; border-radius: 2px; background: var(--border); }
.warm-seg .seg.on { background: var(--green); }
.timer-wrap { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; }
.timer { font-family: var(--font-cond); font-weight: 700; font-size: 5rem; line-height: 1; color: var(--text); }
.timer.on { color: var(--green); }
.timer-state { font-family: var(--font-cond); letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-dim); font-size: 0.8rem; }
/* live clock past its soft target (the gym's 45-min frame) */
.game-log { min-height: 40px; display: flex; flex-direction: column; align-items: center; gap: 10px; }
/* "Live — reading your games": a sensor is feeding this session. Green, calm, a soft pulse
   so it reads as alive without competing with the W/L pills. */
.live-marker { display: inline-flex; align-items: center; gap: 7px; margin: 0; font-family: var(--font-cond); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; font-size: 0.72rem; color: var(--green); }
.live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); box-shadow: 0 0 8px var(--green); animation: live-pulse 2s ease-in-out infinite; }
@keyframes live-pulse { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .live-dot { animation: none; } }

/* --- Chips ---------------------------------------------------------------- */
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; }
.chip-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  min-height: 48px; padding: 10px 16px; width: auto;
  border-radius: var(--r-chip); border: 1.5px solid var(--border);
  background: var(--bg-card); color: var(--text-body);
  font-family: var(--font-body); font-size: 1rem; font-weight: 600; cursor: pointer;
}
.chip-row form.button_to, .chip-grid form.button_to { width: auto; }
/* One row that scrolls sideways instead of wrapping — for a strip that must stay a strip. */
.chip-row--scroll { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding-bottom: 2px; }
.chip-row--scroll::-webkit-scrollbar { display: none; }
.chip-row--scroll .chip { white-space: nowrap; }
.chip--compact { min-height: 38px; padding: 6px 12px; font-size: 0.92rem; }
/* A chip that holds the player's current pick (the fake door's pricing answer, the admin
   window) — same green as a checked chip-radio, so one answer language across the app. */
.chip.is-chosen { border-color: var(--green); background: var(--bg-good-tint); color: var(--text); }

/* #134 — the fake door's chooser: two equal buttons, a title line and a quiet second line. */
.offer-btn { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; text-align: left; min-height: 64px; }
.offer-btn-title { font-weight: 600; color: var(--text); }
.offer-btn-sub { font-family: var(--font-cond); letter-spacing: 0.05em; font-size: 0.78rem; color: var(--text-dim); }

/* #134 — the one-off feedback ask on Home. Plain card, green eyebrow: a question, not an alarm. */
.card-feedback { cursor: default; gap: 10px; }
.card-feedback .eyebrow { color: var(--green); }
.feedback-form { display: flex; flex-direction: column; gap: 10px; }
.card-feedback form.button_to { width: auto; align-self: flex-end; margin: 0; }

/* #134 — the admin early-access read. */
.report-table { width: 100%; border-collapse: collapse; font-size: 0.95rem; }
.report-table th { text-align: left; font-weight: 600; color: var(--text-muted); font-size: 0.8rem; padding: 4px 6px 6px 0; }
.report-table td { padding: 6px 6px 6px 0; border-top: 1px solid var(--border); }

/* The fed session's felt-state check (sessions/_feel_check): a quiet strip under the
   nudge, not a second banner. */
.feel-check { display: flex; flex-direction: column; gap: 6px; padding: 12px 14px; border-radius: var(--r-card); border: 1px solid var(--border); background: var(--bg-card); }
.feel-check__q { font-family: var(--font-cond); font-weight: 700; font-size: 1.05rem; color: var(--text); }
.chip-radio {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 15px; min-height: 46px;
  border-radius: var(--r-chip); border: 1.5px solid var(--border);
  background: var(--bg-card); color: var(--text-body); font-weight: 600; cursor: pointer;
}
.chip-radio input { position: absolute; opacity: 0; width: 0; height: 0; }
.chip-radio:has(input:checked) { border-color: var(--green); background: var(--bg-good-tint); color: var(--text); }

/* End-of-session leak tree (issue #46), "enclosed folder drawer" layout (#73). Was a
   wrapping chip-grid: odd child counts (3, of 6/3/2) always left one chip dangling on
   its own uneven row — founder, live on his phone: "The tree distribution is weird UI
   wise. (Once I select one option like execution failed)." Full-width stacked rows
   sidestep wrapping entirely — a row is always exactly as wide as its container,
   whatever the label length, so "Mechanical execution" and "First touch" never have to
   awkwardly pair up. Same radio/Stimulus wiring as before (leak_tree_controller.js only
   ever reads data-leak-tree-target / data-root / hidden — no JS changed for this).
   `.leak-row` mirrors `.chip-radio`'s shape (same radius, border, checked-state green),
   just full width instead of inline; `.leak-drawer` mirrors `--bg-inset`'s existing
   "recessed well" use (progress tracks) rather than inventing a new surface. */
.leak-list { display: flex; flex-direction: column; gap: 8px; }
.leak-row {
  display: flex; align-items: center; gap: 10px;
  width: 100%; min-height: 48px; padding: 12px 16px;
  border-radius: var(--r-chip); border: 1.5px solid var(--border);
  background: var(--bg-card); color: var(--text-body); font-weight: 600; cursor: pointer;
}
.leak-row input { position: absolute; opacity: 0; width: 0; height: 0; }
.leak-row:has(input:checked) { border-color: var(--green); background: var(--bg-good-tint); color: var(--text); }
.leak-row-label { flex: 1; }
/* Trailing chevron only on expandable roots — a terminal answers directly with no
   drawer to open, and omitting its chevron is itself the "tap = done" signal. */
.leak-chevron { color: var(--text-dim); font-size: 1.2rem; transition: transform 0.15s; }
.leak-row-root:has(input:checked) .leak-chevron { transform: rotate(90deg); color: var(--green); }
.leak-drawer {
  display: flex; flex-direction: column; gap: 6px;
  margin: -2px 0 4px 10px; padding: 8px;
  border-left: 2px solid var(--border-good); border-radius: 0 var(--r-chip) var(--r-chip) 0;
  background: var(--bg-inset);
}
.leak-drawer[hidden] { display: none; }
.leak-row-stop { opacity: 0.7; font-style: italic; }

/* --- Mirror --------------------------------------------------------------- */
.mirror-card { display: flex; align-items: center; gap: 14px; padding: 14px 16px; border-radius: var(--r-card); border: 1px solid var(--border); background: var(--bg-card); }
.mirror-emoji { font-size: 1.8rem; }
.mirror-num { font-family: var(--font-cond); font-weight: 700; font-size: 1.8rem; color: var(--text); min-width: 44px; }
.mirror-num.good { color: var(--green); }
.mirror-num.bad { color: var(--red); }
.mirror-value { font-family: var(--font-cond); font-weight: 600; font-size: 1.15rem; color: var(--text); }
.coach-card { padding: 16px; border-radius: var(--r-card); border: 1px solid var(--border-warn); background: var(--bg-warn-tint); display: flex; flex-direction: column; gap: 8px; }

/* --- Sensei screen (#132) — the ask form and every message card (sensei/_message). --- */
.sensei-ask-form { display: flex; flex-direction: column; gap: 10px; }
.sensei-message { gap: 8px; }
.sensei-question { color: var(--text-muted); font-style: italic; }
.sensei-thinking { display: flex; align-items: center; gap: 10px; color: var(--text-muted); }
.sensei-spinner {
  display: inline-block; width: 16px; height: 16px; flex: none;
  border: 2px solid var(--border); border-top-color: var(--green); border-radius: 50%;
  animation: spin 0.9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  /* A still dot stands in for the spin — "working on it" stays legible without motion. */
  .sensei-spinner { animation: none; border-color: var(--green); }
}
/* history-derived coach reads (few, plain-language) */
.coach-read { color: var(--text-body); font-size: 0.95rem; line-height: 1.4; }

/* --- Structured sensei answers (#132 Phase C) — headline + emoji-led findings, laid out
   by the view so a wall of text is impossible. Mobile first (checked at 390px). --- */
.sensei-headline { font-weight: 700; font-size: 1.05rem; color: var(--text); line-height: 1.3; }
.sensei-points { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.sensei-points li { display: flex; align-items: flex-start; gap: 8px; }
.sensei-point-emoji { flex: none; width: 1.4em; text-align: center; }
.sensei-next {
  padding-top: 10px; margin-top: 2px; border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 2px;
}
.sensei-gap { font-style: italic; }

/* --- Suggestion / route --------------------------------------------------- */
.suggest { justify-content: flex-start; }
.suggest-step_away .q-title { color: var(--text); }

/* --- Fun modes ------------------------------------------------------------ */
.mode-card {
  display: flex; flex-direction: column; gap: 6px; width: 100%; text-align: left;
  padding: 16px 18px; border-radius: var(--r-card); border: 1px solid var(--border);
  background: var(--bg-card); color: var(--text-body); cursor: pointer; font-family: var(--font-body);
  text-decoration: none;
}
.mode-top { display: flex; align-items: baseline; justify-content: space-between; }
.mode-name { font-family: var(--font-cond); font-weight: 700; font-size: 1.25rem; color: var(--text); }
.mode-tag { color: var(--green); font-weight: 600; font-size: 0.9rem; }
.mode-tip { color: var(--text-muted); font-size: 0.92rem; }

/* --- Off-ramp ------------------------------------------------------------- */
.offramp { align-items: center; justify-content: center; text-align: center; gap: 14px; }
.offramp-emoji { font-size: 3rem; }
.cooldown-note { display: inline-block; padding: 10px 16px; border-radius: var(--r-chip); background: var(--bg-warn-tint); border: 1px solid var(--border-warn); color: var(--amber); font-weight: 600; }

/* --- Tap grid tiles (quality) -------------------------------------------- */
.tap-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(76px, 1fr)); gap: 8px; }
.tile { display: flex; flex-direction: column; align-items: center; gap: 4px; min-height: 78px; padding: 10px; border-radius: var(--r-chip); border: 1.5px solid var(--border); background: var(--bg-card); color: var(--text-body); font-size: 0.85rem; cursor: pointer; }
.tile input { position: absolute; opacity: 0; width: 0; height: 0; }
.tile-emoji { font-size: 1.5rem; }
.tile:has(input:checked) { border-color: var(--green); background: var(--bg-good-tint); }

/* --- Summary -------------------------------------------------------------- */
.summary { align-items: center; text-align: center; gap: 16px; }
.summary-grid { display: flex; gap: 12px; width: 100%; }
.sum-cell { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 16px; border-radius: var(--r-card); background: var(--bg-card); border: 1px solid var(--border); }
.sum-num { font-family: var(--font-cond); font-weight: 700; font-size: 2.2rem; color: var(--text); }
.sum-num.good { color: var(--green); }
.sum-num.bad { color: var(--red); }
.recap { width: 100%; display: flex; flex-direction: column; gap: 14px; }
.recap p { line-height: 1.5; }

/* --- Live nudge banner (governor's only visible output) ------------------ */
.nudge { display: none; }
.nudge.is-visible {
  display: block;
  padding: 14px 16px;
  border-radius: var(--r-card);
  border: 1.5px solid var(--border);
  font-family: var(--font-cond);
  font-weight: 600;
  font-size: 1.05rem;
  line-height: 1.35;
}
.nudge-info { background: var(--bg-card); color: var(--text-body); }
.nudge-warn { background: var(--bg-warn-tint); border-color: var(--border-warn); color: var(--amber); }
.nudge-stop { background: var(--bg-bad-tint); border-color: var(--border-bad); color: var(--red); }

/* --- Training (the gym) --------------------------------------------------- */
.pack-code { align-self: flex-start; font-family: var(--font-cond); letter-spacing: 0.08em; font-size: 0.82rem; color: var(--text-muted); display: inline-flex; gap: 8px; padding: 8px 12px; border: 1px solid var(--border-good); border-radius: var(--r-chip); background: var(--bg-good-tint); }
.pack-code span { color: var(--green); font-weight: 700; }
/* #119 — the copy button lives INSIDE the code chip rather than beside it, so the thing you
   press is the thing you are copying. 44px min height for a thumb; the chip's own padding
   is what keeps it from looking like a second control bolted on. */
.pack-copy {
  min-height: 32px; padding: 0 10px; margin: -4px -6px -4px 2px;
  font: inherit; letter-spacing: 0.08em;
  color: var(--text-muted); background: none;
  border: 1px solid var(--border); border-radius: var(--r-chip);
  cursor: pointer;
}
.pack-copy:hover, .pack-copy:focus-visible { color: var(--text-body); border-color: var(--text-dim); }
.drill-metric { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; text-align: center; }
.big-num { font-family: var(--font-cond); font-weight: 700; font-size: 6rem; line-height: 1; color: var(--text); }
.metric-label { font-family: var(--font-cond); letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-dim); font-size: 0.8rem; }
.metric-sub { display: flex; gap: 22px; color: var(--text-muted); font-family: var(--font-cond); letter-spacing: 0.05em; font-size: 0.9rem; }
.metric-sub b { color: var(--text); }
.metric-sub b.good { color: var(--green); }
.progress-track { width: 70%; height: 8px; background: var(--bg-inset); border-radius: 4px; overflow: hidden; }
.progress-fill { height: 100%; background: var(--green); }

/* felt-weak check-in drills — surfaced first in the gym, gently highlighted */
.check-in-drill { border-color: var(--border-good); background: var(--bg-good-tint); }
.check-in-drill .mode-tag { color: var(--green); }

/* --- Coach prescription (S13) ---------------------------------------------
   Teal on purpose. Green is the app's own encouragement and amber is its warnings —
   the one voice in here that belongs to a PERSON gets its own register instead of
   borrowing the machine's. Used on both surfaces the player meets it: the drill
   picker and the warm-up matches right before they queue. */
.coach-focus {
  margin: 0; padding-left: 12px; border-left: 3px solid var(--teal);
  color: var(--text); font-size: 1.05rem; line-height: 1.4; font-weight: 600;
}
.coach-focus-card { border-color: var(--teal); background: rgba(63, 184, 201, 0.07); }
.coach-focus-card .eyebrow { color: var(--teal); }

/* The quest block in the gym picker: what was asked, how far in, how long is left. */
.quest-block {
  display: flex; flex-direction: column; gap: 10px; padding: 14px 16px;
  border-radius: var(--r-card); border: 1px solid var(--teal);
  background: rgba(63, 184, 201, 0.06);
}
.quest-block .eyebrow { color: var(--teal); }
.quest-line { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.quest-name { font-family: var(--font-cond); font-weight: 700; font-size: 1.1rem; color: var(--text); }
.quest-progress { color: var(--teal); font-weight: 600; font-size: 0.9rem; white-space: nowrap; }
/* A finished line stops asking for attention; it does not disappear, because seeing what
   you already cleared is the payoff the counter existed for. */
.quest-line.met .quest-name { color: var(--text-muted); }
.quest-line.met .quest-progress { color: var(--green); }
.quest-clock { margin: 0; color: var(--text-dim); font-size: 0.85rem; }

/* A drill in the main list that pays off an open quest line. */
.counts-toward-quest { border-color: var(--teal); }
.counts-toward-quest .mode-tag { color: var(--teal); }

/* Coach console: quest rows, and the verdict on a finished ask. */
.quest-row { display: flex; flex-direction: column; gap: 10px; }
.quest-met { color: var(--green); font-weight: 600; }
.verdict-met { color: var(--green); font-weight: 600; margin: 0; }
.verdict-missed { color: var(--amber); font-weight: 600; margin: 0; }

/* Rank is read off the emblem, not the words — RL players parse these faster than text. */
.rank-mark { height: 34px; width: auto; vertical-align: middle; }
.rank-line { display: flex; align-items: center; gap: 8px; }
.roster-card { display: flex; flex-direction: row; align-items: center; gap: 12px; }
.roster-card .rank-mark { height: 40px; }
/* Coach roster rows (#91) — last-active + quest progress under the name, and an amber
   "went quiet" chip pinned to the top-right. Never red: this is a read, not an alarm. */
.roster-info { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.roster-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.roster-meta { margin: 0; }
.roster-quiet { font-family: var(--font-cond); letter-spacing: 0.06em; text-transform: uppercase; font-size: 0.72rem; padding: 2px 8px; border-radius: var(--r-chip); white-space: nowrap; color: var(--amber); background: var(--bg-warn-tint); }

/* Home's "a quest is waiting" teaser (#91) — same teal identity as the gym's own
   .quest-block, so a coach's ask reads as one voice wherever it shows up. */
.card-quest { border-color: var(--teal); background: rgba(63, 184, 201, 0.06); }
.card-quest .eyebrow { color: var(--teal); }

/* Coach console form bits — the only typed screen in the app. */
.field-hint { color: var(--text-dim); font-size: 0.85rem; }
.form-error { color: var(--red); font-size: 0.9rem; font-weight: 600; }

/* The founder's feedback bubble (#64). Bottom-LEFT on purpose: the language flag owns the
   top-right, and the bottom-right is where thumbs already live on the drill screen's
   HIT/MISS buttons — a note tab there would get fat-fingered mid-set. */
.note-bubble { position: fixed; left: 14px; bottom: 14px; z-index: 40; }
/* Solid, with a shadow, because it floats over live content: a translucent tab reads as a
   smudge on the text underneath rather than as a control. Deliberately NOT green or red —
   the palette rule (#55) reserves those for "you're good" and the stop-signal, and a founder
   tool has no business borrowing either. */
/* #114: a pill with a label, not a 46px circle holding one glyph. The circle was findable
   only by whoever already knew — every note on prod was the founder's. Height stays at the
   44px tap minimum; the label is what makes it a control rather than an ornament. */
.note-bubble-tab {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 0 16px 0 14px; border-radius: 999px;
  background: var(--bg-card); border: 1.5px solid var(--border); color: var(--text-muted);
  cursor: pointer; list-style: none;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.55);
}
.note-bubble-tab__mark { font-size: 1.15rem; line-height: 1; }
.note-bubble-tab__label { font-size: 0.9rem; white-space: nowrap; }
.note-bubble-invite {
  margin: 0 0 10px; color: var(--text-muted); font-size: 0.85rem; line-height: 1.35;
}
.note-bubble-tab::-webkit-details-marker { display: none; }
.note-bubble[open] .note-bubble-tab { color: var(--text-body); border-color: var(--text-dim); }
.note-bubble-panel {
  position: absolute; bottom: 54px; left: 0;
  width: min(78vw, 320px); padding: 12px;
  background: var(--bg-card); border: 1.5px solid var(--border); border-radius: var(--r-card);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
}
.note-bubble-panel textarea {
  width: 100%; margin-bottom: 8px; padding: 10px;
  background: var(--bg); border: 1.5px solid var(--border); border-radius: 10px;
  color: var(--text-body); font-family: var(--font-body); font-size: 0.95rem;
}
.note-bubble-panel .btn { min-height: 44px; font-size: 0.95rem; }
.note-bubble-form { margin: 0; }
.note-row p { margin: 0 0 4px; }
.note-context { color: var(--text-dim); font-size: 0.8rem; font-family: var(--font-mono, monospace); }

/* Foldable group headings (picker areas, skill-card families). The heading IS the control,
   so it needs the tap target and the affordance: a chevron that turns, and enough air above
   that the page reads as several short lists rather than one long one with labels in it. */
.drill-group { margin-top: 22px; }
.drill-group:first-of-type { margin-top: 0; }
.area-heading {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 0; margin: 0; cursor: pointer; list-style: none;
}
.area-heading::-webkit-details-marker { display: none; }
.area-heading::before {
  content: "›"; display: inline-block; color: var(--text-dim);
  transform: rotate(90deg); transition: transform 0.15s ease;
}
.drill-group:not([open]) .area-heading::before { transform: rotate(0deg); }
.area-count { margin-left: auto; color: var(--text-dim); font-weight: 500; }

/* The drill card's rank read — two chips on one line, small enough that the drill NAME is
   still what the eye lands on first. The built-for chip is dim (context); your own goal is
   the one that carries colour, because that is the number you are about to chase. */
.card-ranks { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 0.85rem; }
.card-rank { display: inline-flex; align-items: center; gap: 5px; }
.card-rank-img { height: 20px; width: auto; }
.card-rank.built-for { color: var(--text-dim); }
.card-rank.your-goal { color: var(--text-body); font-weight: 600; }
.card-rank.below { color: var(--text-dim); font-style: italic; }

/* Admin roster (#53) — one row per player, the role toggle pushed to the far edge so the
   names read as a column and the buttons as another. */
.admin-row { display: flex; flex-direction: row; align-items: center; justify-content: space-between; gap: 12px; }
.admin-who { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1 1 auto; }
.admin-who .rank-mark { height: 32px; }
/* The app-wide `form.button_to { width: 100% }` is right everywhere else — here it makes the
   form span the row and sit on top of the name, so the toggle shrinks to its own content. */
.admin-row form.button_to { width: auto; flex: 0 0 auto; }
.admin-row .btn { width: auto; min-height: 0; padding: 10px 14px; font-size: 0.9rem; }

/* --- Skill card (per-skill RL rank, issue #44) ----------------------------- */
.skill-row { gap: 8px; }
/* padding-right reserves the chevron's gutter on EVERY row, tappable or not (#116) — it is
   what keeps the rank chips in one column instead of stepping in and out by 16px. */
.skill-row-top { display: flex; align-items: center; justify-content: space-between; position: relative; padding-right: 16px; }
.skill-name { font-weight: 600; color: var(--text-body); }
/* Rank badge: small emblem + "Diamond II" inline with the skill name. */
.skill-badge { display: inline-flex; align-items: center; gap: 6px; }
.skill-badge-img { width: 30px; height: 30px; object-fit: contain; }
.skill-badge-label { font-family: var(--font-cond); font-weight: 700; letter-spacing: 0.03em; font-size: 0.85rem; color: var(--text-body); }
/* Rank-up readiness on the weekly review (§8.2). Reuses .skill-badge-img so a skill's
   emblem looks identical wherever it appears — the badge IS the player's mental model. */
.readiness-row { display: flex; align-items: center; gap: 10px; }
.readiness-skill { font-weight: 600; color: var(--text-body); flex: 1; }
.readiness-rank { font-family: var(--font-cond); font-weight: 700; letter-spacing: 0.03em; font-size: 0.85rem; color: var(--text-body); }
/* The milestone line is the celebration — the one place this card raises its voice. */
.readiness-milestone { font-weight: 600; color: var(--green); }
/* Unranked — an invitation, never a warning: same chip shape as .skill-fresh, muted. */
.skill-unranked { font-family: var(--font-cond); letter-spacing: 0.06em; text-transform: uppercase; font-size: 0.72rem; padding: 2px 8px; border-radius: var(--r-chip); white-space: nowrap; color: var(--text-dim); background: var(--bg-inset); border: 1px solid var(--border); }
.skill-track { width: 100%; }
/* #52 — a coach's verdict standing in for drill evidence. Teal (not the XP green above)
   so it reads as a DIFFERENT kind of read: a human call, not a measured number. */
.coach-rated-chip, .coach-only-marker { color: var(--teal); }
/* The coach console's inline verdict control — a fold per ladder row so fifteen rows
   don't turn into fifteen open forms at once (#52). */
.verdict-status { color: var(--text-dim); }
.verdict-fold summary { cursor: pointer; color: var(--teal); }
.verdict-fold .verdict-form { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.verdict-choices { display: flex; gap: 12px; }
.verdict-choice { display: flex; align-items: center; gap: 4px; font-size: 0.85rem; color: var(--text-body); }
/* #116 — a skill row that leads into the gym. button_to wraps its content in a <form>, so
   the form must not introduce its own box: the ROW is the control, and it has to sit flush
   with the plain rows beside it or the card reads as two different kinds of list. */
.skill-row-form { margin: 0; display: contents; }
/* A pure reset, NOT a restyle. `.skill-row` carries no background or border of its own, so
   giving the button one framed the tappable rows and left the rest bare — the card then read
   as two different lists. The button must be invisible as a button; only the chevron says
   this row does something. display:block because a <button> is inline-block by default,
   which alone would shift the row. */
button.skill-row {
  display: block; width: 100%; padding: 0;
  text-align: left; font: inherit; color: inherit;
  background: none; border: 0; border-radius: 0; cursor: pointer;
  -webkit-appearance: none; appearance: none;
}
/* The one visible difference, deliberately quiet — a chevron, not a call to action.
   Fifteen shouting rows is a worse card than fifteen calm ones.

   ABSOLUTE, and the gutter is reserved on EVERY row — both halves matter. As an ordinary
   flex child this was a THIRD item in a `justify-content: space-between` row, so the rank
   chip stopped being flush right and got centred in whatever space the skill name left:
   the chips then landed at five different x positions down the card, drifting with name
   length ("Kickoffs" pulled its chip 176px further left than "Rotation & positioning").
   Taking it out of flow puts the row back to two items; padding on every `.skill-row-top`,
   tappable or not, is what keeps the plain rows in the same column as the tappable ones. */
.skill-row--tappable .skill-row-top::after {
  content: "›"; position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  color: var(--text-dim); font-size: 1.1rem; line-height: 1;
}
.skill-row--tappable:hover .skill-row-top::after,
.skill-row--tappable:focus-visible .skill-row-top::after { color: var(--text-body); }
.skill-row--tappable:focus-visible { outline: 2px solid var(--text-dim); outline-offset: 2px; }
/* #134 — the same row as a LINK (a coach-only skill leads to the fake door's chooser, a GET):
   the anchor must vanish the way the button does, or the row reads as a different thing. */
a.skill-row { display: block; color: inherit; text-decoration: none; }

/* The "you are looking at a filtered list" banner in the gym picker. */
.filter-chip {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin: 0 0 12px; padding: 8px 12px;
  background: var(--bg-inset); border-radius: var(--r-chip);
  font-size: .85rem;
}
.filter-chip__label { color: var(--text-muted); }
/* --green, not a bare hex. There is no `--accent` token in this app, so the fallback in
   an earlier draft of this rule was what actually rendered — and it was #35d07f, the mint
   RETIRED on 2026-08-15 when Chinthaka's #5dbc50 landed. A fallback colour is a place for
   a dead palette to come back to life unnoticed. */
.filter-chip__clear { color: var(--green); white-space: nowrap; }

.skill-row-bottom { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding-right: 16px; }
.skill-goal { font-family: var(--font-cond); letter-spacing: 0.05em; font-size: 0.8rem; color: var(--text-dim); }
/* "cooling" markers — informational, never alarming (§7.3): muted blue for due, soft amber for overdue */
.skill-fresh { font-family: var(--font-cond); letter-spacing: 0.06em; text-transform: uppercase; font-size: 0.72rem; padding: 2px 8px; border-radius: var(--r-chip); white-space: nowrap; }
.skill-fresh.due { color: #7fb2e6; background: rgba(74, 156, 245, 0.12); }
.skill-fresh.overdue { color: var(--amber); background: var(--bg-warn-tint); }
.check-in-card { border-color: var(--border); }
.check-in-card .eyebrow { color: #7fb2e6; }
.xp-gain { font-family: var(--font-cond); font-weight: 700; letter-spacing: 0.03em; font-size: 0.85rem; color: var(--green); }

/* --- History -------------------------------------------------------------- */
.section-head { display: flex; align-items: baseline; justify-content: space-between; margin-top: 8px; }
.link-more { color: var(--green); text-decoration: none; font-weight: 600; font-size: 0.9rem; }
.week-nums { display: flex; gap: 22px; margin-top: 8px; }
.week-nums span { display: flex; flex-direction: column; }
.week-nums b { font-family: var(--font-cond); font-weight: 700; font-size: 1.9rem; color: var(--green-ink); }
.week-nums i { font-style: normal; font-size: 0.78rem; color: rgba(4, 17, 10, 0.72); }
.session-row { flex-direction: row; align-items: center; gap: 14px; }
.session-net { font-family: var(--font-cond); font-weight: 700; font-size: 1.5rem; min-width: 40px; color: var(--text-muted); }
.session-net.good { color: var(--green); }
.session-net.bad { color: var(--red); }
.session-main { flex: 1; display: flex; flex-direction: column; gap: 2px; }
.held-badge { font-family: var(--font-cond); font-weight: 700; font-size: 0.8rem; padding: 6px 12px; border-radius: var(--r-chip); border: 1.5px solid var(--border); }
.held-badge.held { color: var(--green); border-color: var(--border-good); background: var(--bg-good-tint); }
.held-badge.overrode { color: var(--red); border-color: var(--border-bad); background: var(--bg-bad-tint); }
.held-card { border-color: var(--border-good); background: var(--bg-good-tint); }
.spend-row { display: flex; justify-content: space-between; font-family: var(--font-cond); font-weight: 600; margin: 12px 0 4px; }
.spend-row b { color: var(--text); }
.progress-fill.spend-ranked { background: var(--green); }
.progress-fill.spend-training { background: #4a9cf5; }
.progress-fill.spend-fun { background: var(--amber); }
.honest-nums { display: flex; gap: 30px; margin-top: 8px; }
.honest-nums span { display: flex; flex-direction: column; }
.honest-nums b { font-family: var(--font-cond); font-weight: 700; font-size: 2rem; }
.honest-nums b.good { color: var(--green); }
.honest-nums b.bad { color: var(--red); }
.honest-nums i { font-style: normal; font-size: 0.78rem; color: var(--text-muted); max-width: 12ch; }
.bench-card { padding: 14px 16px; border-radius: var(--r-card); border: 1px solid var(--border); background: var(--bg-card); }
.bench-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 10px; }
.bench-bars { display: flex; gap: 10px; align-items: flex-end; height: 72px; }
.bench-bar { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; gap: 4px; }
.bench-fill { width: 100%; background: var(--green); border-radius: 4px 4px 0 0; min-height: 4px; }
.bench-bar span { font-size: 0.74rem; color: var(--text-muted); }

/* --- Inputs --------------------------------------------------------------- */
.text-input {
  width: 100%; min-height: 52px; border-radius: var(--r-card);
  border: 1.5px solid var(--border); background: var(--bg-card); color: var(--text);
  padding: 12px 14px; font: inherit;
}
.tap-grid.divisions { grid-template-columns: repeat(4, 1fr); }
.tap-grid.divisions .tile { min-height: 50px; }
.rank-badge { width: 34px; height: 34px; object-fit: contain; }
/* per-rank drill goal (the "good result at your rank") */
.rank-goal { color: var(--green); font-family: var(--font-cond); font-weight: 600; letter-spacing: 0.02em; font-size: 0.9rem; }
/* Coaching note under a drill metric — advice, never a warning: dim, not amber/red. */
.drill-note { color: var(--text-dim); font-size: 0.85rem; margin-top: 10px; }
/* "Something wrong with this drill?" — deliberately quiet; it must not compete with HIT/MISS. */
.report-link { color: var(--text-dim); font-size: 0.8rem; text-decoration: none; border-bottom: 1px dotted var(--border); }
/* "Discard this set" (#47) — same quiet treatment as the report link, but it's a
   button_to (destructive POST). `form.discard-form` (not just `.discard-form`) matches
   `form.button_to`'s specificity so this actually overrides its full-width/height rule
   above, instead of losing to it. */
form.discard-form { width: auto; margin: 4px auto 0; }
.discard-link {
  width: auto; min-height: auto; display: inline; padding: 0; border: none; background: none;
  color: var(--text-dim); font-size: 0.8rem; font-family: inherit; font-weight: 400;
  text-decoration: none; border-bottom: 1px dotted var(--border); cursor: pointer;
}
/* Transient acknowledgement; the governor's banner is the loud channel, not this. */
.flash-notice {
  margin: 0 0 12px; padding: 10px 14px; border-radius: var(--r-chip);
  background: var(--bg-good-tint); color: var(--green); font-size: 0.9rem; text-align: center;
}
/* flat rank ladder: 3 per row so each tier is its own row (Bronze 1/2/3, … then SSL
   alone). No text label — the badges are self-explanatory. */
.rank-grid { grid-template-columns: repeat(3, 1fr); }
.rank-tile { min-height: 58px; padding: 8px; }
.rank-tile .rank-badge { width: 40px; height: 40px; }
/* pre-rank feel: give the choices room below the question */
.prerank { margin-top: 16px; display: flex; flex-direction: column; gap: 12px; }

/* --- Breather (box-breathing + reset countdown) ------------------------- */
.breather { text-align: center; }
.breathe-wrap { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; }
/* The meditating avatar is the main stage; a vertical line beside it paces the breath.
   A faint rank aura sits behind it (--aura-z: 0) — `isolation` keeps its screen-blend
   contained to this box, as on the skill card's .aura-stage. */
/* The stage IS the framed box now (the square + avatar fill it). An explicit square size
   is needed because its children are absolutely positioned — without it the stage would
   collapse to zero height. The faint rank aura sits behind, contained by `isolation`. */
.breathe-stage {
  position: relative; width: min(80%, 58vh); aspect-ratio: 1; margin: 0 auto;
  isolation: isolate;
}
/* The avatar, clipped to the frame below. `contain` keeps the whole meditating figure
   visible (no crop) — the square border gives it the deliberate edge it was missing. */
.breathe-avatar { width: 100%; height: 100%; object-fit: contain; object-position: center bottom; }
.breathe-avatar--fallback { width: 100%; height: 100%; background: var(--bg-good-tint); }
/* The sensei's two poses are stacked, not laid out — same box, same padding, so the ONLY
   difference between the layers is the drawing. The padding keeps him off the frame's
   border (his crop is tight, and art touching the line reads as a mistake) and is padding
   rather than an inset on purpose: an absolutely positioned <img> with `width: auto`
   falls back to its INTRINSIC width, so left/right offsets alone would not size it.
   Padding shrinks the content box that `object-fit: contain` fits into, which is what we
   actually want. */
.breathe-sensei { position: absolute; inset: 0; padding: 6%; object-position: center; }
/* A cut, not a fade: --breath-pose is only ever 0 or 1, so exactly one layer is painted
   and the other is fully off. Nothing composites, so there's no half-and-half frame and
   no mid-breath dip in his solidity. Deliberately NOT transitioned — an eased opacity
   here would put the double exposure straight back.
   Only the RASTER fallback needs this. The vector sensei is a single drawing that changes
   shape, so it has no layers to hide from each other. */
.breathe-sensei--full  { opacity: var(--breath-pose, 0); }
.breathe-sensei--empty { opacity: calc(1 - var(--breath-pose, 0)); }
/* The vector sensei is a wrapper (it carries the accessible name; the SVG itself is
   aria-hidden) around an inline <svg> that letterboxes itself via its own viewBox — the
   vector equivalent of the `object-fit: contain` the raster pair gets from .breathe-avatar,
   which does nothing on a non-replaced element. */
.breathe-sensei--morph > svg { width: 100%; height: 100%; display: block; }
/* Square-perimeter pacer: a dot travels around a square outline that FRAMES the avatar —
   one edge per phase (inhale = up the left, hold = across the top, exhale = down the right,
   hold = across the bottom). Position via --dot-x/--dot-y (0-100%, JS-driven, see
   breather_controller#step); color via --breathe-color per phase (data-driven, overridable
   at /dev/breather). */
/* Fixed, not phase-colored: a border cycling through every phase color was the single
   loudest, most saturated thing on the screen and drowned out the much fainter aura cue
   behind it. The MARKER still carries the phase color (that's the breathing cue); the
   frame is just a frame. `--border` is the same quiet neutral the aura hero's own stage
   frames with (.aura-stage) — green specifically read as competing with the sensei. */
.breathe-square {
  position: absolute; inset: 0; z-index: 2;
  border: 3px solid var(--border);
  border-radius: 18px;
}
/* /dev/breather `border=off`: the dot still rides the same square track, only the outline
   itself (the loudest, most saturated color on the screen) is hidden. */
.breathe-square--no-border { border-color: transparent; }
/* Clips the avatar to the square's rounded rect. Kept OFF .breathe-square itself so the
   perimeter dot (a sibling) is never clipped. */
.breathe-frame { position: absolute; inset: 0; overflow: hidden; border-radius: 15px; z-index: 1; }
/* The marker stays a CIRCLE even though its track is a square: it's a ball, and a ball
   inflating and deflating is the one growing/shrinking thing this app's player already
   reads without being told. (It was briefly squared to match the track; matching the
   track was never worth losing that.) Its size is the lungs: smallest at empty, growing
   across the inhale, HOLDING while you hold, shrinking across the exhale, holding again.
   Scaled via `transform` rather than width/height so the growth costs no layout. */
.breathe-dot {
  position: absolute; width: 18px; height: 18px; border-radius: 50%; z-index: 3;
  left: var(--dot-x, 0%); top: var(--dot-y, 100%); /* 0,100 = bottom-left, the inhale start */
  transform: translate(-50%, -50%) scale(calc(0.6 + 0.55 * var(--breath-fill, 0)));
  background: var(--breathe-color, var(--green));
  box-shadow: 0 0 16px var(--breathe-color, var(--green));
  transition: background-color 0.6s ease, box-shadow 0.6s ease;
}
.breathe-phase { font-family: var(--font-cond); font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: var(--green); font-size: 1.1rem; }
/* Countdown is deliberately small + dim — a quiet cue, not an anxiety timer. */
.breathe-countdown { font-size: 0.9rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }
/* Sound on/off preference toggle (persisted in localStorage, default on). Two labels are
   both rendered (translated server-side) and swapped via the .is-off class so no copy
   lives in JS. */
.breathe-sound { display: flex; justify-content: center; }
.breathe-sound-toggle { min-height: 40px; font-size: 0.85rem; padding: 6px 16px; }
.breathe-sound-toggle .breathe-sound-off { display: none; }
.breathe-sound-toggle.is-off .breathe-sound-on { display: none; }
.breathe-sound-toggle.is-off .breathe-sound-off { display: inline; }
/* Only shown if the browser refused autoplay for the breathing guide while sound is on. */
.breathe-audio-on { max-width: 280px; font-size: 0.85rem; min-height: 44px; }
/* The exit fades in a few seconds into the breather (controller adds .exit-shown) so an
   early bail is always available; it's fully shown well before the countdown ends. */
.breathe-exit { opacity: 0; pointer-events: none; transition: opacity 1.2s ease; }
.breather.exit-shown .breathe-exit,
.breather.is-ready .breathe-exit { opacity: 1; pointer-events: auto; }
/* the "take the breather" CTA inside the nudge banner */
.btn.breather-cta { margin-top: 10px; min-height: 46px; background: var(--bg-card); border-color: var(--border-warn); color: var(--amber); font-weight: 600; }

textarea {
  width: 100%; min-height: 64px; border-radius: var(--r-card);
  border: 1.5px solid var(--border); background: var(--bg-card); color: var(--text);
  padding: 12px; font: inherit; resize: vertical;
}

/* The player-facing app is tap-only and has no dropdowns anywhere — these exist for the
   coach console, which is a desk surface, not a mid-session one. Matched to the textarea
   above so the one typed screen in the app doesn't look like a different product. */
select, input[type="number"], input[type="text"] {
  width: 100%; min-height: 44px; border-radius: var(--r-card);
  border: 1.5px solid var(--border); background: var(--bg-card); color: var(--text);
  padding: 10px 12px; font: inherit;
}

/* ============================================================
   Character aura — skill-card hero (see app/views/skills/_aura_hero.html.erb).
   One grayscale flame texture, masked to a silhouette, recolored per rank by a
   filter. Height / brightness / glow / hot-core all derive from --intensity (the
   server inlines only that number + the rank colors). Falls back to a rank-colored
   glow when the art isn't present yet (.aura-hero--noart).
   ============================================================ */
.aura-hero { --speed: 3s; display: flex; flex-direction: column; gap: 12px; }

.aura-stage {
  position: relative;
  width: 100%;
  height: clamp(260px, 44vh, 400px);
  display: grid;
  place-items: end center;               /* character + aura rooted at the bottom */
  /* ONE row, exactly the stage's height. Without this the implicit row is `auto`, sizes
     itself to the avatar's natural height, and `.aura-character`'s `max-height: 96%` has
     no definite height to resolve against — so it never applied and the figure was simply
     clipped by `overflow: hidden`. That bug predates the new art (the old 449x887 drawing
     rendered 743px tall in a 398px stage); the taller progression art just made it
     impossible to miss. minmax(0, 1fr) rather than 100% so the row can never be forced
     open by its content. */
  grid-template-rows: minmax(0, 1fr);
  overflow: hidden;
  isolation: isolate;                    /* contain the aura's screen-blend to the stage */
  border-radius: var(--r-card);
  clip-path: inset(0 round var(--r-card)); /* clip the blended/masked aura to the rounded corners */
  border: 1px solid var(--border);
  background:
    radial-gradient(120% 80% at 50% 100%,
      color-mix(in srgb, var(--glow) 22%, transparent), transparent 70%),
    var(--bg-inset);
}

/* The aura sits behind the character; height grows 50% → 100% with intensity.
   --speed lives HERE, not only on .aura-hero: the splash shade and the breather reuse
   .aura outside a hero, and an undefined --speed makes `animation` invalid, which reads
   as a frozen single frame of the flames. */
.aura {
  --speed: 3s;
  position: absolute; left: 0; right: 0; bottom: 0;
  top: var(--aura-top, 0);              /* push the top down so flames start lower */
  z-index: var(--aura-z, 2);            /* 2 = in front of the character, 0 = behind it */
  mix-blend-mode: screen;               /* screened, so the avatar shows through the flames */
  opacity: var(--aura-alpha, 1);        /* overall translucency (see --aura-alpha) */
  -webkit-mask: var(--mask) center bottom / cover no-repeat;
          mask: var(--mask) center bottom / cover no-repeat;
}
.aura__layer {
  position: absolute; inset: 0;
  background-repeat: repeat-y;
  background-position: center 0;
  background-size: 100% auto;
  color: var(--glow);                    /* drop-shadow uses currentColor */
  filter:
    sepia(1)
    saturate(var(--sat))
    hue-rotate(calc(var(--hue) * 1deg))
    brightness(calc(0.7 + var(--intensity) * 0.6))
    drop-shadow(0 0 calc(14px + var(--intensity) * 28px) currentColor);
  animation: aura-scroll var(--speed) linear infinite;
}
.aura__layer--flames  { background-image: var(--flames); opacity: calc(0.7 + var(--intensity) * 0.3); }
.aura__layer--flames2 { background-image: var(--flames); animation-duration: calc(var(--speed) * 1.4);
                        animation-direction: reverse; opacity: .4; transform: scaleX(-1); }
.aura__layer--core    { background-image: var(--core); animation-duration: calc(var(--speed) * .85);
                        mix-blend-mode: screen;
                        opacity: calc(0.35 + var(--intensity) * var(--intensity) * 0.65); }
@keyframes aura-scroll {
  from { background-position-y: 0; }
  to   { background-position-y: calc(var(--aura-scroll, 1024px) * -1); }
}

.aura-character {
  position: relative; z-index: 1;
  width: 78%; max-height: 96%; object-fit: contain;
  filter: drop-shadow(0 8px 24px rgba(0, 0, 0, .5));
  /* Both drawings share one cell so they land on the same floor at the same size (#115).
     The stage's `place-items: end center` then aligns them identically — no absolute
     positioning, so there is no second set of coordinates that can drift from the first.
     (.aura is position:absolute and is not a grid item, so this affects only the avatar.) */
  grid-area: 1 / 1;
}
/* The eyes-closed drawing, held invisible except for the instant of a blink. Opacity only:
   the two files are the same character in the same pose, so nothing moves and the browser
   composites without a repaint of the flames behind.

   NO drop-shadow on this layer. The base drawing underneath already casts one, and when
   this layer faded in on top its own shadow stacked on the base's — a darker halo that
   pulsed with every blink (the founder's "glitch shadow", 2026-09-12). One shadow, cast
   once, by the frame that is always there.

   The timing is JS, not a keyframe: `aura_controller` toggles `.is-blinking` at random
   intervals in the human range (~3–6 s; an adult blinks 15–20 times a minute at rest), so
   the avatar never settles into a metronome. ~70 ms of fade either side of ~100 ms shut:
   a hard cut reads as a dropped frame, a slow fade as falling asleep. */
.aura-character--blink { opacity: 0; filter: none; transition: opacity 70ms ease-in-out; }
.aura-character--blink.is-blinking { opacity: 1; }
/* No character art yet → the rank emblem, glowing, centered. */
.aura-character--placeholder { width: auto; display: grid; place-items: center; padding-bottom: 8%; }
.aura-character--placeholder .aura-badge {
  width: 128px; height: auto;
  filter: drop-shadow(0 0 calc(10px + var(--intensity) * 22px) var(--glow));
}
.aura-hero--noart .aura-stage { place-items: center; }

/* The rank emblem pinned to the stage's bottom-right corner — over the aura (z 3 beats the
   aura's 2) and out of the grid flow, so it never nudges the character off its floor. */
.aura-corner-badge {
  position: absolute; right: 12px; bottom: 12px; z-index: 3;
  width: 56px; height: auto;
  filter: drop-shadow(0 2px 8px rgba(0, 0, 0, .6));
}

.aura-xp__head { display: flex; justify-content: space-between; font-size: 12px;
                 color: var(--text-muted); margin-bottom: 6px; }
.aura-xp__track {
  height: 10px; border-radius: 999px; background: rgba(255, 255, 255, .06); overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--glow) 20%, transparent);
}
.aura-xp__fill {
  height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--glow) 30%, #000), var(--glow));
  box-shadow: 0 0 12px var(--glow); transition: width .5s;
}

/* The always-on feed indicator (#83). It sits on every screen because the feed does —
   it is the only way to know the game is being read while you are looking at something
   else. Deliberately tiny and top-LEFT, mirroring the language flag's top-right so the
   two never collide, and quiet enough to ignore until it isn't green. */
.pc-pill { position: fixed; top: 10px; left: 10px; z-index: 60; }

/* The sound-unlock prompt (#96). Hidden until a cue is actually REFUSED by the browser's
   autoplay policy — this is not a settings toggle, it is the dojo reporting that it tried
   to speak and could not. Amber, never red: red stays reserved for the stop-signal itself
   (#55), and spending it here is how it stops landing when the app really does say stop.
   Top-centre rather than beside the feed pill, because unlike the pill this one needs to
   be tapped, and it is only ever on screen when something is wrong. */
.sound-unlock {
  position: fixed; top: 10px; left: 50%; transform: translateX(-50%); z-index: 70;
  padding: 6px 14px; border-radius: 999px;
  background: var(--bg-card); border: 1px solid var(--border-warn); color: var(--amber);
  font-family: var(--font-cond); font-size: .7rem; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; cursor: pointer;
}
.pc-pill__link {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border-radius: 999px;
  background: var(--bg-card); border: 1px solid var(--border);
  font-family: var(--font-cond); font-size: .68rem; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-dim);
  opacity: .55; transition: opacity .3s ease;
}
.pc-pill__link:hover { opacity: 1; }
[data-pc-state="live"] .pc-pill__link { opacity: .85; color: var(--green); border-color: var(--border-good); }
[data-pc-state="blocked"] .pc-pill__link,
[data-pc-state="degraded"] .pc-pill__link { opacity: 1; color: var(--amber); border-color: var(--border-warn); }
.pc-pill__count:empty { display: none; }
.pc-pill__count { opacity: .7; font-variant-numeric: tabular-nums; }
/* The pill is chrome, not content — never let it push the app around on a small screen. */
@media (max-width: 420px) { .pc-pill__word { display: none; } }

/* --- PC feed: the browser as the sensor (#83) ---------------------------- */
.pc-feed { text-align: center; }
/* NOTE THE PALETTE: problem states are AMBER, not red. Red is reserved for the
   stop-signal alone (#34) — spending it on "the socket is down" is exactly how it stops
   landing when the app actually says stop. */
.pc-status { margin: 20px auto 8px; display: flex; flex-direction: column; align-items: center; gap: 6px; }
.pc-status__dot {
  width: 12px; height: 12px; border-radius: 50%; background: var(--text-dim);
  box-shadow: 0 0 12px transparent; transition: background-color .4s ease, box-shadow .4s ease;
}
.pc-status__word {
  font-family: var(--font-cond); font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; font-size: 1.15rem; color: var(--text-dim);
}
.pc-status__detail { max-width: 34ch; margin: 0 auto; }
[data-pc-state="live"] .pc-status__dot { background: var(--green); box-shadow: 0 0 14px var(--green); }
[data-pc-state="live"] .pc-status__word { color: var(--green); }
[data-pc-state="waiting"] .pc-status__dot,
[data-pc-state="connecting"] .pc-status__dot,
[data-pc-state="blocked"] .pc-status__dot,
[data-pc-state="degraded"] .pc-status__dot { background: var(--amber); box-shadow: 0 0 10px var(--amber); }
[data-pc-state="waiting"] .pc-status__word,
[data-pc-state="connecting"] .pc-status__word,
[data-pc-state="blocked"] .pc-status__word,
[data-pc-state="degraded"] .pc-status__word { color: var(--amber); }
.pc-counters { display: flex; gap: 12px; justify-content: center; margin: 16px 0 4px; }
.pc-counters .mirror-card { flex: 1; max-width: 180px; }
.pc-last { margin-top: 4px; }
.pc-last__label { text-transform: uppercase; letter-spacing: .08em; font-size: .72rem; opacity: .7; }
.pc-setup { margin: 22px auto 0; max-width: 40ch; text-align: left; }
.pc-setup summary { cursor: pointer; font-size: .9rem; color: var(--text-muted); min-height: 40px; display: flex; align-items: center; }
.pc-setup__steps { margin: 8px 0 10px; padding-left: 20px; font-size: .85rem; line-height: 1.7; color: var(--text-muted); }
.pc-setup code {
  display: inline-block; margin-top: 4px; padding: 3px 7px; border-radius: 6px;
  background: var(--bg-inset); font-size: .78rem; word-break: break-all;
}

@media (prefers-reduced-motion: reduce) {
  .aura__layer { animation: none; }
  /* Purely decorative, and a repeating flicker in the corner of the eye is exactly what
     this setting is asking us to stop. Hidden rather than paused: a paused blink could
     freeze mid-blink and leave the avatar with his eyes shut. */
  .aura-character--blink { display: none; }
  /* The dot must keep moving to pace breathing — that's functional. Only the color
     fade between phases is decorative, so that's what reduced motion drops. */
  .breathe-square, .breathe-dot { transition: none; }
  .breathe-exit { transition: none; }
}

/* #110 — the PC setup checklist. Numbers in a fixed gutter so the eye can count the steps
   without reading them, which is the whole reason this replaced three paragraphs. */
.checklist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.checklist-step { display: flex; align-items: flex-start; gap: 12px; }
.checklist-num {
  flex: 0 0 28px; height: 28px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--bg-inset); border: 1px solid var(--border);
  color: var(--text-muted); font-weight: 700; font-size: .85rem;
}
.checklist-step--done .checklist-num { background: var(--green); border-color: var(--green); color: var(--green-ink); }
.checklist-step--done .checklist-title { color: var(--text-dim); text-decoration: line-through; }
/* The restart. Not a colour — red is the stop-signal's alone (#55) — just weight. */
.checklist-step--key .checklist-title { font-weight: 700; }
.checklist-title { margin: 3px 0 2px; color: var(--text-body); }
.checklist-note { margin: 0; color: var(--text-muted); font-size: .85rem; line-height: 1.35; }
.btn-inline { display: inline-block; margin-top: 6px; }
.checklist-done { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; color: var(--text-body); }
.checklist-tick {
  width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center;
  background: var(--green); color: var(--green-ink); font-weight: 700; font-size: .8rem;
}

/* #133 — today's quest on Home. Amber, deliberately NOT the coach's teal: the coach's ask
   and the app's pill are two voices and must never read as one. Amber is already the
   app's "attention, not alarm" color (red stays the stop-signal's). */
.card-quest-daily { border-color: var(--amber); background: rgba(245, 183, 61, 0.07); }
.card-quest-daily .eyebrow { color: var(--amber); }
/* Done: the payoff stays up, calm, until the player puts it away with "Got it" — a small
   quiet button, not a second card, so the done state reads as a receipt and not an ask. */
.card-quest-daily.done { border-color: var(--border-good); background: var(--bg-good-tint); cursor: default; }
.card-quest-daily.done .eyebrow { color: var(--green); }
.card-quest-daily.done form { margin-top: 10px; }
.btn.got-it { width: auto; min-height: 40px; padding: 8px 18px; font-size: 0.95rem; border-color: var(--border-good); color: var(--green); }
/* The picker's tag on the quest's suggested drill, same amber so the eye joins the two. */
.todays-quest { border-color: var(--amber); }
.todays-quest .mode-tag { color: var(--amber); }
