/* Arena console — «Трибуна» (views/stand.js) and «Полигон» (views/training.js),
   plus the FEN board both of them draw.

   Two screens in one stylesheet because index.html is frozen this round and
   cannot link a second one; the range's rules are fenced at the foot of the
   file under their own `trn-` prefix, exactly as profile.css carries the
   analytics screen under `anl-`.

   EVERY NUMBER HERE IS THE CLAUDE DESIGN CONSOLE MOCK'S, copied rather than
   eyeballed — the mock writes its styles inline with exact pixels, so a port
   that guesses is a port that drifts. Each block carries the mock's own
   declaration in the comment above it.

   The tone triplets in tokens.css ARE the mock's palette (--t-gold 241,193,82 ·
   --t-violet 167,139,250 · --t-danger 255,93,108 · --t-ok 53,199,131 ·
   --t-mute 236,231,250), so `rgba(var(--t-mute), .42)` below IS the mock's
   `rgba(236,231,250,.42)` and not an approximation of it.

   WRITTEN NARROW-FIRST. The phone reading is the base and the console arrives
   in the @container blocks. Every multi-column arrangement is an @container
   query against `scene`, never a window media query: the rail and the context
   column are subtracted from the window before the scene sees any of it, so at
   a 1280px window the scene is 858px and a `min-width: 1280px` media query
   would lay a wide grid into a narrow hole. shell.css's budget: three columns
   need >= 900 scene pixels, two need >= 620. */

/* ==========================================================================
   THE FEN BOARD — shared by the stand's preview and the range's drill
   ==========================================================================

   THE CONTRAST BUG THIS BLOCK EXISTS TO END. The range used to paint its
   squares from the console's violet palette — rgba(233,226,248,.14) for
   "light" over the #08060f page and rgba(94,74,148,.55) for "dark". Composited
   that makes the LIGHT square darker than the dark one (relative luminance
   0.021 against 0.035), so the board barely read as a chequerboard, and the
   black glyphs at #1d1630 measured 1.17:1 against the light square and 1.39:1
   against the dark one. The first drill in the catalogue asks the player to
   mate a king they cannot see.

   The mock's answer is not a tweak to the glyph colour — it draws the board in
   the same brown chessground theme the real match uses, and says so:
   «Клетки — те же #f0d9b5/#b58863». That is also what the hub's «СЕЙЧАС»
   thumbnail already ships (lobby3.css `.l3-mini-sq`), so the app's three
   boards now agree. Measured after: black 13.3:1 on light, 5.9:1 on dark. */

.fen-board {
  /* A query container so the glyphs scale with the BOARD rather than with the
     window: the same component is 248px in the range and up to 300px in the
     stand, and a vw-based glyph size gets one of the two wrong. */
  container-type: inline-size;
  width: 100%;
  aspect-ratio: 1;
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  grid-template-rows: repeat(8, 1fr);
  border-radius: 6px;
  overflow: hidden;
  background: #f0d9b5;
  /* mock: `0 0 0 7px rgba(30,21,13,.85), 0 0 0 8px rgba(241,193,82,.28),
     0 20px 46px rgba(0,0,0,.62)` — a wooden edge and a gold hairline. */
  box-shadow:
    0 0 0 7px rgba(30, 21, 13, 0.85),
    0 0 0 8px rgba(241, 193, 82, 0.28),
    0 20px 46px rgba(0, 0, 0, 0.62);
}

.fen-sq {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f0d9b5;
}
.fen-sq.is-dark { background: #b58863; }

/* Bought board skin on the preview (services_cosmetics). stand.js toggles the
   wrapper class through ui/board.js's applyBoardSkin, the same renderer the
   seat uses; these rules repaint the mini board's own squares (the seat's
   chessground board is painted by css/views/game.css). Same emerald/obsidian
   pair as the seat, so the preview matches the table it opens. */
.fen-board.skin-emerald { background: #47a178; }
.fen-board.skin-emerald .fen-sq { background: #47a178; }
.fen-board.skin-emerald .fen-sq.is-dark { background: #1b3b2e; }

/* One filled glyph set for both colours, coloured apart — outline ♔♕♖ beside
   filled ♚♛♜ read as two different piece sets, which is the note the mock
   leaves on its own renderer. mock: `font-size:25px` on its 248px board, which
   is 10.08cqw — expressed that way the same glyph is right on the stand's
   larger board too. */
.fen-pc {
  font-style: normal;
  font-size: 10.08cqw;
  line-height: 1;
  color: #17110a;
  text-shadow: 0 1px 1px rgba(255, 255, 255, 0.25);
}
/* A white piece on a LIGHT square is 1.37:1 on fill alone — in cburnett the
   thing that separates it is the black outline, not the fill, so the outline is
   drawn rather than implied. #2a1d10 against #f0d9b5 is 11.4:1, which is what
   the eye actually reads. The hub thumbnail gets away with a single 1px shadow
   because its glyphs are 11px; these are 25px and need all four sides. */
.fen-pc.is-white {
  color: #fff;
  text-shadow:
    1px 0 0 #2a1d10, -1px 0 0 #2a1d10, 0 1px 0 #2a1d10, 0 -1px 0 #2a1d10,
    0 0 2px #2a1d10, 0 2px 3px rgba(0, 0, 0, 0.45);
}

/* mock: coordinates at `font-size:8px;font-weight:800`, ink chosen against the
   square it sits on — rgba(240,217,181,.9) on dark, rgba(150,108,66,.95) on
   light. Rank on the a-file, file on the first rank. */
.fen-cr,
.fen-cf {
  position: absolute;
  font-style: normal;
  font-family: var(--font-body);
  /* mock: `font-size:8px` on its 248px board. */
  font-size: 3.23cqw;
  font-weight: 800;
  line-height: 1;
  color: rgba(150, 108, 66, 0.95);
}
.fen-cr { top: 1px; left: 2px; }
.fen-cf { bottom: 1px; right: 3px; }
.fen-sq.is-dark .fen-cr,
.fen-sq.is-dark .fen-cf { color: rgba(240, 217, 181, 0.9); }

/* ==========================================================================
   «ТРИБУНА» — views/stand.js
   ========================================================================== */

/* mock: `padding:16px 18px` on a console, `0 15px 12px` on a phone. The
   selector carries #scene because shell.css sets `padding-top: 18px` on
   `#scene.shell-scene .container`, which outranks a plain #view rule. */
#view.st-host,
#scene.shell-scene #view.st-host {
  max-width: none;
  padding: 0 15px 12px;
  overflow-x: hidden;
}

/* The screen IS the column — `.view` is flex, and there is no wrapper div in
   between. mock: `grid-template-rows:auto minmax(0,1fr);gap:13px`. */
.view-stand {
  display: flex;
  flex-direction: column;
  gap: 13px;
  width: 100%;
  align-items: stretch;
}
.view-stand > * { min-width: 0; }

/* --- the head bar ---
   mock (console): `display:flex;align-items:center;gap:13px;flex-wrap:wrap;
   padding:13px 17px;border-radius:16px;border:1px solid rgba(167,139,250,.24);
   background:linear-gradient(115deg,rgba(36,22,62,.8),rgba(18,12,34,.66))`.
   The phone header is the same row tightened to `gap:9px`. */
.st-head {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  padding: 11px 13px;
  border-radius: var(--r-panel);
  border: 1px solid rgba(var(--t-violet), 0.24);
  background: linear-gradient(115deg, rgba(36, 22, 62, 0.8), rgba(18, 12, 34, 0.66));
}
/* mock: 21px on a console, 19px on a phone. */
.st-head-icon { font-size: 19px; line-height: 1; }
.st-head-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

/* mock: `font-family:Unbounded;font-weight:800` at 14px on a phone and 16px on
   a console; the sub line 9px / 10px at `color:rgba(236,231,250,.46)`. */
.st-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 800;
  line-height: 1.2;
}
.st-sub { font-size: 9px; color: rgba(var(--t-mute), 0.46); }

/* mock: `display:flex;align-items:center;gap:6px;padding:5px 11px;
   border-radius:999px;background:rgba(255,93,108,.12);
   border:1px solid rgba(255,93,108,.3);font-size:10px;font-weight:800;
   color:#ff8a94` with a 6px pulsing dot inside. */
.st-live {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border-radius: var(--r-pill);
  background: rgba(var(--t-danger), 0.12);
  border: 1px solid rgba(var(--t-danger), 0.3);
  font-size: 10px;
  font-weight: 800;
  color: var(--danger-soft);
  white-space: nowrap;
  margin-left: auto;
}
.st-live-n:empty { display: none; }
.st-live-dot {
  width: 6px;
  height: 6px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--danger);
  animation: ac-pulse 1.4s infinite;
}

/* --- the two panels ---
   One column on a phone, and the SEAT comes first there: that is the mock's
   own phone order (board, then «идут сейчас» at the foot), and it is also what
   makes a row tap visible without scrolling. mock (console):
   `grid-template-columns:250px minmax(0,1fr) 290px` — the third track is the
   stand chat, which has no backend and is not drawn. */
.st-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 12px;
  align-items: start;
}
.st-list-panel { order: 2; }
.st-seat-panel { order: 1; }

/* mock: `border-radius:16px;border:1px solid rgba(167,139,250,.18);
   background:rgba(18,12,34,.56);overflow:hidden` */
.st-panel {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border-radius: var(--r-panel);
  border: 1px solid var(--stroke);
  background: var(--panel);
  overflow: hidden;
}

/* mock: `padding:11px 14px;border-bottom:1px solid rgba(167,139,250,.14);
   display:flex;align-items:center;gap:8px` */
.st-panel-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--hairline);
}
/* mock: `font-family:Unbounded;font-size:11px;font-weight:700;
   letter-spacing:.1em;text-transform:uppercase` */
.st-panel-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-panel-head);
  font-weight: var(--fw-panel-head);
  letter-spacing: var(--ls-panel-head);
  text-transform: uppercase;
  line-height: 1.2;
}

/* mock: `padding:9px;display:flex;flex-direction:column;gap:6px` */
.st-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 9px;
  min-height: 0;
}

/* --- a live game ---
   mock: `display:flex;align-items:center;gap:10px;padding:10px 11px;
   border-radius:12px;text-align:left;transition:.16s;
   border:1px solid rgba(167,139,250,.14);background:rgba(20,13,38,.5)`,
   and the selected row `rgba(241,193,82,.5)` / `rgba(241,193,82,.1)`. */
.st-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 11px;
  border-radius: 12px;
  text-align: left;
  cursor: pointer;
  border: 1px solid var(--hairline);
  background: var(--panel-soft);
  transition: border-color var(--dur-fast), background var(--dur-fast);
}
@media (hover: hover) {
  .st-row:hover { border-color: rgba(var(--t-gold), 0.4); }
}
.st-row:focus-visible { outline: 2px solid rgba(var(--t-gold), 0.7); outline-offset: 2px; }
.st-row.is-on {
  border-color: rgba(var(--t-gold), 0.5);
  background: rgba(var(--t-gold), 0.1);
}

/* mock: `width:30px;height:30px;border-radius:9px;font-size:15px;
   background:rgba(167,139,250,.1);border:1px solid rgba(167,139,250,.2)` */
.st-row-av {
  width: 30px;
  height: 30px;
  flex: 0 0 auto;
  border-radius: var(--r-sm);
  display: grid;
  place-items: center;
  font-size: 15px;
  line-height: 1;
  background: rgba(var(--t-violet), 0.1);
  border: 1px solid rgba(var(--t-violet), 0.2);
}

.st-row-col {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
/* mock: `font-size:11px;font-weight:800` with ellipsis — a nowrap pair of
   handles («@очень_длинный_ник_игрока — @antonio») otherwise pushes the track
   to its min-content width and hangs the row off a 390px phone. */
.st-row-pair {
  font-size: 11px;
  font-weight: 800;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* mock: `font-size:9px;color:rgba(236,231,250,.42)` */
.st-row-meta {
  font-size: 9px;
  color: rgba(var(--t-mute), 0.42);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* mock: `padding:3px 8px;border-radius:7px;background:rgba(255,93,108,.12);
   font-size:9px;font-weight:800;color:#ff8a94` */
.st-row-watchers {
  flex: 0 0 auto;
  white-space: nowrap;
  padding: 3px 8px;
  border-radius: var(--r-xs);
  background: rgba(var(--t-danger), 0.12);
  font-size: 9px;
  font-weight: 800;
  color: var(--danger-soft);
}
/* The row carries no button of its own — the mock's row IS "show me this one",
   and the seat beside it carries the single «Смотреть партию». A chip here
   costs the 250px track ~70px, which is what truncated «@dashka_pmw» to
   «@da…» when it was tried. */

/* --- «прямо сейчас никто не играет» ---
   The mock's `wait` empty state (code `no_live_games`), tone 167,139,250:
   `padding:17px 16px;border-radius:15px;border:1px dashed rgba(...,.36);
   background:rgba(...,.05)` and three lines plus one action. */
.st-empty {
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 17px 16px;
  border-radius: var(--r-lg);
  border: 1px dashed rgba(var(--t-violet), 0.36);
  background: rgba(var(--t-violet), 0.05);
}
.st-empty-top { display: flex; align-items: center; gap: 9px; }
.st-empty-glyph { font-size: 20px; line-height: 1; }
.st-empty-kind {
  white-space: nowrap;
  padding: 3px 8px;
  border-radius: var(--r-xs);
  background: rgba(var(--t-violet), 0.14);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(var(--t-violet), 0.95);
}
/* mock: `font-family:Unbounded;font-size:14px;font-weight:800;line-height:1.25` */
.st-empty-title {
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 800;
  line-height: 1.25;
  text-wrap: pretty;
}
/* mock: `font-size:11px;line-height:1.55;color:rgba(236,231,250,.62)` */
.st-empty-cause {
  font-size: 11px;
  line-height: 1.55;
  color: rgba(var(--t-mute), 0.62);
  text-wrap: pretty;
}
/* mock: `min-height:40px;padding:11px 16px;border-radius:11px;
   background:rgba(167,139,250,.16);border:1px solid rgba(167,139,250,.5);
   font-family:Unbounded;font-size:11px;font-weight:800` */
.st-empty-act {
  align-self: flex-start;
  min-height: 40px;
  padding: 11px 16px;
  border-radius: var(--r-md);
  background: rgba(var(--t-violet), 0.16);
  border: 1px solid rgba(var(--t-violet), 0.5);
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 800;
  color: rgb(var(--t-violet));
  cursor: pointer;
}
@media (hover: hover) {
  .st-empty-act:hover { transform: translateY(-1px); }
}

/* --- the preview seat ---
   mock: `display:flex;flex-direction:column;min-height:0;gap:9px` */
.st-seat {
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 10px;
  min-width: 0;
}
.st-seat .fen-board { max-width: 360px; margin: 6px auto; }

/* mock: `display:flex;align-items:center;gap:11px;padding:10px 14px;
   border-radius:14px;border:1px solid rgba(167,139,250,.18);
   background:rgba(18,12,34,.56)` */
.st-seat-card {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 14px;
  border-radius: 14px;
  border: 1px solid var(--stroke);
  background: var(--panel);
  min-width: 0;
}
/* mock: `width:32px;height:32px;border-radius:10px;font-size:15px;
   background:linear-gradient(135deg,#241638,#120c22);
   border:1px solid rgba(167,139,250,.28)` */
.st-seat-av {
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  border-radius: 10px;
  display: grid;
  place-items: center;
  font-size: 15px;
  line-height: 1;
  background: linear-gradient(135deg, #241638, #120c22);
  border: 1px solid rgba(var(--t-violet), 0.28);
}
.st-seat-who { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
/* mock: `font-size:13px;font-weight:800` / `font-size:10px;color:rgba(...,.44)` */
.st-seat-name {
  font-size: 13px;
  font-weight: 800;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.st-seat-sub { font-size: 10px; color: rgba(var(--t-mute), 0.44); }
/* mock: `font-family:'JetBrains Mono';font-size:21px;font-weight:700;
   padding:4px 12px;border-radius:9px;background:rgba(167,139,250,.09);
   color:rgba(236,231,250,.72)` */
.st-seat-clock {
  flex: 0 0 auto;
  font-size: 21px;
  font-weight: 700;
  line-height: 1.15;
  padding: 4px 12px;
  border-radius: var(--r-sm);
  background: rgba(var(--t-violet), 0.09);
  color: rgba(var(--t-mute), 0.72);
}
.st-seat-clock:empty { display: none; }

/* mock: `display:flex;align-items:center;gap:9px;padding:10px 14px;
   border-radius:13px;border:1px dashed rgba(167,139,250,.26);
   background:rgba(16,11,30,.5)` with the note at 11/700 rgba(...,.6) */
.st-turn {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 10px 14px;
  border-radius: 13px;
  border: 1px dashed rgba(var(--t-violet), 0.26);
  background: rgba(16, 11, 30, 0.5);
}
.st-turn-txt { font-size: 11px; font-weight: 700; color: rgba(var(--t-mute), 0.6); }
.st-turn-ply { font-size: 11px; font-weight: 700; color: var(--gold); }

/* The rails with no public state endpoint: a seat card is real, the position
   is not claimed. Same dashed register as the mock's other "nothing here yet"
   blocks so it never reads as a broken board. */
.st-noboard {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 22px 16px;
  border-radius: 13px;
  border: 1px dashed rgba(var(--t-violet), 0.26);
  background: rgba(16, 11, 30, 0.5);
}
.st-noboard-glyph { font-size: 22px; line-height: 1; }
.st-noboard-txt {
  font-size: 10px;
  line-height: 1.45;
  color: rgba(var(--t-mute), 0.5);
  text-wrap: pretty;
}

.st-seat-blank {
  margin: 0;
  padding: 26px 14px;
  text-align: center;
  font-size: 11px;
  line-height: 1.5;
  color: rgba(var(--t-mute), 0.5);
  text-wrap: pretty;
}

/* The one primary action on this screen. mock's own primary button:
   `padding:11px 22px;border-radius:11px;
   background:linear-gradient(135deg,#f1c152,#d99a26);color:#1a1204;
   font-family:Unbounded;font-size:12px;font-weight:800` */
.st-enter {
  min-height: 44px;
  padding: 11px 22px;
  border-radius: var(--r-md);
  border: 0;
  background: var(--gold-grad);
  color: var(--gold-ink);
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 800;
  cursor: pointer;
  transition: transform var(--dur-fast), filter var(--dur-fast);
}
@media (hover: hover) {
  .st-enter:hover { transform: translateY(-2px); filter: brightness(1.06); }
}

/* Skeletons at the FINAL geometry, so nothing moves when the answer lands. */
.st-sk-row { height: 52px; border-radius: 12px; margin: 0; }
.st-sk-seat { height: 54px; border-radius: 14px; margin: 0; }
.st-sk-board { aspect-ratio: 1; max-width: 360px; width: 100%; margin: 6px auto; border-radius: 6px; }

/* --- two columns, once the scene can hold them ---
   mock (console): `250px minmax(0,1fr) 290px`. Without the chat column the
   list keeps its 250px track and the seat takes the rest. */
@container scene (min-width: 620px) {
  #view.st-host,
  #scene.shell-scene #view.st-host { padding: 16px 18px; }

  .st-grid { grid-template-columns: 250px minmax(0, 1fr); }
  .st-list-panel { order: 1; }
  .st-seat-panel { order: 2; }
  .st-head { gap: 13px; padding: 13px 17px; }
  .st-head-icon { font-size: 21px; }
  .st-title { font-size: 16px; }
  .st-sub { font-size: 10px; }
}

/* --- huge scene: the board grows, the list scrolls in its panel ---------
   The stand is FRAME_HOME (ui/shell.js): the scene is the window minus the
   rail, so 1728×1080 is a 1650px scene — the `huge` class (shell.css
   «WINDOW CLASSES»). Two things happen there, both the screen's content:

   * The spectator board — the reason this screen exists — stopped at the
     mock's 360px inside a seat column that is now ~1300px wide. It takes
     the room: 580px, still centred, still narrower than its column.
   * A busy evening's list (sixteen live games) used to push the scene past
     its frame at 1728 — measured 108% fill, 81px of scene scroll, which
     breaks the frame's law. The screen now owns the scene's exact height
     and the list scrolls inside its own panel — the one live panel scroller
     the density law allows. A scroller needs a definite box to scroll in,
     so the height chain is spelled out: the host takes the scene's height,
     the column fills it, the grid takes what is left, and only then does
     the list flex and overflow. Every row stays one scroll away; the frame
     no longer moves. */
@container scene (min-width: 1600px) {
  #view.st-host,
  #scene.shell-scene #view.st-host { height: 100%; }
  .view-stand { height: 100%; }
  .st-grid { flex: 1 1 auto; min-height: 0; grid-template-columns: 300px minmax(0, 1fr); align-items: stretch; }
  .st-list-panel { min-height: 0; }
  .st-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; scrollbar-width: thin; }
  .st-seat-panel .st-seat { flex: 1 1 auto; justify-content: center; }
  .st-seat .fen-board { max-width: 580px; }
  .st-sk-board { max-width: 580px; }
}

/* 2560×1440 (scene 2482): the seat column is ~2100px wide, so the spectator
   board — already grown to 580 — can take more of it without becoming the
   absurd full-width square a 1440p column would otherwise invite. */
@container scene (min-width: 2200px) {
  .st-seat .fen-board { max-width: 680px; }
  .st-sk-board { max-width: 680px; }
}

/* ==========================================================================
   «ПОЛИГОН» — views/training.js
   ========================================================================== */

/* mock: `padding:16px 18px` on a console, `0 15px 12px` on a phone. */
#view.trn-host,
#scene.shell-scene #view.trn-host {
  max-width: none;
  padding: 0 15px 12px;
  overflow-x: hidden;
}

/* mock (console): `display:grid;grid-template-columns:minmax(0,1.32fr)
   minmax(0,1fr);gap:13px`. On a phone the two columns are one scroll. */
.view-training {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 13px;
  width: 100%;
  align-items: start;
}
.view-training > * { min-width: 0; }

/* mock: `display:flex;flex-direction:column;gap:11px` */
.trn-main { display: flex; flex-direction: column; gap: 11px; min-width: 0; }
/* mock: `display:flex;flex-direction:column;gap:12px` */
.trn-side { display: flex; flex-direction: column; gap: 12px; min-width: 0; }

/* --- the head ---
   mock (console): `display:flex;align-items:center;gap:12px`, a 24px glyph, the
   title in Unbounded 15/700 over a 10px line at .45, and a gold pill on the
   right. On a phone: `gap:9px`, a 19px glyph and a 14px title. */
.trn-head { display: flex; align-items: center; gap: 9px; }
.trn-head-icon { font-size: 19px; line-height: 1; }
.trn-head-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.trn-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 700;
  line-height: 1.2;
}
.trn-sub { font-size: 10px; color: rgba(var(--t-mute), 0.45); text-wrap: pretty; }
/* mock: `padding:5px 11px;font-size:10px` on a console, `padding:4px 9px;
   font-size:9px` on a phone; `border-radius:999px;
   background:rgba(241,193,82,.11);border:1px solid rgba(241,193,82,.3)` on both. */
.trn-streak {
  flex: 0 0 auto;
  white-space: nowrap;
  padding: 4px 9px;
  border-radius: var(--r-pill);
  background: rgba(var(--t-gold), 0.11);
  border: 1px solid rgba(var(--t-gold), 0.3);
  font-size: 9px;
  font-weight: 800;
  color: var(--gold);
}
.trn-streak[hidden] { display: none; }

/* --- the discipline strip ---
   mock: `display:flex;gap:6px` with `flex:1` tabs of
   `padding:7px 2px;border-radius:11px`, a 15px icon over a 9px mono line, and
   `border:1px solid rgba(241,193,82,.55)` + `background:rgba(241,193,82,.12)`
   on the selected one. The phone version keeps a 44px floor. */
.trn-tabs { display: flex; gap: 5px; flex-wrap: wrap; }
/* mock: `gap:6px` on a console, `gap:5px` on a phone.
   The mock's strip is nine `flex:1` tabs across the column, ~56px each. The
   catalogue has four, and four tabs stretched across the same column read as
   four buttons rather than one strip — so they keep the mock's proportion and
   stop growing. */
.trn-tab {
  flex: 1 1 0;
  min-width: 0;
  max-width: 122px;
  min-height: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 7px 2px;
  border-radius: var(--r-md);
  border: 1px solid rgba(var(--t-violet), 0.16);
  background: var(--panel-soft);
  cursor: pointer;
  transition: border-color var(--dur-fast), background var(--dur-fast);
}
@media (hover: hover) {
  .trn-tab:hover { border-color: rgba(var(--t-gold), 0.5); }
}
.trn-tab.is-on {
  border-color: rgba(var(--t-gold), 0.55);
  background: rgba(var(--t-gold), 0.12);
}
.trn-tab-icon { font-size: 15px; line-height: 1; }
/* mock: `font-size:9px;font-weight:700` in mono, gold when selected and
   rgba(236,231,250,.34) otherwise. The mock's third tone is red for a weak
   discipline; ours is green for a finished series — the same three-tone
   system, saying the thing this screen can actually know. */
.trn-tab-num {
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
  color: rgba(var(--t-mute), 0.34);
}
.trn-tab.is-on .trn-tab-num { color: var(--gold); }
.trn-tab-num.is-full { color: rgba(var(--t-ok), 0.7); }
.trn-tab.is-on .trn-tab-num.is-full { color: var(--gold); }

/* --- the drill panel ---
   mock (console): `display:flex;flex-direction:column;gap:11px;
   padding:14px 15px;border-radius:16px;border:1px solid rgba(167,139,250,.18);
   background:rgba(18,12,34,.56)`. On a phone: `gap:9px;padding:12px 13px;
   border-radius:14px`. */
.trn-panel {
  container-type: inline-size;
  container-name: drill;
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 12px 13px;
  border-radius: 14px;
  border: 1px solid var(--stroke);
  background: var(--panel);
  min-width: 0;
}

/* mock: `display:flex;align-items:center;flex-wrap:wrap` at `gap:9px` on a
   console and `gap:7px` on a phone. */
.trn-meta { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
/* mock: `padding:4px 10px;border-radius:7px;background:rgba(167,139,250,.12);
   border:1px solid rgba(167,139,250,.26);font-size:10px;font-weight:800;
   color:#c4b5fd` */
.trn-badge {
  padding: 4px 10px;
  border-radius: var(--r-xs);
  background: rgba(var(--t-violet), 0.12);
  border: 1px solid rgba(var(--t-violet), 0.26);
  font-size: 10px;
  font-weight: 800;
  color: var(--violet-300);
  white-space: nowrap;
}
/* mock: `font-size:12px;font-weight:800` */
.trn-name { font-size: 12px; font-weight: 800; }
/* The mock's PHONE meta row is four things — badge, name, spacer, stars — and
   drops the variant line and the solved count entirely. Both come back on a
   console. */
.trn-variants,
.trn-solved { display: none; }
/* mock: `font-size:10px;color:rgba(236,231,250,.34)` / `rgba(236,231,250,.4)` */
.trn-variants {
  /* `flex-basis: 0` and not `auto`: flexbox decides where to WRAP from the
     hypothetical size, then shrinks — so an `auto` basis put the difficulty
     stars alone on a second row at our 460px column even though everything
     fits once it has shrunk. This line is the one thing in the row that can
     lose characters without losing a fact. */
  flex: 1 1 0;
  min-width: 0;
  font-size: 10px;
  color: rgba(var(--t-mute), 0.34);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.trn-solved { font-size: 10px; color: rgba(var(--t-mute), 0.4); white-space: nowrap; }
/* mock: `font-size:11px;color:rgba(241,193,82,.7);letter-spacing:.08em` */
.trn-diff {
  font-size: 11px;
  letter-spacing: 0.08em;
  color: rgba(var(--t-gold), 0.7);
  white-space: nowrap;
}

/* mock: `display:flex;gap:14px;align-items:flex-start` — the board on the left
   at the mock's 264px, the question and the answers taking the rest. The
   board keeps that width as its FLOOR and grows with the panel up to a
   ceiling — see `.trn-visual` in the @container block below. Below the width
   where that leaves the answers unreadable it becomes the phone arrangement:
   board on top, answers under it. */
.trn-body { display: flex; flex-direction: column; gap: 11px; min-width: 0; }
/* mock (phone): `width:calc(100% - 14px);margin:6px auto 0` */
.trn-visual {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 11px;
  width: calc(100% - 14px);
  margin: 6px auto 0;
  min-width: 0;
}
/* mock: `display:flex;align-items:center;gap:8px` under the board */
.trn-boardnote {
  display: flex;
  align-items: center;
  gap: 8px;
  align-self: stretch;
  padding: 0 2px;
}
/* mock: `width:10px;height:10px;border-radius:50%;background:#fdfbf6;
   box-shadow:0 0 0 1px rgba(59,43,24,.9),0 0 10px rgba(255,255,255,.3)` */
.trn-dot {
  width: 10px;
  height: 10px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: #fdfbf6;
  box-shadow: 0 0 0 1px rgba(59, 43, 24, 0.9), 0 0 10px rgba(255, 255, 255, 0.3);
}
.trn-dot.is-black {
  background: #17110a;
  box-shadow: 0 0 0 1px rgba(240, 217, 181, 0.8), 0 0 10px rgba(0, 0, 0, 0.5);
}
/* mock: `font-size:10px;font-weight:700;line-height:1.35;
   color:rgba(236,231,250,.62)` */
.trn-boardnote-txt {
  font-size: 10px;
  font-weight: 700;
  line-height: 1.35;
  color: rgba(var(--t-mute), 0.62);
  text-wrap: pretty;
}

.trn-answer { display: flex; flex-direction: column; gap: 9px; min-width: 0; flex: 1; }
/* mock: `font-size:13px;font-weight:700;line-height:1.45` on a console,
   11px on a phone. */
.trn-q {
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  line-height: 1.45;
  text-wrap: pretty;
}
/* mock: `display:flex;flex-wrap:wrap;gap:5px` chips of
   `padding:4px 10px;border-radius:7px;background:rgba(167,139,250,.1);
   border:1px solid rgba(167,139,250,.2);font-size:10px;font-weight:700` */
.trn-stats { display: flex; flex-wrap: wrap; gap: 5px; }
.trn-stat {
  padding: 4px 10px;
  border-radius: var(--r-xs);
  background: rgba(var(--t-violet), 0.1);
  border: 1px solid rgba(var(--t-violet), 0.2);
  font-size: 10px;
  font-weight: 700;
  color: rgba(var(--t-mute), 0.62);
}

/* mock: `display:flex;flex-direction:column;gap:6px` */
.trn-opts { display: flex; flex-direction: column; gap: 6px; }
/* mock: `display:flex;align-items:center;gap:10px;padding:11px 13px;
   border-radius:11px;text-align:left;transition:.16s;
   border:1px solid rgba(167,139,250,.18);background:rgba(26,17,48,.5)`;
   answered-correct `rgba(53,199,131,.6)` / `rgba(53,199,131,.1)`;
   answered-wrong `rgba(255,93,108,.6)` / `rgba(255,93,108,.08)` */
.trn-opt {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 13px;
  min-height: 44px;
  border-radius: var(--r-md);
  text-align: left;
  border: 1px solid var(--stroke);
  background: rgba(26, 17, 48, 0.5);
  cursor: pointer;
  transition: border-color var(--dur-fast), background var(--dur-fast);
}
@media (hover: hover) {
  .trn-opt:hover:not(:disabled) { border-color: rgba(var(--t-gold), 0.5); }
}
.trn-opt:disabled { cursor: default; }
.trn-opt.is-ok { border-color: rgba(var(--t-ok), 0.6); background: rgba(var(--t-ok), 0.1); }
.trn-opt.is-bad { border-color: rgba(var(--t-danger), 0.6); background: rgba(var(--t-danger), 0.08); }
/* mock: `width:22px;height:22px;border-radius:7px;font-size:10px;
   font-weight:800;background:rgba(167,139,250,.12);
   color:rgba(236,231,250,.5)` */
.trn-opt-key {
  width: 22px;
  height: 22px;
  flex: 0 0 auto;
  border-radius: var(--r-xs);
  display: grid;
  place-items: center;
  font-size: 10px;
  font-weight: 800;
  background: rgba(var(--t-violet), 0.12);
  color: rgba(var(--t-mute), 0.5);
}
/* mock: `flex:1;text-align:left;font-size:12px;font-weight:700;
   line-height:1.35` (11px on a phone) */
.trn-opt-txt {
  flex: 1;
  min-width: 0;
  text-align: left;
  font-size: 11px;
  font-weight: 700;
  line-height: 1.3;
}
/* mock: `font-size:13px;font-weight:800` in the outcome's colour */
.trn-opt-mark { flex: 0 0 auto; font-size: 13px; font-weight: 800; }
.trn-opt.is-ok .trn-opt-mark { color: var(--ok); }
.trn-opt.is-bad .trn-opt-mark { color: var(--danger); }

/* mock: `display:flex;flex-direction:column;gap:7px;padding:13px 15px;
   border-radius:13px;animation:ac-rise .18s ease-out` with the border and wash
   tinted by the outcome. */
.trn-explain {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 13px 15px;
  border-radius: 13px;
  animation: ac-rise 0.18s ease-out;
}
.trn-explain.is-ok { border: 1px solid rgba(var(--t-ok), 0.3); background: rgba(var(--t-ok), 0.06); }
.trn-explain.is-bad { border: 1px solid rgba(var(--t-danger), 0.3); background: rgba(var(--t-danger), 0.05); }
.trn-verdict-row { display: flex; align-items: center; gap: 9px; }
/* mock: `font-family:Unbounded;font-size:12px;font-weight:800` */
.trn-verdict { font-family: var(--font-display); font-size: 12px; font-weight: 800; }
.trn-verdict.is-ok { color: var(--ok); }
.trn-verdict.is-bad { color: var(--danger); }
/* mock: `padding:7px 16px;border-radius:9px;font-size:11px` on a console and
   `padding:6px 13px;border-radius:8px;font-size:10px` on a phone;
   `background:rgba(241,193,82,.14);border:1px solid rgba(241,193,82,.34);
   font-weight:800` on both. */
.trn-next {
  flex: 0 0 auto;
  min-height: 36px;
  padding: 6px 13px;
  border-radius: 8px;
  background: rgba(var(--t-gold), 0.14);
  border: 1px solid rgba(var(--t-gold), 0.34);
  font-size: 10px;
  font-weight: 800;
  color: var(--gold);
  cursor: pointer;
  white-space: nowrap;
}
@media (hover: hover) {
  .trn-next:hover { background: rgba(var(--t-gold), 0.26); }
}
/* mock: `color:rgba(236,231,250,.74)` at 11px/1.55 on a console and 10px/1.5
   on a phone. */
.trn-explain-txt {
  font-size: 10px;
  line-height: 1.5;
  color: rgba(var(--t-mute), 0.74);
  text-wrap: pretty;
}

/* Answered and genuinely empty — never the same sentence as "could not load". */
.trn-blank {
  margin: 0;
  padding: 22px 12px;
  text-align: center;
  font-size: 11px;
  line-height: 1.5;
  color: rgba(var(--t-mute), 0.5);
  text-wrap: pretty;
}

/* --- interactive chess puzzle board (Chess.com-style) --- */
.trn-cg-wrap {
  width: 100%;
  max-width: 400px;
  aspect-ratio: 1;
  margin: 0 auto;
  border-radius: 6px;
  overflow: hidden;
  box-shadow: 0 0 0 3px rgba(30, 21, 13, 0.85), 0 0 0 4px rgba(241, 193, 82, 0.28), 0 12px 32px rgba(0, 0, 0, 0.5);
}
.trn-q.is-wrong {
  color: var(--danger);
  animation: trn-shake 0.3s ease-out;
}
.trn-q.is-solved {
  color: var(--ok);
}
.trn-puzzle-progress {
  font-size: 10px;
  color: rgba(var(--t-mute), 0.6);
  margin-top: 4px;
}
.trn-solved-icon {
  font-weight: 800;
}
@keyframes trn-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-4px); }
  40% { transform: translateX(4px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(3px); }
}

/* --- the series column ---
   The mock's «Где ты теряешь» panel, at its geometry, carrying the progress
   this screen can actually load. mock: `border-radius:16px;
   border:1px solid rgba(167,139,250,.18);background:rgba(18,12,34,.56)` with
   a `padding:11px 15px` head and a `padding:12px 15px;gap:12px` body. */
/* The phone has no card here at all: the mock draws a bare 10px uppercase
   heading with the rows under it, and the card arrives on a console. */
.trn-series {
  display: flex;
  flex-direction: column;
  gap: 9px;
  min-width: 0;
}
.trn-panel-head {
  display: flex;
  align-items: center;
  gap: 8px;
}
.trn-panel-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-section);
  font-weight: var(--fw-panel-head);
  letter-spacing: var(--ls-panel-head);
  text-transform: uppercase;
  line-height: 1.2;
}
/* mock: `font-size:9px;color:rgba(236,231,250,.36)` */
.trn-panel-note { font-size: 9px; color: rgba(var(--t-mute), 0.36); white-space: nowrap; }
.trn-series-body {
  display: flex;
  flex-direction: column;
  gap: 9px;
  min-width: 0;
}

/* mock: `display:flex;flex-direction:column` at `gap:5px` on a console and
   `gap:4px` on a phone. */
.trn-leak { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
/* mock: `display:flex;align-items:baseline` at `gap:8px` / `gap:7px` */
.trn-leak-top { display: flex; align-items: baseline; gap: 7px; min-width: 0; }
/* mock: 13px on a console, 12px on a phone. */
.trn-leak-icon { font-size: 12px; line-height: 1; }
/* mock: `font-weight:700` at 11px on a console and 10px on a phone. */
.trn-leak-name {
  font-size: 10px;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* mock: `font-family:'JetBrains Mono';font-size:11px;font-weight:700` in the
   outcome colour — red for a leak. Ours is a series count: gold while it is
   open, green when the series is finished. */
.trn-leak-num { font-size: 11px; font-weight: 700; color: var(--gold); white-space: nowrap; }
.trn-leak-num.is-full { color: var(--ok); }
/* mock: `background:rgba(167,139,250,.13)` at `height:6px;border-radius:4px`
   on a console and `height:5px;border-radius:3px` on a phone. */
.trn-leak-bar {
  height: 5px;
  border-radius: 3px;
  background: rgba(var(--t-violet), 0.13);
  overflow: hidden;
}
/* mock: `border-radius:4px;background:linear-gradient(90deg,#ff5d6c,#7c3aed)`
   for a leak and `linear-gradient(90deg,#35c783,#7c3aed)` for a strength. */
.trn-leak-fill {
  height: 100%;
  border-radius: 4px;
  background: linear-gradient(90deg, var(--gold), var(--violet-600));
}
.trn-leak-fill.is-full { background: linear-gradient(90deg, var(--ok), var(--violet-600)); }
/* mock: `display:flex;align-items:baseline` with the tip at
   `color:rgba(236,231,250,.4);line-height:1.4`, 10px/gap 8 on a console and
   9px/gap 7 on a phone. */
.trn-leak-foot { display: flex; align-items: baseline; gap: 7px; }
.trn-leak-tip {
  flex: 1;
  min-width: 0;
  font-size: 9px;
  line-height: 1.4;
  color: rgba(var(--t-mute), 0.4);
  text-wrap: pretty;
}
/* mock: `white-space:nowrap;font-size:10px;font-weight:800;
   color:rgba(241,193,82,.8)` and rgba(236,231,250,.3) when there is nothing
   to do. */
.trn-leak-go {
  flex: 0 0 auto;
  white-space: nowrap;
  background: transparent;
  border: 0;
  padding: 0;
  font-size: 10px;
  font-weight: 800;
  color: rgba(var(--t-gold), 0.8);
  cursor: pointer;
  transition: color var(--dur-fast);
}
@media (hover: hover) {
  .trn-leak-go:hover { color: var(--gold); }
}
.trn-leak-go.is-here { color: rgba(var(--t-mute), 0.3); cursor: default; }

/* Skeletons at the FINAL geometry. */
.trn-sk-badge { width: 88px; height: 22px; border-radius: var(--r-xs); margin: 0; }
.trn-sk-name { width: 120px; height: 16px; border-radius: var(--r-xs); margin: 0; }
.trn-sk-board { width: 100%; aspect-ratio: 1; border-radius: 6px; margin: 0; }
.trn-sk-q { height: 32px; border-radius: var(--r-xs); margin: 0; }
.trn-sk-opt { height: 44px; border-radius: var(--r-md); margin: 0; }
.trn-sk-leak { height: 46px; border-radius: var(--r-xs); margin: 0; }

/* --- board beside answers, once the panel can hold both ---
   mock: `flex:0 0 auto;width:264px;padding:8px 8px 0` for the visual column,
   `gap:14px` between it and the answers. Below 446px of panel the answers
   would be narrower than a wrapped option can survive, so the phone's stacked
   reading is kept instead. At a 1280px window our scene is 858px, which leaves
   this panel 430px inside its padding — the mock's own narrowest console (the
   834 class, 454px) is only 24px wider, so the threshold sits below both and
   the console reading is the mock's. */
@container drill (min-width: 420px) {
  .trn-body { flex-direction: row; align-items: flex-start; gap: 14px; }
  /* The 44px floor is a thumb rule; the mock's console option is its natural
     `padding:11px 13px` tall and nothing more. */
  .trn-opt { min-height: 0; }
  .trn-body.is-bare { display: block; }
  .trn-visual {
    flex: 0 0 auto;
    /* THE MOCK'S 264px IS A FLOOR NOW, NOT A CONSTANT. It is right at the
       mock's own panel width and it is what the 1280 window draws, so that
       reading is kept exactly. But the panel grows with the scene — 460px
       at a 1280 window, 715px at 1728, 1189px at 2560 — and a board frozen
       at 248px beside an answer column that stretched from 150px to 879px
       was the whole of the "растянуто" the screen was called: measured
       before this line, the board was 248×248 at all five widths while the
       scene height under it grew 714 → 1354px.
       57cqw IS THE MOCK'S OWN PROPORTION — its 264px visual in its 454px
       panel — so the board holds the mock's ratio while the panel grows,
       and the 264px floor (not the percentage) is what pins the 1280
       reading: cqw reads the panel's content box, 429px there, where 57%
       is 245px and the floor answers 264px. The 456px ceiling is a 440px
       board: the stand's preview seat caps its board at 360px and the
       hero of this screen earns more than that, but uncapped on a 2560px
       window the same proportion is a 644px board eating the question it
       exists to ask. Measured board, width-pinned iframes on the real
       stylesheet:
         1280  248   (pixel-identical to before — the signed-off reading)
         1512  303    1728  373    1920  436    2560  440
       cqw of `drill` rather than a new @container step: the board scales
       with the panel it sits in, continuously, the same way `.fen-pc`
       already scales its glyphs in cqw of the board itself. */
    width: clamp(264px, 57cqw, 456px);
    padding: 8px 8px 0;
    margin: 0;
  }
  /* The row's flexible track. With the board freed it was the thing still
     spending the width: 879px of answer buttons on a 2560px window, wider
     than the board beside them. One ceiling on the column, not per child —
     the question, the stat chips and the four options share a reading
     measure, and 640px leaves the row 456 + 14 + 640 = 1110px, under the
     1157px body the 2560 window produces. Measured inert at 1920 and below
     (the column is 327px there); it first bites around a 2460px window. */
  .trn-answer { max-width: 640px; }
  .trn-q { font-size: 13px; }
  .trn-opt-txt { font-size: 12px; line-height: 1.35; }
}

/* --- two columns, once the scene can hold them ---
   mock (console): `minmax(0,1.32fr) minmax(0,1fr)` with `gap:13px`. */
@container scene (min-width: 620px) {
  #view.trn-host,
  #scene.shell-scene #view.trn-host { padding: 16px 18px; }

  .view-training { grid-template-columns: minmax(0, 1.32fr) minmax(0, 1fr); }

  .trn-head { gap: 12px; }
  .trn-head-icon { font-size: 24px; }
  .trn-title { font-size: 15px; }
  .trn-streak { padding: 5px 11px; font-size: 10px; }
  .trn-tabs { gap: 6px; }
  .trn-panel { gap: 11px; padding: 14px 15px; border-radius: var(--r-panel); }
  .trn-meta { gap: 9px; }
  .trn-variants { display: block; }
  .trn-solved { display: block; }
  /* The variant line is the flexible track on a console, so the spacer that
     pushes the stars right on a phone must stop competing with it. */
  .trn-meta .grow { flex: 0 0 0; }
  .trn-next { padding: 7px 16px; border-radius: var(--r-sm); font-size: 11px; }
  .trn-explain-txt { font-size: 11px; line-height: 1.55; }

  .trn-series {
    gap: 0;
    border-radius: var(--r-panel);
    border: 1px solid var(--stroke);
    background: var(--panel);
    overflow: hidden;
  }
  .trn-panel-head { padding: 11px 15px; border-bottom: 1px solid var(--hairline); }
  .trn-panel-title { font-size: var(--fs-panel-head); }
  .trn-series-body { gap: 12px; padding: 12px 15px; }
  .trn-leak { gap: 5px; }
  .trn-leak-top { gap: 8px; }
  .trn-leak-icon { font-size: 13px; }
  .trn-leak-name { font-size: 11px; }
  .trn-leak-bar { height: 6px; border-radius: 4px; }
  .trn-leak-foot { gap: 8px; }
  .trn-leak-tip { font-size: 10px; }
}
