/* ============================================================
   Oxyka PWA shell chrome — tab bar, panels, splash.
   Loaded AFTER flow.css (which owns the DROPS-gold :root tokens +
   the shipped .screen/.grid/.card/.dock/.spinner primitives). This
   file adds ONLY the shell chrome and references flow.css tokens —
   never hard-coded DROPS-gold hex. app.css is loaded on the PWA shell
   (/) ONLY; /flow (Mini App) never sees it → zero Mini App impact.
   ============================================================ */

:root {
  /* Additive shell extension (not a DROPS-gold token) — the fixed tab-bar
     content height; panels + the training dock reserve this + safe-area so
     nothing tucks under the bar. */
  --tabbar-h: 56px;
}

/* The `hidden` HTML attribute must win over the panel/main flex display. */
[hidden] { display: none !important; }

/* ============================================================
   Cold-start splash (D-07/D-09) — night bg + gold spinner while the
   silent /auth/refresh resolves. NEVER white. Reuses flow.css
   .loading/.spinner; here we only guarantee the night ground.
   ============================================================ */
.app-splash {
  background: var(--night);
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}

/* ============================================================
   Screen host / safe-area (UI-SPEC §Screen host). viewport-fit=cover
   draws under the status bar → re-inset the top here.
   ============================================================ */
.screen { padding-top: env(safe-area-inset-top); }

/* ============================================================
   Tab panels — one visible at a time, toggled by the `hidden`
   attribute from tabbar.js. The training panel hosts the flow SPA;
   the data panels are scroll hosts filled by plan 50-06.
   ============================================================ */
.app-main { position: relative; }

.panel { min-height: var(--tg-viewport-stable-height, 100vh); }
.panel[hidden] { display: none !important; }

/* Data panels carry bottom scroll clearance ≥ tab-bar + safe-area so the
   last grid row never tucks under the fixed bar (mirrors the shipped
   .flow-main padding-bottom: 96px pattern). The teacher tab hosts
   (#panel-school, #panel-plans) get the SAME edge padding — without it the
   screen-head sits under the iOS notch and the last plan row + its delete
   confirm tuck under the fixed bar (WR-02; parity with #panel-plandetail /
   #panel-roster below). */
#panel-lexicon,
#panel-sets,
#panel-profile,
#panel-school,
#panel-plans {
  padding: env(safe-area-inset-top) var(--s-4)
           calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--s-4));
}

/* The training panel hosts flow's own sticky .dock; give it the same
   clearance so the «Начать подход»/«Готово» CTA clears the tab bar.
   Shell-scoped — /flow keeps flow.css's dock padding byte-identical. */
#panel-training .dock {
  padding-bottom: calc(var(--s-4) + var(--tabbar-h) + env(safe-area-inset-bottom));
}

/* ============================================================
   Bottom tab bar (D-03) — fixed, 4 equal tabs, DROPS-gold.
   Active = --gold icon+label + top gold indicator; inactive = --fg2.
   ============================================================ */
.tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  display: flex;
  align-items: stretch;
  background: var(--surface);
  border-top: 1px solid var(--hairline);
  padding-bottom: env(safe-area-inset-bottom);
  box-shadow: 0 -6px 20px rgba(8, 10, 18, 0.5);
}

.tab {
  flex: 1 1 0;
  min-width: 0;
  /* ≥44×44px tap target (iOS HIG) even though the icon+label is smaller. */
  min-height: 52px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 7px 4px;
  position: relative;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--fg2);                 /* inactive */
  -webkit-tap-highlight-color: transparent;
  transition: color var(--dur-fast) var(--ease);
}

.tab.active { color: var(--gold); }  /* icon stroke (currentColor) + label go gold */

.tab:active { transform: scale(0.97); }

.tab-icon { width: 24px; height: 24px; display: block; }

.tab-label {
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.01em;
}
.tab.active .tab-label { font-weight: 700; }

/* Top gold indicator on the active tab. Uses the shipped --gold token + the
   --glow-soft shadow token (no raw hex); the glow is dropped under
   prefers-reduced-motion below, honouring the shipped reduced-motion path. */
.tab.active::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 28px;
  height: 2.5px;
  border-radius: 0 0 3px 3px;
  background: var(--gold);
  box-shadow: var(--glow-soft);
}

/* ============================================================
   «Все / Освоенные» segmented control (plan 50.1-01 / D-01/D-02) —
   one row directly under .screen-head, above the first .sec. Mirrors
   the shipped .count-chip.active gold-pill aesthetic (flow.css:494).
   Tokens only — no raw hex; reuses flow.css :root (no new --token).
   ============================================================ */
.seg {
  display: flex;
  gap: var(--s-1);
  padding: var(--s-1);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  margin-bottom: var(--s-4);
}

.seg-opt {
  flex: 1;
  /* ≥44×44px tap target (iOS HIG) even though the two split the row width. */
  min-height: 44px;
  border-radius: var(--r-pill);
  background: transparent;
  border: none;
  color: var(--fg2);                 /* inactive */
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--dur-fast) var(--ease);
}

/* Active option — mirrors .count-chip.active (gold gradient + on-gold text +
   soft glow); the glow is dropped under prefers-reduced-motion below. */
.seg-opt.active {
  background: var(--gold-grad);
  color: var(--on-gold);
  font-weight: 700;
  box-shadow: var(--glow-soft);
}

.seg-opt:active { transform: scale(0.97); }

/* Deck-capacity bar (plan 260715-f4e + follow-up) — under the «Изучаю» segment:
   a gold fill track (deck size / me.slot_limit) with a status + «N / LIMIT» label
   above. Reuses the app's progress-bar vocabulary (--surface track, --gold-grad
   fill). Hidden on «Освоенные» (global [hidden] rule above). Numbers-only text. */
.deck-cap {
  padding: 8px 16px 2px;
}
.deck-cap-label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 12px;
  color: var(--fg2, #8a93a8);
  margin-bottom: 5px;
}
.deck-cap-count {
  font-variant-numeric: tabular-nums;
}
.deck-cap-track {
  height: 6px;
  background: var(--surface);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.deck-cap-fill {
  width: 0%;
  height: 100%;
  background: var(--gold-grad);
  border-radius: var(--r-pill);
  transition: width var(--dur-fast) var(--ease);
}

/* ============================================================
   «Ждут места» overflow section (60-09, UXS-02 / D-06).
   Sits under the «Изучаю» deck grid. Muting is opacity ONLY —
   no new token, no new hex, and explicitly no selection ring:
   these cards are not selectable and not trainable. The shipped
   `.sec` divider and `.grid` are reused untouched; the classes
   below only scope rhythm, the neutral dot tint and the muting.
   Tokens only, dark-only (no light-mode branch).
   ============================================================ */
/* --s-6 section rhythm between the deck grid and the queue, and --s-8 plus
   the safe area under the last card so it clears the fixed tabbar (the 58
   WR-02 regression class). `.sec:first-child` would otherwise collapse the
   divider's own top margin to 2px inside this wrapper. */
.pending-sec-wrap {
  display: block;
  margin-top: var(--s-6);
  padding-bottom: calc(var(--s-8) + env(safe-area-inset-bottom));
}
/* `.sec .dot` paints itself from currentColor. Waiting for a slot is neither
   an error nor an action, so the divider stays quiet rather than taking one
   of the 🔴🟡🟢✨ status accents. */
.pending-sec { color: var(--fg-mute); }
/* Cards can differ in height once an action row hangs under them; a stretched
   `.fig` would drag its bottom-pinned `.name` overlay off the gif. */
.pending-grid { align-items: start; }
.pending-cell { display: block; min-width: 0; }
/* The muting itself — reduced emphasis on the SHIPPED card, nothing else. */
.pending-card { opacity: .55; }
/* The card carries no tap target, so it must not advertise one: `.fig` ships a
   pointer cursor and a press transform for a drill-in card. */
.pending-card,
.pending-card:active { cursor: default; transform: none; }
/* «Активировать» row — a sibling UNDER the muted card, never inside it. */
.pending-actions { margin-top: var(--s-2); }
/* Compact the shipped .cta for a grid cell (the .pd-invite-btn precedent):
   only the box and the label size are scoped down. .cta's own min-height
   keeps the ≥44px iOS HIG tap band, and its fill/weight are untouched — the
   secondary variant supplies the ground and the disabled state. */
.pending-activate {
  width: 100%;
  max-width: none;
  padding: var(--s-2) var(--s-3);
  font-size: var(--t-label);
}
/* Slot-free hint + inline activate failure — both reuse a shipped text
   primitive (.state-body / .selector-error) so neither declares a size,
   a weight or a colour of its own; only their rhythm is scoped here. */
.pending-hint { margin: 0 0 var(--s-3); max-width: none; }
.pending-err { margin: 0 0 var(--s-3); }

/* ============================================================
   «Наборы» — set list rows + drill-in header (plan 50-06 / D-04;
   UI-SPEC assigns .set-row to app.css). Tokens only — no raw hex.
   ============================================================ */
.set-list { display: flex; flex-direction: column; gap: var(--s-2); }

.set-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  width: 100%;
  min-height: 44px;                 /* iOS HIG tap target */
  padding: var(--s-3) var(--s-4);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  color: var(--fg1);
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.set-row:active { background: var(--surface-2); transform: scale(0.985); }

.set-row-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.set-row-name {
  font-family: var(--font-ui);
  font-size: var(--t-h);
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.set-row-count { font-size: var(--t-label); font-weight: 600; color: var(--fg2); }
.set-row-chev { flex: none; color: var(--fg3); }

.set-head { align-items: flex-start; }
.set-back {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 44px;                 /* iOS HIG tap target */
  padding: 0;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--fg2);
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 600;
  -webkit-tap-highlight-color: transparent;
}
.set-back:active { color: var(--fg1); }

/* ============================================================
   Figure detail overlay (plan 50-06, D-04/D-05) — fixed panel ABOVE
   the originating grid tab; the focal element is the full <video>.
   UI-SPEC assigns .detail/.status-selector to app.css. Tokens only.
   ============================================================ */
.detail-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;                       /* above panels (0) and the tabbar (40) */
  background: var(--night);
  overflow: hidden;
}
.detail-overlay[hidden] { display: none !important; }

/* Freeze the grid behind while the overlay scrolls its own content. */
body.detail-open { overflow: hidden; }

.detail-scroll {
  height: 100%;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: env(safe-area-inset-top) 0
           calc(env(safe-area-inset-bottom) + var(--s-6));
}

.detail-topbar {
  display: flex;
  justify-content: flex-end;
  padding: var(--s-2) var(--s-3);
}
.detail-close {
  width: 44px;                       /* iOS HIG tap target */
  height: 44px;
  display: grid;
  place-items: center;
  background: var(--scrim);
  border: 1px solid var(--hairline);
  border-radius: 50%;
  color: var(--fg1);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.detail-close:active { background: var(--surface-2); }

.detail-media {
  margin: 0 var(--s-4);
  border-radius: var(--r-img);
  overflow: hidden;
  background: var(--surface);
}
.detail-media video {
  width: 100%;
  max-height: 62vh;
  display: block;
  background: var(--night-deep);
}
.detail-media .gif { width: 100%; aspect-ratio: 4/5; border-radius: 0; }

.detail-body { padding: var(--s-5) var(--s-4) 0; }

.detail-name {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: 0 0 var(--s-4);
  font-family: var(--font-serif);
  font-size: var(--t-title);
  font-weight: 700;
  line-height: 1.15;
  color: var(--fg1);
}

.detail-meta {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin-bottom: var(--s-5);
}
.detail-meta-row {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  font-size: var(--t-body);
}
.detail-meta-label {
  flex: none;
  min-width: 96px;
  font-size: var(--t-caps);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--fg3);
}
.detail-meta-value { color: var(--fg1); }
.detail-count { font-variant-numeric: tabular-nums; font-weight: 700; }

/* «Обновлена» detail banner (57-05, PROP-03) — a low-key informational plate
   above the figure name; the gold left-rule is the single accent moment. Tokens
   only, static copy, dark-only (no dismiss — opening the card is the clear). */
.detail-updated {
  margin: 0 0 var(--s-4);
  padding: var(--s-3);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border-left: 2px solid var(--gold);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.45;
  color: var(--fg1);
}

/* «Как я танцую» selector — the applied option carries the gold ring. */
.selector-label {
  font-size: var(--t-caps);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--fg2);
  margin-bottom: var(--s-3);
}
.selector-options {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-3);
}
.status-opt {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 44px;                  /* iOS HIG tap target */
  padding: var(--s-3);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  color: var(--fg1);
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 600;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.status-opt:active { transform: scale(0.97); }
.status-opt.sel {
  box-shadow: var(--ring-gold);      /* shipped selection ring (.fig.sel) */
  transform: translateY(-2px);
  border-color: transparent;
  font-weight: 700;
}
.selector-error {
  margin: var(--s-3) 0 0;
  font-size: var(--t-body);
  color: var(--st-count);
}

/* ============================================================
   «Профиль» mini-cabinet (plan 50-06; Phase 49 D-05 inheritance).
   Destructive «Выйти на всех устройствах» = red --st-count ghost —
   the single destructive action of the phase (UI-SPEC §Color).
   ============================================================ */
.profile {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}
.profile-identity {
  font-family: var(--font-serif);
  font-size: var(--t-title);
  font-weight: 700;
  line-height: 1.15;
  color: var(--fg1);
}
.profile-row { font-size: var(--t-body); color: var(--fg2); }
.profile-check { color: var(--st-music); font-weight: 700; }
.profile-link { color: var(--gold); font-weight: 600; text-decoration: none; }
.profile-link:active { color: var(--gold-deep); }

/* «РЕЖИМ» role block (ROLE-03) — a thin column wrapper so the caps label,
   role line, .seg toggle and mode badge group cleanly inside the .profile
   column. Reuses shipped primitives only (.seg/.seg-opt, .profile-referral-label,
   .profile-row) — no new gold badge/border/glow, no new token, no raw hex. */
.profile-role {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
/* The shipped .seg carries margin-bottom: var(--s-4) (app.css:146); drop it here
   so the toggle doesn't double-space against the column's own gap. */
.profile-role .seg { margin-bottom: 0; }

.profile-actions {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin-top: var(--s-4);
}

/* Red-label ghost button — destructive, deliberately NOT gold. */
.btn-ghost-danger {
  width: 100%;
  min-height: 44px;                  /* iOS HIG tap target */
  padding: var(--s-3);
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  color: var(--st-count);
  font-family: var(--font-ui);
  font-size: var(--t-body);
  font-weight: 600;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.btn-ghost-danger:active { background: var(--surface); }

.profile-confirm {
  padding: var(--s-4);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
}
.profile-confirm-q {
  margin: 0 0 var(--s-3);
  font-size: var(--t-body);
  line-height: 1.45;
  color: var(--fg1);
}
.profile-confirm-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-2);
}

/* Reduced-motion: honour the shipped path (flow.css flips --motion/--glow-k→0)
   — drop the tab transitions + the indicator glow this file introduces
   (no unconditional animation added). */
@media (prefers-reduced-motion: reduce) {
  .tab { transition: none; }
  .tab:active { transform: none; }
  .tab.active::before { box-shadow: none; }
  .seg-opt { transition: none; }
  .seg-opt:active { transform: none; }
  .seg-opt.active { box-shadow: none; }
  .set-row { transition: none; }
  .set-row:active { transform: none; }
  .status-opt { transition: none; }
  .status-opt:active { transform: none; }
  .status-opt.sel { transform: none; }
}

/* ============================================================
   Waitlist blocker (S1) + access-granted (S2) + Профиль referral
   (S3) — plan 51-05. Full-screen S1/S2 compose the shipped
   .state-box / .count-chip / .cta primitives; only the vertical
   rhythm, the counter/label wrapper, the copy-success state and
   the S2 success-mark sizing are net-new. Tokens only — no raw
   DROPS-gold hex (the referral link text uses --gold per the
   UI-SPEC color map).
   ============================================================ */
.waitlist-counter {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
}
/* Counter reads as a stable number — tabular figures, no reflow. */
.waitlist-counter .count-chip { font-variant-numeric: tabular-nums; }
.waitlist-sub {
  margin: 0;
  max-width: 300px;
  font-size: var(--t-label);
  line-height: 1.4;
  color: var(--fg2);
}
.waitlist-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  width: 100%;
  max-width: 404px;
  margin-top: var(--s-5);
}
/* S2 success mark — green (reuses .profile-check --st-music color) at hero size. */
.waitlist-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
}
/* Copy-success inline state — brief gold glow on the CTA, reverted in JS. */
.waitlist-copied { box-shadow: var(--glow-soft); }
/* Clipboard-absent fallback — the referral URL as selectable gold text. */
.waitlist-manual {
  width: 100%;
  max-width: 404px;
  margin-top: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  text-align: left;
}
.waitlist-manual-label {
  margin: 0 0 var(--s-1);
  font-size: var(--t-label);
  color: var(--fg2);
}
.waitlist-manual-url {
  margin: 0;
  font-size: var(--t-label);
  line-height: 1.4;
  color: var(--gold);
  word-break: break-all;
  -webkit-user-select: text;
  user-select: text;
}

/* S3 — Профиль «ПРИГЛАСИ ДРУЗЕЙ» grow-base referral section (D-04). Caps
   label + grow-base body + the shipped gold CTA. NO invited counter here —
   the advance effect is waiter-only. */
.profile-referral {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding-top: var(--s-4);
  border-top: 1px solid var(--hairline);
}
.profile-referral-label {
  font-size: var(--t-caps);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg3);
}
.profile-referral-body {
  margin: 0;
  font-size: var(--t-body);
  line-height: 1.45;
  color: var(--fg2);
}

/* Motion: the S2 check scales in ONLY when motion is allowed (no
   unconditional animation — mirrors the shipped reduced-motion path). */
@media (prefers-reduced-motion: no-preference) {
  .waitlist-check { animation: waitlist-pop var(--dur-base) var(--ease-spring) both; }
}
@keyframes waitlist-pop {
  from { transform: scale(0.6); opacity: 0; }
  to   { transform: scale(1);   opacity: 1; }
}

/* ============================================================
   Figure upload (plan 52-06) — «+» FAB (S1), video+name form (S3),
   deck-full sheet (S2), blocking XHR progress overlay (S4), and the
   processing / error placeholder cards (S5). DROPS-gold tokens ONLY
   (no raw hex). The «+» FAB is this phase's single new gold
   reservation (UI-SPEC §Color); every error surface is deliberately
   NEUTRAL — --st-count red stays reserved for 🔴 status semantics.
   ============================================================ */

/* S1 — «+» Add-figure FAB (D-12): 56×56 gold, fixed bottom-right above the tab
   bar; shown ONLY on «Изучаю» (toggled from lexicon.js via .hidden). */
.fab-add {
  position: fixed;
  right: var(--s-4);
  bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--s-4));
  z-index: 50;                        /* above panels(0)/tabbar(40), below overlays(60) */
  width: var(--s-8);                  /* 56px — ≥44px iOS HIG */
  height: var(--s-8);
  border: none;
  border-radius: var(--r-pill);
  background: var(--gold-grad);
  color: var(--on-gold);
  box-shadow: var(--glow-soft);
  display: grid;
  place-items: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform var(--dur-fast) var(--ease);
}
.fab-add:active { transform: scale(0.94); }
.fab-add svg { width: 26px; height: 26px; display: block; }
.fab-add.hidden { display: none !important; }

/* S3 — upload form (reuses .detail-overlay/.detail-scroll from the detail chrome). */
.upload-form {
  padding: var(--s-2) var(--s-4) 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
}
.upload-title {
  margin: 0;
  font-family: var(--font-serif);
  font-size: var(--t-title);
  font-weight: 700;
  line-height: 1.15;
  color: var(--fg1);
}
.upload-helper {
  margin: calc(-1 * var(--s-3)) 0 0;
  font-size: var(--t-body);
  line-height: 1.45;
  color: var(--fg2);
}

/* Filepick tile — a <label> wrapping a visually-hidden, still-focusable input. */
.upload-filepick {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 52px;                  /* ≥44px iOS HIG */
  padding: var(--s-3) var(--s-4);
  background: var(--surface);
  border: 1px dashed var(--hairline);
  border-radius: var(--r-sm);
  color: var(--fg1);
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 600;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.upload-filepick:active { background: var(--surface-2); }
/* Visually hidden but focusable + labelled (accessibility invariant). */
.upload-filepick input {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}

/* Chosen-file row — escaped filename + M:SS badge + «Заменить». */
.upload-fileinfo {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
  font-size: var(--t-label);
  color: var(--fg1);
}
.upload-fname {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 58%;
}
.upload-badge {
  font-variant-numeric: tabular-nums;
  color: var(--fg2);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-chip);
  padding: 2px var(--s-2);
}
.upload-replace { color: var(--gold); font-weight: 700; cursor: pointer; margin-left: auto; }
/* Big-file expectation hint — full-width row inside the flex fileinfo box. */
.upload-fileinfo .upload-sizehint {
  flex-basis: 100%;
  margin: var(--s-1) 0 0;
  font-size: var(--t-label);
}

/* Inline validation — NEUTRAL (readable primary fg), not red (color contract). */
.upload-error {
  margin: calc(-1 * var(--s-3)) 0 0;
  font-size: var(--t-body);
  font-weight: 600;
  color: var(--fg1);
}

.upload-field-wrap { display: flex; flex-direction: column; gap: var(--s-2); }
.upload-field-label { font-size: var(--t-body); color: var(--fg2); }
.upload-field {
  width: 100%;
  min-height: 48px;                  /* ≥44px iOS HIG */
  padding: var(--s-3) var(--s-4);
  background: var(--surface);
  border: 1px solid var(--hairline); /* NOT gold — form borders use --hairline */
  border-radius: var(--r-sm);
  color: var(--fg1);
  font-family: var(--font-ui);
  font-size: var(--t-body);
  -webkit-appearance: none;
}
.upload-field::placeholder { color: var(--fg-mute); }
.upload-field:focus { outline: none; border-color: var(--fg3); }

/* Trim-on-upload (plan 260715-tn2) — iPhone-style range picker in the upload
   form: preview video + a dimmed track with a gold selected span and two
   draggable edge handles. Handles MUST carry touch-action:none (project
   lesson: drag elements without it are dead on touch). */
.upload-trim {
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* Long-press on a handle/playhead must never start a blue text selection
     (slicer lesson) — prefixes for iOS WebKit. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
.upload-trim-videowrap {
  position: relative;
  cursor: pointer;
}
.upload-trim-videowrap::after {      /* tap-to-play badge, hidden while playing */
  content: '▶';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 56px;
  height: 56px;
  margin: -28px 0 0 -28px;
  border-radius: 50%;
  background: rgba(15, 20, 32, 0.65);
  display: flex;
  align-items: center;
  justify-content: center;
  padding-left: 5px;                 /* optical centering of the triangle */
  font-size: 22px;
  color: #fff;
  pointer-events: none;
}
.upload-trim-videowrap.is-playing::after { display: none; }
.upload-trim-video {
  display: block;
  width: 100%;
  max-height: 42vh;
  border-radius: 12px;
  background: #000;
}
/* Play button + track live in one row (iMovie-style). */
.upload-trim-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
}
.upload-trim-play {
  flex: 0 0 44px;
  width: 44px;
  height: 44px;
  border: none;
  border-radius: 50%;
  background: var(--surface);
  color: #fff;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.upload-trim-track {
  position: relative;
  flex: 1;
  height: 28px;
  background: var(--surface);
  border-radius: var(--r-pill);
  margin: 0 10px;                    /* side room so edge handles stay grabbable */
  touch-action: none;                /* REQUIRED for pointer-scrub on touch */
  cursor: pointer;
}
.upload-trim-range {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--gold-grad);
  opacity: 0.35;
  border-radius: var(--r-pill);
  pointer-events: none;
}
.upload-trim-playhead {                /* position cursor inside the range */
  position: absolute;
  top: -4px;
  bottom: -4px;
  width: 2px;
  background: #fff;
  border-radius: 1px;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35);
  transform: translateX(-50%);
  pointer-events: none;
}
.upload-trim-handle {
  position: absolute;
  top: 50%;
  width: 20px;
  height: 40px;
  transform: translate(-50%, -50%);
  border: none;
  border-radius: 8px;
  padding: 0;
  background: var(--gold-grad);
  touch-action: none;                /* REQUIRED for pointer-drag on touch */
  cursor: ew-resize;
  transition: transform var(--dur-fast) var(--ease);
}
.upload-trim-handle::after {         /* invisible 44px-class tap target */
  content: '';
  position: absolute;
  inset: -4px -14px;
}
.upload-trim-handle:active { transform: translate(-50%, -50%) scale(1.1); }
.upload-trim-handle.is-active {        /* the edge the nudge row adjusts */
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.85);
}
.upload-trim-readout {
  font-size: 13px;
  color: var(--fg2, #8a93a8);
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.upload-trim-nudges {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 8px;
}
.upload-trim-nudge,
.upload-trim-edgechip {
  height: 34px;
  padding: 0 12px;
  border: none;
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--fg2, #8a93a8);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.upload-trim-edgechip {
  min-width: 76px;
  color: #e9c36a;                      /* gold text: this chip is a mode, not a nudge */
}

/* S4 — blocking progress overlay. The bar fill is the one gold moment here. */
.upload-progress-box {
  min-height: var(--tg-viewport-stable-height, 100vh);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-6);
  padding: 0 var(--s-6);
  text-align: center;
}
.upload-progress {
  width: 100%;
  max-width: 360px;
  height: 10px;
  background: var(--surface);        /* track */
  border-radius: var(--r-pill);
  overflow: hidden;
}
.upload-progress-bar {
  width: 0%;
  height: 100%;
  background: var(--gold-grad);      /* determinate fill */
  border-radius: var(--r-pill);
  transition: width var(--dur-fast) var(--ease);
}
.upload-progress-pct {
  font-variant-numeric: tabular-nums;
  font-size: var(--t-h);
  font-weight: 700;
  color: var(--fg1);
}
.upload-warning {
  margin: 0;
  max-width: 320px;
  font-size: var(--t-body);
  line-height: 1.45;
  color: var(--fg2);
}

/* «Фигура обновлена» tile pip (57-05, PROP-03) — additive gold corner marker on
   every data-grid `.fig`. Mirrors `.fig .marker` (blurred scrim disc, legible over
   any video frame) but gold + top-RIGHT, reusing the `.check` 10px corner anchor
   (that slot is select-mode only → unused in data grids). Tokens only, dark-only,
   NO pulse/animation — a calm gold dot even at post-58 mass-edit density. */
.fig-updated {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 3;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--scrim);
  backdrop-filter: blur(calc(8px * var(--blur-k)));
  -webkit-backdrop-filter: blur(calc(8px * var(--blur-k)));
  border: 1px solid var(--hairline);
  color: var(--gold);
}
.fig-updated i {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 8px currentColor;
}

/* S5 — processing / error placeholder cards (reuse .fig / .gif.placeholder). */
.fig.processing { cursor: default; }
.fig .proc-badge {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  padding: 3px var(--s-2);
  border-radius: var(--r-chip);
  background: var(--scrim);
  border: 1px solid var(--hairline);
  color: var(--fg1);
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 700;
  white-space: nowrap;
}
/* Error card = NEUTRAL surface (no red, no shimmer) — tap → detail recovery. */
.fig.errored .gif {
  animation: none;
  background: var(--surface);
  background-image: none;
}

/* «Освоил» slot-freeing dock (Phase 60 D-09, UI-SPEC S3). Rhythm ONLY — the
   button and its consequence confirm are the shipped flow.css primitives
   (.cta / .cta-green / .cta-secondary / .finish-confirm family), which the
   shell already loads ahead of this file. No colour, size, weight or radius
   is declared here; the green is the shipped --finish completion semantic.
   The host is emitted empty for every figure and filled only for one on the
   active deck, so `:empty` must collapse it entirely. */
.detail-mastered {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  margin-top: var(--s-5);
}
.detail-mastered:empty { display: none; }

/* Release-a-slot + ingest-error recovery actions in the figure detail (S6). */
.detail-actions {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin-top: var(--s-6);
}

/* Reduced-motion: drop the FAB press transform + the placeholder shimmer (keep
   the static 🔴 tint) + the progress-bar transition (determinate width only). */
@media (prefers-reduced-motion: reduce) {
  .fab-add { transition: none; }
  .fab-add:active { transform: none; }
  .fig.processing .gif { animation: none; }
  .upload-progress-bar, .deck-cap-fill { transition: none; }
  .upload-trim-handle { transition: none; }
  .upload-trim-handle:active { transform: translate(-50%, -50%); }
}

/* ============================================================
   S2 «Школа» dashboard (plan 58-03, D-06) — thin teacher home:
   serif school title + two tappable counter chips + a single
   «Создать план» CTA. Token-only (no new hex/weight/radius); the
   chips reuse the --surface card aesthetic of the .set-row family.
   ============================================================ */
.dash-chips {
  display: flex;
  gap: var(--s-3);
  margin-bottom: var(--s-6);
}
.dash-chip {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-1);
  min-height: 44px;                 /* iOS HIG tap target */
  padding: var(--s-3) var(--s-4);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  color: var(--fg1);
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.dash-chip:active { background: var(--surface-2); transform: scale(0.985); }
.dash-chip-num {
  font-family: var(--font-ui);
  font-size: var(--t-h);
  font-weight: 700;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  color: var(--fg1);
}
.dash-chip-label {
  font-size: var(--t-caps);
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--fg3);
}
/* The single dashboard CTA sits inline in the panel flow (not a fixed dock). */
.dash-cta { margin-top: var(--s-4); }
.dash-empty { margin-bottom: var(--s-4); }

/* ============================================================
   S3 «Планы» hub (plan 58-04, TAPI-02) — plan rows (nav + rename +
   inline two-tap delete) and the single-field create/rename bottom
   sheet. Token-only (no new hex/weight/radius); rows reuse the
   .set-row family, the destructive verb reuses the shipped red-ghost
   + .profile-confirm inline confirm. No light-mode branch.
   ============================================================ */
.plan-body { display: block; }

/* A plan = nav row + a tools row + a hidden inline delete confirm. */
.plan-item { display: flex; flex-direction: column; gap: var(--s-2); }

/* «{N} не опубликовано» on the plan row (61.1-06, S3 / DIST-05 · D-07) — a line
   under the shipped .set-row-count, inside .set-row-body. The pip reuses the
   INNER .fig-updated i primitive (57-05): same 9px, same glow, same «unread /
   needs attention» semantics. The inner i, NOT the .fig-updated wrapper — that
   one is absolutely positioned into a card corner and has no business on a list
   row. This is not a new use of the accent, it is the shipped semantics carried
   from the tile to the row. No pulse, no animation. Tokens only, dark-only. */
.plan-unpub {
  display: flex;
  align-items: center;
  gap: var(--s-1);
  margin-top: var(--s-2);
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 600;
  color: var(--fg1);
}
.plan-unpub-dot {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  color: var(--gold);
  background: currentColor;
  box-shadow: 0 0 8px currentColor;
}

.plan-tools { display: flex; gap: var(--s-2); }
.plan-tool {
  flex: 1;
  min-height: 44px;                  /* iOS HIG tap target */
  padding: var(--s-2) var(--s-3);
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  color: var(--fg2);
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 600;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--dur-fast) var(--ease);
}
.plan-tool:active { background: var(--surface); }
/* Destructive trigger — red --st-count, deliberately never gold. */
.plan-tool-danger { color: var(--st-count); }
/* Consequence line inside the two-tap confirm (secondary --fg2 via .state-body). */
.plan-confirm-note { margin: 0 0 var(--s-3); }

/* Create / rename bottom sheet — a --scrim over a --surface card docked to the
   bottom. Focus-trapped + Esc-dismissable + aria-modal (wired in plans.js). */
.plan-sheet-scrim {
  position: fixed;
  inset: 0;
  z-index: 60;                       /* above panels(0)/tabbar(40) — matches overlays */
  background: var(--scrim);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-top: env(safe-area-inset-top);
}
.plan-sheet-scrim[hidden] { display: none !important; }
.plan-sheet {
  width: 100%;
  max-width: 440px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-top-left-radius: var(--r-card);
  border-top-right-radius: var(--r-card);
  padding: var(--s-5) var(--s-4)
           calc(env(safe-area-inset-bottom) + var(--s-6));
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}
.plan-sheet-title {
  margin: 0;
  font-family: var(--font-serif);    /* "expensive-moment" title — mirrors .upload-title */
  font-size: var(--t-title);
  font-weight: 700;
  line-height: 1.15;
  color: var(--fg1);
}
.plan-sheet-field-wrap { display: flex; flex-direction: column; gap: var(--s-2); }
.plan-sheet-label { font-size: var(--t-body); color: var(--fg2); }
.plan-sheet-field {
  width: 100%;
  min-height: 48px;                  /* ≥44px iOS HIG */
  padding: var(--s-3) var(--s-4);
  background: var(--night);          /* inset vs the --surface sheet card */
  border: 1px solid var(--hairline); /* NOT gold — form borders use --hairline */
  border-radius: var(--r-sm);
  color: var(--fg1);
  font-family: var(--font-ui);
  font-size: var(--t-body);
  -webkit-appearance: none;
}
.plan-sheet-field::placeholder { color: var(--fg-mute); }
.plan-sheet-field:focus { outline: none; border-color: var(--fg3); }
/* Inline save error — neutral (--fg1), NOT red (--st-count stays a destructive
   reservation), mirroring the shipped .upload-error convention. */
.plan-sheet-error {
  margin: 0;
  font-size: var(--t-body);
  font-weight: 600;
  color: var(--fg1);
}
.plan-sheet-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-2);
}

@media (prefers-reduced-motion: reduce) {
  .plan-tool { transition: none; }
}

/* ============================================================
   Plan detail (plan 58-05, TAPI-03 / UXS-01) — figure grid + add-figure
   picker (copy-on-add) + light figure-edit overlay. Token-only: the grid
   cards reuse .fig/.gif, the no-preview placeholder reuses
   .fig.processing/.gif.placeholder (uploads.js S5), and the overlays reuse
   the shipped .detail-overlay / .plan-sheet vocabulary. No new hex/weight/radius.
   ============================================================ */

/* The plandetail SUB-VIEW host is NOT in the #panel-lexicon padding list above
   (it is not a tab) — give it the same edge padding + tab-bar clearance so the
   back button / grid never tuck under the notch or the fixed bar. */
#panel-plandetail {
  padding: env(safe-area-inset-top) var(--s-4)
           calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--s-4));
}
.pd-body { display: block; }
.pd-head { align-items: flex-start; }
/* align-items:start — a card carrying a delivery plaque is taller than one
   without, and a stretched .fig would drag its absolutely-positioned .name
   overlay off the gif to the bottom of the equalised row. */
.pd-grid { margin-top: var(--s-2); align-items: start; }

/* ------------------------------------------------------------------
   Delivery-progress plaque (60-08, MOB-02 / D-04 / D-10).
   Informational and derived — NEVER gold, NEVER green: it reports an
   outcome, it is not an action. It sits in a cell UNDER the card
   (.pd-fig-cell) rather than inside it, because .fig pins its name
   overlay to the card's bottom edge. Count typography is the shipped
   .set-row-count primitive (13px/600, --fg2), reused so this block
   declares no font weight of its own. Tokens only — no new hex, no new
   radius, no light-mode branch (dark-only).
   ------------------------------------------------------------------ */
.pd-fig-cell { display: block; min-width: 0; }
.pd-plaque {
  display: block;
  margin-top: var(--s-1);
  padding: var(--s-1) var(--s-2);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-chip);
  line-height: 1.3;
}
/* Numbers read primary; the labels stay secondary (inherited from
   .set-row-count) and «Ждут места» stays neutral with them — waiting for a
   slot is neither an error nor a CTA. The progress tail is tertiary. */
.pd-plaque-v { color: var(--fg1); }
.pd-plaque-tail { color: var(--fg3); }

/* Third plaque shape — draft (61.1-06, S2 / DIST-05). The ONLY delta to
   .pd-plaque is a DASHED border: «provisional, not final yet» is a convention
   that reads without a legend. No new colour (the accent budget is not spent
   here and no «amber status» is invented), no new radius, and deliberately NO
   opacity: dimming means «unavailable/inert», while a draft is fully real — it
   can be opened, edited and deleted — and dimming would also fight the
   .fig.processing shimmer. The text stays --fg1 via the shipped .pd-plaque-v so
   it reads a touch heavier than «Доставлено N» without becoming an accent:
   unpublished is neither an error nor a warning, it is an unfinished decision
   by the teacher. Dark-only, no light-mode branch. */
.pd-plaque-draft { border-style: dashed; }

/* ------------------------------------------------------------------
   Publish strip (61.1-06, S1 / SLC-05 · DIST-05 · D-05/D-07).
   CONDITIONAL — present only while the plan holds drafts and gone the
   moment they are published, which is what keeps the gold button from
   becoming a permanent second gold beside the FAB. IN FLOW, never
   fixed: a fixed bar would fight the FAB and the tabbar. Chrome is the
   .pd-plaque one (--surface + hairline + --r-chip) so the surface gains
   no new material. Button geometry comes from the shipped .cta
   (padding 16px, min-height 44px ≥ the iOS HIG tap floor) — no new
   geometry is declared here. Manrope, NEVER Cormorant: this is a work
   tool, not an expensive moment. Tokens only — no new hex, weight or
   radius; dark-only, no light-mode branch.
   ------------------------------------------------------------------ */
.pd-publish {
  display: block;
  margin: var(--s-4) 0 var(--s-5);
  padding: var(--s-3) var(--s-4);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-chip);
}
.pd-publish-title {
  margin: 0 0 var(--s-2);
  font-family: var(--font-ui);
  font-size: var(--t-h);
  font-weight: 600;
  color: var(--fg1);
}
.pd-publish-note { margin: 0 0 var(--s-3); }
/* The two-tap confirm sits under the trigger, not over it — same inline
   reveal the shipped .pd-remove-confirm uses. */
.pd-publish-confirm { margin-top: var(--s-3); }
.pd-publish-conseq { margin: 0 0 var(--s-3); }

/* ------------------------------------------------------------------
   «Ученики (N)» plan roster (60-08, MOB-02 / D-05). Reuses the shipped
   .set-list / .set-row / .set-row-body primitives; READ-ONLY, so the
   rows carry no trailing control and no destructive red. --s-7 is the
   major vertical break between the figure grid and this section, and
   --s-8 + the safe area is reserved under the last row so the tabbar
   never eats it (the 58 WR-02 regression class). Tokens only.
   ------------------------------------------------------------------ */
.pd-roster {
  display: block;
  margin-top: var(--s-7);
  padding-bottom: calc(var(--s-8) + env(safe-area-inset-bottom));
}
/* Manrope, never serif — .state-heading is the serif display face and is used
   only inside this section's state boxes. */
.pd-roster-title {
  margin: 0 0 var(--s-3);
  font-family: var(--font-ui);
  font-size: var(--t-h);
  font-weight: 600;
  color: var(--fg1);
}
/* .set-row ships a pointer cursor + press transform for a drill-in row; these
   rows go nowhere, so they must not advertise a tap. */
.pd-roster-row { cursor: default; }
.pd-roster-row:active { transform: none; background: var(--surface); }
/* Spinner block while the roster resolves — no white flash, no layout jump. */
.pd-roster-loading { min-height: 44px; }

/* Visually-hidden ARIA live region (screen-reader announcements only). */
.sr-live {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* Add-figure picker — a bottom sheet mirroring the .plan-sheet vocabulary but
   taller (it hosts a scrolling grid of the teacher's OWN figures). */
.pd-pick-scrim {
  position: fixed;
  inset: 0;
  z-index: 60;                       /* above panels(0)/tabbar(40) — matches overlays */
  background: var(--scrim);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-top: env(safe-area-inset-top);
}
.pd-pick-scrim[hidden] { display: none !important; }
.pd-pick-sheet {
  width: 100%;
  max-width: 440px;
  max-height: 85vh;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-top-left-radius: var(--r-card);
  border-top-right-radius: var(--r-card);
  padding: var(--s-5) var(--s-4)
           calc(env(safe-area-inset-bottom) + var(--s-4));
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}
.pd-pick-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}
.pd-pick-title {
  margin: 0;
  font-family: var(--font-serif);    /* "expensive-moment" title — mirrors .plan-sheet-title */
  font-size: var(--t-title);
  font-weight: 700;
  line-height: 1.15;
  color: var(--fg1);
}
.pd-pick-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.pd-pick-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-2);
}

/* Selection ring on a picked figure card — the shipped --ring-gold, on .gif. */
.fig.pd-picked .gif { box-shadow: var(--ring-gold); }

/* Figure-edit overlay body — reuses .detail-overlay/.detail-scroll (0 side
   padding) so re-inset the form; fields reuse the shipped .upload-field. */
.pd-edit {
  padding: 0 var(--s-4);
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}
.pd-textarea {
  min-height: 96px;
  resize: vertical;
  line-height: 1.45;
}
.pd-edit-danger {
  margin-top: var(--s-2);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

/* ============================================================
   «Ученики» roster (plan 58-06 / TAPI-04). Reuses the .set-row /
   .btn-ghost-danger / .profile-confirm family — token-only, no new
   hex/weight/radius. The owner-only soft-remove trigger sits inline
   in the row; its two-tap confirm reveals below.
   ============================================================ */

/* The roster panel is a tab host but was not in the #panel-lexicon edge-padding
   list — give it the same edge padding + tab-bar clearance so the rows / confirm
   never tuck under the notch or the fixed bar (mirrors #panel-plandetail). */
#panel-roster {
  padding: env(safe-area-inset-top) var(--s-4)
           calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--s-4));
}

.roster-list { margin-top: var(--s-2); }
.roster-item { display: block; }

/* The row reuses .set-row's flex space-between; the remove trigger sits at the
   trailing edge (the body's min-width:0 keeps the name ellipsised). */
.roster-remove {
  width: auto;                       /* override .btn-ghost-danger full-width */
  flex: none;
  min-width: 44px;                   /* iOS HIG tap target (min-height already 44) */
  padding: var(--s-2) var(--s-4);
}

/* The two-tap confirm attaches just below its row inside .roster-item. */
.roster-confirm { margin-top: var(--s-2); }
/* Consequence line inside the confirm (secondary --fg2 via .state-body). */
.roster-confirm-note { margin: 0 0 var(--s-3); }

/* ============================================================
   First-run onboarding carousel (plan 59-05, P6 / JOIN-05).
   A 3-card overlay over the «Наборы» plan grid, shown once to an
   invite-newcomer (server show_join_carousel flag; D-10/D-11).
   Token-only, dark-only, DROPS-gold — reuses .cta / .cta-gold /
   .cta-secondary + the shipped --st-* status tokens + the
   .plan-sheet scrim/card vocabulary. No new hex/weight/radius,
   no light-mode branch.
   ============================================================ */
.oc-scrim {
  position: fixed;
  inset: 0;
  z-index: 70;                       /* above panels(0)/tabbar(40)/sheets(60) */
  background: var(--scrim);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: env(safe-area-inset-top) var(--s-4) 0;
}
.oc-scrim[hidden] { display: none !important; }
.oc-card {
  width: 100%;
  max-width: 440px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-top-left-radius: var(--r-card);
  border-top-right-radius: var(--r-card);
  box-shadow: var(--sh-card);
  /* Safe-area: keep the CTA above the home indicator (Phase 58 WR-02). */
  padding: var(--s-6) var(--s-4) max(var(--s-6), env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
  animation: oc-card-in var(--dur-base) var(--ease);
}
@keyframes oc-card-in {
  from { transform: translateY(16px); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}
/* Visually-hidden aria-live region — announces the active card heading. */
.oc-live {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap;
}
.oc-body {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  min-height: 168px;                 /* stabilises height across the 3 cards */
}
.oc-title {
  margin: 0;
  font-family: var(--font-serif);    /* "expensive-moment" heading */
  font-size: var(--t-title);
  font-weight: 700;
  line-height: 1.15;
  color: var(--fg1);
}
.oc-text {
  margin: 0;
  font-size: var(--t-body);
  line-height: 1.45;
  color: var(--fg2);
}
.oc-cap { color: var(--fg1); font-weight: 600; }
/* Status swatches — dual-encoded (dot colour + emoji + word), never colour alone. */
.oc-swatches { display: flex; flex-direction: column; gap: var(--s-2); }
.oc-swatch { display: flex; align-items: center; gap: var(--s-2); }
.oc-dot {
  width: 12px; height: 12px;
  border-radius: var(--r-pill);
  flex: 0 0 auto;
}
.oc-emoji { font-size: var(--t-body); line-height: 1; }
.oc-word { font-size: var(--t-body); color: var(--fg1); }
/* Progress dots — active = gold, inactive = --fg3 (44px tap band). */
.oc-dots {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--s-1);
  min-height: 44px;
}
.oc-dot-nav {
  width: 8px; height: 8px;
  border-radius: var(--r-pill);
  background: var(--fg3);
  transition: transform var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.oc-dot-nav.active { background: var(--gold); transform: scale(1.3); }
.oc-cta { min-height: 44px; }        /* iOS HIG tap target for «Далее»/«Понятно» */
/* Reduced-motion: no card slide-in, no dot transition (instant switch). */
@media (prefers-reduced-motion: reduce) {
  .oc-card { animation: none; }
  .oc-dot-nav { transition: none; }
}

/* ============================================================
   Teacher invite sheet (plan 59-06, P1 / JOIN-01). «Пригласить
   ученика» entry (.cta-secondary — NOT gold; the add-figure FAB owns
   the surface's gold, two-gold guard UI-SPEC) opens a .plan-sheet
   bottom-sheet: the server-built per-plan URL + «Скопировать ссылку»
   (the one gold in the sheet) + owner-only «Сбросить ссылку»
   (.btn-ghost-danger two-tap). Token-only, dark-only, DROPS-gold —
   reuses .plan-sheet / .profile-confirm / .btn-ghost-danger /
   .waitlist-copied + the shipped tokens. No new hex/weight/radius,
   no light-mode branch.
   ============================================================ */

/* Entry buttons on the plan-detail head — compact (override .cta's full width
   so they sit at the head's trailing edge beside the title). Tap target stays
   ≥44px: .cta's own min-height is NOT overridden here. */
.pd-invite-btn,
.pd-upload-btn {
  width: auto;
  max-width: none;
  flex: 0 0 auto;
  padding: var(--s-2) var(--s-4);
  font-size: var(--t-label);
}

/* Trailing action stack (60-07). .screen-head is space-between, so a third
   direct child would strand a button mid-row and squeeze the plan title; the
   two compact entries also do not fit one phone-width row side by side. Wrap
   them in one flex column at the trailing edge. Shipped tokens only — no new
   spacing, color, weight or radius. */
.pd-head-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--s-2);
  flex: 0 0 auto;
}

/* The sheet reuses .plan-sheet chrome; re-assert the safe-area bottom clearance
   (Phase 58 WR-02) so the owner reset control clears the home indicator. */
.pd-invite-sheet {
  padding-bottom: max(var(--s-6), env(safe-area-inset-bottom));
}
.pd-invite-body { margin: 0; }
.pd-invite-linkwrap {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
/* URL box — inset --night on the --surface sheet; break-all so a long token
   never overflows the card; selectable so the manual fallback can be copied. */
.pd-invite-url {
  min-height: 44px;                  /* iOS HIG band + stable height while loading */
  padding: var(--s-3) var(--s-4);
  background: var(--night);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  color: var(--fg1);
  font-size: var(--t-body);
  line-height: 1.45;
  word-break: break-all;
  -webkit-user-select: text;
  user-select: text;
}
.pd-invite-loading { min-height: 0; padding: 0; }
.pd-invite-err { padding: 0; text-align: left; }
/* Owner-only reset — ghost-danger stack above the sheet bottom. */
.pd-invite-reset {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.pd-invite-reset-note { margin: 0 0 var(--s-3); }

/* ============================================================
   In-app notification inbox (plan 62-02, INBX-01). Tokens ONLY —
   every color via flow.css :root var(); no raw hex, no new tokens.
   Gold is reserved for exactly: the bell unread pill, the unread-row
   accent (2px rule + 9px dot), and the error retry CTA (shipped .cta).
   Bell resting icon, type glyphs, chevrons and read rows are NEVER gold
   (62-UI-SPEC §Color). The full-screen feed reuses .detail-overlay /
   .detail-scroll / .detail-close verbatim; only the topbar + rows are new.
   ============================================================ */

/* Fixed-chrome bell — clones the .fab-add tier (z-50: above panels(0)/
   tabbar(40), below the z-60 feed overlay), top-right per D-01, 44×44 tap
   target. Resting icon = --fg1 (NOT gold). Sibling of #app-main → outside
   both tabbars, self-hides on `training` via the .hidden idiom. */
.inbox-bell {
  position: fixed;
  top: calc(env(safe-area-inset-top) + var(--s-2));
  right: var(--s-4);
  z-index: 50;
  width: 44px;                          /* iOS HIG tap-target floor */
  height: 44px;
  border: none;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--fg1);                    /* resting bell — NOT gold */
  display: grid;
  place-items: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform var(--dur-fast) var(--ease);
}
.inbox-bell:active { transform: scale(0.94); }
.inbox-bell svg { width: 24px; height: 24px; display: block; }
.inbox-bell.hidden { display: none !important; }   /* tab-scoped hide idiom */

/* Unread-count pill — top-right of the bell. The one gold moment on the
   bell: --gold-grad fill / --on-gold text / --glow-soft. Tabular caps so
   the width does not jitter as the count changes; hidden at 0. */
.inbox-bell-badge {
  position: absolute;
  top: -2px;
  right: -2px;
  min-width: 18px;
  height: 18px;
  padding: 0 var(--s-1);
  border-radius: var(--r-pill);
  background: var(--gold-grad);
  color: var(--on-gold);
  box-shadow: var(--glow-soft);
  display: grid;
  place-items: center;
  font-size: var(--t-caps);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.inbox-bell-badge[hidden] { display: none !important; }   /* hidden at count 0 */

/* Feed topbar — variant of .detail-topbar with the title pushed left and the
   reused .detail-close pushed right. */
.inbox-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--s-2) var(--s-4);
}
.inbox-topbar-title {
  margin: 0;
  font-size: var(--t-h);
  font-weight: 700;
  line-height: 1.2;
  color: var(--fg1);
}

/* Day-group header — reuses the .section-head vocabulary (13px --fg2 600).
   OPTIONAL (D-04): rows carry their own timestamp regardless. */
.inbox-daygroup {
  margin: var(--s-5) var(--s-4) var(--s-2);
  font-size: var(--t-label);
  font-weight: 600;
  color: var(--fg2);
}

/* Feed list gutter. */
.inbox-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: 0 var(--s-4);
}

/* Notification row card — read state. --surface / --r-card / --sh-card,
   flex row: glyph · text · chev|time. min-height is the 44px control floor. */
.inbox-row {
  position: relative;                   /* anchor for the unread dot */
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-height: 44px;
  padding: var(--s-3);
  border-radius: var(--r-card);
  background: var(--surface);
  box-shadow: var(--sh-card);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform var(--dur-fast) var(--ease);
}
.inbox-row:active {
  background: var(--surface-3);
  transform: scale(0.99);
}

/* Unread — the single accent moment per row: 2px --gold left-rule + a 9px
   gold dot (reuses the .detail-updated / .fig-updated i idiom), raised on
   --surface-2, primary text weight 700. */
.inbox-row--unread {
  background: var(--surface-2);
  border-left: 2px solid var(--gold);
}
.inbox-row--unread .inbox-row-primary { font-weight: 700; color: var(--fg1); }
.inbox-row-dot {
  position: absolute;
  top: var(--s-3);
  right: var(--s-3);
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--gold);
  box-shadow: 0 0 8px var(--gold);      /* glow via the accent token, not raw hex */
}

/* Tombstone (`remove`) — informational, not clickable: muted primary, no
   chevron, default cursor. */
.inbox-row--tombstone {
  cursor: default;
}
.inbox-row--tombstone .inbox-row-primary { color: var(--fg-mute); }
.inbox-row--tombstone .inbox-row-glyph { color: var(--fg-mute); }
.inbox-row--tombstone:active { background: var(--surface); transform: none; }

/* Row type glyph — 20px stroke-SVG, neutral --fg2 (never gold; type is
   differentiated by glyph shape, not by status color — 62-UI-SPEC §Color). */
.inbox-row-glyph {
  flex: none;
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  color: var(--fg2);
}
.inbox-row-glyph svg { width: 20px; height: 20px; display: block; }

/* Text block — primary line (body) + secondary line (label, --fg2). */
.inbox-row-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}
.inbox-row-primary {
  font-size: var(--t-body);
  font-weight: 400;                     /* read; unread → 700 above */
  line-height: 1.45;
  color: var(--fg1);
}
.inbox-row-secondary {
  font-size: var(--t-label);
  font-weight: 600;
  line-height: 1.3;
  color: var(--fg2);
}

/* Trailing affordances — chevron on clickable rows, timestamp caps. */
.inbox-row-chev {
  flex: none;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  color: var(--fg3);
}
.inbox-row-chev svg { width: 18px; height: 18px; display: block; }
.inbox-row-time {
  flex: none;
  font-size: var(--t-caps);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--fg3);
}
