/* 08-parent-profile-room.css — part 08/10 of the ordered bp bundle (the former single bp.css,
   split by surface; the content is UNREORDERED). ORDER IS LOAD-BEARING: cascade ties
   resolve by document order, so hosts must link 01→10 in exactly this sequence —
   CssHygieneTests pins both the list and the order in both hosts. */

/* ── Главная родителя (/home/parent) — wave 6 Task 3, Главная родителя.dc.html ──────────────────── */

.rp-phome-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-bottom: 16px;
}

.rp-phome-card {
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.rp-phome-head {
  display: flex;
  align-items: center;
  gap: 12px;
}

.rp-phome-head-text { flex: 1; min-width: 0; }
.rp-phome-name { font-size: 15.5px; font-weight: 700; }
.rp-phome-tenant { font-size: 12.5px; color: var(--muted-foreground); }

/* Shared initials avatar (card head 40px, week rows 26px via --sm). --warm is the alternating
   per-child tint — children are told apart by avatar tone, not by fabricated subject colours. */
.rp-phome-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-foreground);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  font-weight: 700;
  flex-shrink: 0;
}

.rp-phome-avatar--warm {
  background: var(--warning-muted);
  color: var(--warning-muted-foreground);
}

.rp-phome-avatar--sm {
  width: 26px;
  height: 26px;
  font-size: 10.5px;
}

.rp-phome-next {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 10px 12px;
}

.rp-phome-next-ico {
  width: 16px;
  height: 16px;
  display: inline-flex;
  color: var(--primary);
  flex-shrink: 0;
}

.rp-phome-next-body { flex: 1; min-width: 0; line-height: 1.3; }
.rp-phome-next-title { font-size: 13px; font-weight: 600; }
/* Spec 11.5: «Курс „X" · Поток „Y"» — optional line between the title and the "when" line. */
.rp-phome-next-ctx { font-size: 11.5px; color: var(--muted-foreground); opacity: 0.85; }
.rp-phome-next-when { font-size: 12px; color: var(--muted-foreground); }
.rp-phome-next-none { font-size: 13px; color: var(--muted-foreground); }

.rp-phome-next-link {
  font-size: 12.5px;
  color: var(--primary);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.rp-phome-bal {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 10px 12px;
}

.rp-phome-bal--warn {
  border-color: color-mix(in srgb, var(--warning) 45%, var(--border));
  background: var(--warning-muted);
}

.rp-phome-bal-body { flex: 1; line-height: 1.3; }

.rp-phome-bal-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted-foreground);
}

.rp-phome-bal-num { font-size: 13.5px; font-weight: 700; }

.rp-phome-bal--warn .rp-phome-bal-label,
.rp-phome-bal--warn .rp-phome-bal-num { color: var(--warning-muted-foreground); }

.rp-phome-topup {
  height: 34px;
  padding: 0 14px;
  display: inline-flex;
  align-items: center;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--primary);
  border: 1.5px solid var(--primary);
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  flex-shrink: 0;
}

.rp-phome-topup--primary {
  background: var(--primary);
  color: var(--primary-foreground);
  border: 0;
}

.rp-phome-tasks { display: flex; flex-direction: column; }
.rp-phome-tasks .rp-shome-card-head { margin-bottom: 4px; }

/* «Курсы ребёнка» — read-only per-course progress rows (UX-audit wave 4, item 7). */
.rp-phome-courses { display: flex; flex-direction: column; }
.rp-phome-courses .rp-shome-card-head { margin-bottom: 4px; }

.rp-phome-course-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 0;
  border-top: 1px solid var(--border);
}

.rp-phome-courses .rp-phome-course-row:first-of-type { border-top: 0; }

.rp-phome-course-name { font-size: 13.5px; font-weight: 600; min-width: 0; }

.rp-phome-course-pct {
  font-size: 12.5px;
  color: var(--muted-foreground);
  white-space: nowrap;
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

.rp-phome-week { padding: 20px; }

.rp-phome-week-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 11px 0;
  border-top: 1px solid var(--border);
}

.rp-phome-week-list .rp-phome-week-row:first-child { border-top: 0; }

.rp-phome-week-when {
  width: 84px;
  flex-shrink: 0;
  font-size: 13px;
  color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}

.rp-phome-week-body { flex: 1; min-width: 0; }
.rp-phome-week-title { font-size: 13.5px; font-weight: 600; }
.rp-phome-week-child { font-size: 12.5px; color: var(--muted-foreground); }
.rp-phome-week-none { font-size: 13px; color: var(--muted-foreground); }

.rp-phome-empty { font-size: 14px; color: var(--muted-foreground); margin: 0; }

@media (max-width: 768px) {
  .rp-phome-grid { grid-template-columns: 1fr; margin-bottom: 0; }
  /* Мокап: недельная карточка есть только на десктопе — на мобиле карточки детей несут ту же
     информацию per-child. */
  .rp-phome-week { display: none; }
}

/* ── Задания детей (/children-homework) — wave 6 Task 4, Задания детей.dc.html ─────────────────── */
/* Per-child filter chips + three status sections on the shared AssignmentList (rp-asg*). */

.rp-chw-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 20px;
}

.rp-chw-chip {
  height: 34px;
  padding: 0 14px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--card);
  color: var(--foreground);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

.rp-chw-chip--active {
  background: var(--accent);
  color: var(--accent-foreground);
  border-color: color-mix(in srgb, var(--primary) 45%, var(--border));
}

/* Chip avatar — 20px initials circle; --warm alternating per-child tint (rp-phome-avatar rule). */
.rp-chw-chip-av {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-foreground);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 9.5px;
  font-weight: 700;
  flex-shrink: 0;
}

.rp-chw-chip-av--warm {
  background: var(--warning-muted);
  color: var(--warning-muted-foreground);
}

.rp-chw-sec { margin-bottom: 22px; }

.rp-chw-sec-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

.rp-chw-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted-foreground);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* Section-tone counter badge («Требуют внимания» destructive / «На проверке» info / «Готово» success). */
.rp-chw-count {
  min-width: 19px;
  height: 19px;
  padding: 0 6px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.rp-chw-count--danger  { background: var(--destructive-muted); color: var(--destructive-muted-foreground); }
.rp-chw-count--info    { background: var(--info-muted);        color: var(--info-muted-foreground); }
.rp-chw-count--success { background: var(--success-muted);     color: var(--success-muted-foreground); }

@media (max-width: 640px) {
  .rp-chw-chip {
    height: 32px;
    padding: 0 12px;
    font-size: 12.5px;
  }
  /* LOCKED-12: одна разметка — аватар фильтр-чипа прячется на мобиле (аватар СТРОКИ остаётся). */
  .rp-chw-chip-av { display: none; }
  .rp-chw-sec { margin-bottom: 16px; }
  .rp-chw-label { font-size: 11px; }
  .rp-chw-count {
    min-width: 17px;
    height: 17px;
    padding: 0 5px;
    font-size: 10.5px;
  }
}

/* ── Wave 6 Task 6 — /child/{id}/homework/block/{id}, parent read-only submission view
   (Задания детей.dc.html detail frame). Chrome only: the engine cards below are
   HomeworkSubmissionDetail's own. Back link mirrors .rp-les-back (Занятие page). ── */
.rp-chw-sub { max-width: 720px; margin: 0 auto; }

.rp-chw-sub-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--muted-foreground);
  text-decoration: none;
  margin-bottom: 16px;
}
.rp-chw-sub-back:hover { color: var(--foreground); }

.rp-chw-sub-badge { margin-bottom: 16px; }

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   ParentModeBadge (wave 6 Task 5 — Режим родителя.dc.html, П-14)
   Explicit parent-mode context on shared entity pages: the inline pill next to the /my-lessons
   title, the full-width read-only banner above /lesson/{id} content, and the LOCKED-10 info card.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */

.rp-pbadge-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 26px;
  padding: 0 11px;
  border-radius: 999px;
  background: var(--info-muted);
  color: var(--info-muted-foreground);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}

.rp-pbadge-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 14px;
  background: var(--info-muted);
  color: var(--info-muted-foreground);
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  font-weight: 600;
}

.rp-pbadge-banner-text { min-width: 0; }

/* LOCKED-10 info card: neutral card tone (the banner already carries the info tint). */
.rp-pbadge-info {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card);
  padding: 13px 15px;
  margin-bottom: 12px;
}

.rp-pbadge-info-ico {
  display: inline-flex;
  flex-shrink: 0;
  margin-top: 2px;
  color: var(--muted-foreground);
}

.rp-pbadge-info-text {
  font-size: 12.5px;
  color: var(--muted-foreground);
  line-height: 1.55;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   /profile (wave 6 Task 7 — Профиль.dc.html, П-10 «только личное»)
   A centered 640px column of section cards: «Личные данные» (Rp Mud inputs inside a Bp card —
   the MixedState engine is untouched), «Пароль» (dialog trigger), the manager-only info pointer,
   and the «Аккаунт» card (export row + delete zone). The 152-ФЗ consequences block and the
   code-confirm flow reuse the rp-set-danger-star and rp-btn-danger-star chrome from TenantPanel.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */

.rp-prof {
  max-width: 640px;
  margin: 0 auto;
}

.rp-prof-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card);
  padding: 22px;
  margin-bottom: 16px;
}

.rp-prof-card-title {
  font-size: 14px;
  font-weight: 700;
  margin-bottom: 16px;
}

.rp-prof-avatar { margin-bottom: 4px; }

/* neutral outline action button (mockup: «Сменить пароль», «Запросить архив»).
   Also worn by <label><InputFile/></label> picker buttons (RpImageUpload/RpImageField/
   FileBlockEditor) — a label doesn't center its content the way a button does, hence the
   explicit inline-flex centering; gap spaces the optional leading icon. */
.rp-prof-btn {
  height: 38px;
  justify-content: center;
  gap: 8px;
  padding: 0 14px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--foreground);
  font: inherit;
  font-size: 13px;
  white-space: nowrap;
  flex-shrink: 0;
}
.rp-prof-btn:disabled { opacity: 0.6; cursor: not-allowed; }

.rp-prof-text {
  font-size: 12.5px;
  color: var(--muted-foreground);
  line-height: 1.55;
  margin: 0 0 14px;
}

/* info-muted plate: the «Ищете настройки площадки…» pointer between the cards and (--flow) the
   owner closure-first plate inside the delete zone. */
.rp-prof-hint {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--info-muted);
  color: var(--info-muted-foreground);
  padding: 13px 16px;
  margin-bottom: 16px;
}
.rp-prof-hint--flow { margin-bottom: 0; }
.rp-prof-hint-ico { display: inline-flex; flex-shrink: 0; margin-top: 1px; }
.rp-prof-hint-text { font-size: 13px; line-height: 1.55; }
.rp-prof-hint-text a { color: var(--info-muted-foreground); font-weight: 600; }

/* «Аккаунт» card action rows */
.rp-prof-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 14px;
}
.rp-prof-row-main { flex: 1; min-width: 0; }
.rp-prof-row-title { font-size: 13.5px; font-weight: 600; }
.rp-prof-row-title--danger { color: var(--destructive); }
.rp-prof-row-sub {
  font-size: 12.5px;
  color: var(--muted-foreground);
  line-height: 1.5;
}

/* delete zone (the second «Аккаунт» row grown into the inline UC-22 flow) */
.rp-prof-del > .rp-prof-row-title--danger { margin-bottom: 6px; }
.rp-prof-del-lead { margin-bottom: 12px; }
.rp-prof-warn-title2 { margin-top: 8px; }

.rp-prof-check {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  font-size: 13px;
  line-height: 1.5;
  margin: 12px 0;
  cursor: pointer;
}
.rp-prof-check input {
  width: 16px;
  height: 16px;
  margin: 1px 0 0;
  accent-color: var(--destructive);
  cursor: pointer;
  flex-shrink: 0;
}

@media (max-width: 600px) {
  .rp-prof-card { padding: 16px; }

  /* Mobile mockup centers the avatar block; RpImageUpload's own layout is Mud utility flex —
     restack its root row into a centered column without touching the shared component. */
  .rp-prof-avatar > div {
    flex-direction: column;
    align-items: center;
    text-align: center;
  }
  .rp-prof-avatar > div > div { align-items: center; }

  .rp-prof-row { flex-direction: column; align-items: stretch; }
  .rp-prof-btn { width: 100%; height: 44px; }
  .rp-prof-del .rp-btn-danger-outline { width: 100%; height: 44px; }
}

/* ---------- RoomChatPanel (wave 7 Task 2) ---------- */
/* Compact «Чат занятия» panel for /room/{LessonId} — see the header inventory comment for the full
   rationale. RoomPage (T4) owns show/hide + desktop-column vs mobile-sheet placement around this
   markup; the panel itself is a plain flex column that fills whatever box it's given. */
.rp-rchat {
  display: flex;
  flex-direction: column;
  min-height: 0;
  height: 100%;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.rp-rchat-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  font-weight: 600;
  flex-shrink: 0;
}
.rp-rchat-head-title { flex: 1; }

.rp-rchat-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.rp-rchat-empty {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted-foreground);
  font-size: 13px;
  text-align: center;
  padding: 20px;
}
/* Loading state: a spinner over a --card ground takes the muted tone — same override
   .rp-deckpick-loading .rp-spin applies below for the identical shape. */
.rp-rchat-empty .rp-spin {
  border-color: color-mix(in srgb, var(--muted-foreground) 30%, transparent);
  border-top-color: var(--muted-foreground);
}

.rp-rchat-msg { display: flex; flex-direction: column; align-items: flex-start; }
.rp-rchat-msg--mine { align-items: flex-end; }
.rp-rchat-msg-meta { font-size: 11.5px; color: var(--muted-foreground); margin-bottom: 2px; }

.rp-rchat-bubble {
  display: inline-block;
  max-width: 90%;
  padding: 7px 11px;
  border-radius: 12px;
  background: var(--secondary);
  color: var(--secondary-foreground);
  font-size: 13px;
  line-height: 1.45;
  overflow-wrap: anywhere;
  word-break: break-word;
}
/* Own bubble — accent-filled, mirrors rp-msg-bubble--mine's own-message convention. */
.rp-rchat-bubble--mine { background: var(--primary); color: var(--primary-foreground); }
/* Tombstone for a removed message — muted/dashed, no fill (rp-msg-bubble--removed's compact cousin). */
.rp-rchat-bubble--removed { background: transparent; border: 1px dashed var(--border); color: var(--muted-foreground); font-style: italic; }

.rp-rchat-composer {
  display: flex;
  gap: 8px;
  padding: 10px;
  border-top: 1px solid var(--border);
  flex-shrink: 0;
}
.rp-rchat-input {
  flex: 1;
  height: 36px;
  padding: 0 12px;
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  background: var(--card);
  color: var(--foreground);
  font: inherit;
  font-size: 13px;
}
.rp-rchat-send {
  width: 36px;
  height: 36px;
  border: 0;
  border-radius: var(--radius-md);
  background: var(--primary);
  color: var(--primary-foreground);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
}
.rp-rchat-send:disabled { opacity: 0.5; cursor: default; }

/* ---------- RoomToolRail (wave 7 Task 3) ---------- */
/* Board tool palette extracted out of WhiteboardSurface into its own always-visible rail — see the
   header inventory comment for the full rationale. Default orientation is a vertical 52px column
   (desktop / phone-landscape); RoomPage (T4) applies rp-rtool--row for the mobile/tablet horizontal
   layout. Every button calls repetitumBoard.* directly — this component owns no canvas. */
.rp-rtool {
  width: 52px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 0;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
/* Horizontal arm (mobile/tablet). `overflow-x: auto` is a structural guarantee, not a nicety: 2 tools +
   6 swatches + 2 actions + separators measured 392px against 349px of available width on a 375px phone,
   and because the parent column resolves overflow-x to `auto`, that 43px spilled into a page-wide
   horizontal scroll. The rail now scrolls inside itself; the sizes below (32px buttons, 22px swatches)
   make that scroll unnecessary at 375px, and it stays correct for whatever the palette grows into. */
.rp-rtool--row {
  width: auto;
  max-width: 100%;
  flex-direction: row;
  padding: 5px 8px;
  gap: 4px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  justify-content: center;
}
.rp-rtool--row::-webkit-scrollbar { display: none; }
.rp-rtool--row .rp-rtool-btn { width: 32px; height: 32px; }
.rp-rtool--row .rp-rtool-color { width: 22px; height: 22px; }

.rp-rtool-btn {
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--muted-foreground);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
/* Active pen/eraser tool — accent fill, mirrors the stageTabs/boardTools active treatment in the
   mockup's demo script. */
.rp-rtool-btn--active { background: var(--accent); color: var(--accent-foreground); }
.rp-rtool-btn:disabled { opacity: 0.4; cursor: default; }

.rp-rtool-sep { width: 26px; height: 1px; background: var(--border); margin: 4px 0; flex-shrink: 0; }
.rp-rtool--row .rp-rtool-sep { width: 1px; height: 22px; margin: 0 6px; }

.rp-rtool-color {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 2px solid transparent;
  padding: 0;
  cursor: pointer;
}
/* The ring is a UI-chrome token; the swatch fill itself is the inline hex stroke color (LOCKED-1). */
.rp-rtool-color--active { border-color: var(--primary); }
.rp-rtool-color:disabled { opacity: 0.4; cursor: default; }

/* ---------- RoomPage chrome ---------- */
/* Full rationale in the header inventory comment. */

/* rp-room-slide(-title/-image/-body) — SlideStage's own markup. Needs a `container-type:size` ancestor
   for the cqmin units — `.rp-room-square` below provides it directly. */
/* Слайд — тот же светлый лист, что и доска (--wb-paper/--wb-ink, не --card/--foreground): он кроет
   квадрат целиком (inset:0), а поверх слайда рисуют тем же захардкоженным маркером, что и по доске
   (per-slide stroke context, whiteboard.js) — на тёмном слайде чёрный маркер был не виден так же,
   как на тёмной доске. Слейт-рампу под этот лист допиннивает app.css. */
.rp-room-slide { position: absolute; inset: 0; overflow: auto; padding: 4% 5%;
    background: var(--wb-paper); color: var(--wb-ink); }
/* Font sizes are PURE container-query units (cqmin) — no px clamp. The clamp's px floor/ceiling made the
   same slide look bigger (floor on small screens) or smaller (ceiling on large screens) relative to the
   square, so a deck rendered differently per device. Pure cqmin makes the slide a uniform zoom of the
   square: identical proportions on every screen. Markdown children inside the body are em-relative, so
   they scale with the body size automatically. */
.rp-room-slide-title { margin: 0 0 0.6em; font-size: 5cqmin; font-weight: 600; }
/* An AI image is the slide's focal element — large, centered, contained (never cropped), capped so the
   title + a short body still fit in the square. cqmin keeps it proportional like the text. */
.rp-room-slide-image { display: block; max-width: 100%; max-height: 60cqmin; margin: 0 auto 0.6em;
    object-fit: contain; border-radius: 8px; }
.rp-room-slide-body { font-size: 3.2cqmin; line-height: 1.4; }

/* Full-viewport flex-column shell for /room/{lessonId} (ReaderLayout, no sidebar/appbar). This base rule
   must live in bp.css, not app.css: app.css loads AFTER bp.css, so a base `.rp-room{display:flex}` there
   would outrank the phone-landscape `.rp-room{display:grid}` below by source order (equal 0,1,0
   specificity) and the landscape grid would never apply. In-file, the later landscape block wins the
   tie — the intended cascade. */
.rp-room { display: flex; flex-direction: column; height: 100dvh; background: var(--background);
    position: relative; overflow: hidden; }

/* Top bar: title/subtitle, timer pill (rp-chip--success, reused), scene controls, participant count. */
.rp-room-top {
  display: flex;
  align-items: center;
  gap: 14px;
  height: 52px;
  padding: 0 16px;
  background: var(--card);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
/* The title is the ONLY element in the bar allowed to give up width (every chip is flex-shrink:0 via the
   primitive) — so a long lesson note ellipsises instead of squeezing the status chips into wrapping. */
.rp-room-top-title { line-height: 1.2; min-width: 0; flex: 0 1 auto; }
.rp-room-top-name {
  font-size: 13.5px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rp-room-top-sub { font-size: 11.5px; color: var(--muted-foreground); white-space: nowrap; }
.rp-room-top-spacer { flex: 1 1 0; min-width: 0; }
.rp-room-top-users {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  color: var(--muted-foreground);
  flex-shrink: 0;
}

/* Countdown pill — tabular figures + a floor on the label box so the chip keeps ONE width for the whole
   lesson. Paired with RemainingLabel's zero-padded mm:ss: proportional digits alone still shifted the
   pill (and every sibling after it) on each tick, which read as the header "twitching". */
.rp-room-timer {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  min-width: 74px;
  justify-content: center;
}

/* Connection chip — a button so a touch device can reach the detail line it can't hover for. */
.rp-room-net {
  border: 0;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  padding: 5px 12px;
  border-radius: 999px;
}

/* Scene controls (board/slides segment + the «‹ Слайд N / M ›» control, or the student's read-only
   stage badge) — ONE RenderFragment, rendered in two static CSS-gated positions (desktop:
   rp-room-top-scene; mobile: rp-room-scenerow, its own row right under the square) so the page never
   needs a duplicate-instance guard the way RoomChatPanel does (this fragment has no autonomous side
   effects) — the picker popup underneath is duplicated the same safe way. */
.rp-room-scenegroup { display: flex; align-items: center; gap: 8px; min-width: 0; }
.rp-room-scenetab-lbl { white-space: nowrap; }

/* ---------- Stage-owned slide nav (rp-room-stagenav) ---------- */
/* Deck paging lives on the stage, pinned to the bottom edge of the slide it pages — it used to be a 30px
   «‹ 3/12 ⌄ ›» cluster in the top bar, ~400px away from the content on a desktop and jammed between the
   stage tabs and the connection chip on a phone. Rendered once, for both roles (the student gets the
   counter without the arrows, LOCKED-2).

   The band is `pointer-events:none` so it never steals a stroke from the whiteboard canvas underneath;
   only the controls themselves take input. Glass surface (card at 92% + blur) because the stage behind
   it is arbitrary tutor content — a flat token fill disappeared over a light slide. */
.rp-room-stagenav {
  position: absolute;
  left: 50%;
  bottom: 10px;
  transform: translateX(-50%);
  z-index: 12;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--card) 92%, transparent);
  backdrop-filter: blur(8px);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-md);
  pointer-events: none;
  max-width: calc(100% - 24px);
}
.rp-room-stagenav > * { pointer-events: auto; }
.rp-room-stagenav-btn {
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--foreground);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.rp-room-stagenav-btn:hover:not(:disabled) { background: var(--secondary); }
.rp-room-stagenav-btn:disabled { opacity: 0.35; cursor: default; }
.rp-room-stagenav-count {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 38px;
  padding: 0 12px;
  flex-shrink: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--foreground);
  font: inherit;
  cursor: pointer;
}
.rp-room-stagenav-count:hover { background: var(--secondary); }
.rp-room-stagenav-count--static { cursor: default; padding: 0 14px; }
.rp-room-stagenav-count--static:hover { background: transparent; }
/* Tabular figures + a width floor: a 12-slide deck must not resize its own control on «9 / 12» →
   «10 / 12», which on the stage would visibly nudge the arrows apart mid-presentation. */
.rp-room-slide-counter {
  font-size: 13.5px;
  font-weight: 600;
  color: inherit;
  min-width: 52px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  white-space: nowrap;
}

/* Picker — ONE instance, a panel over the stage anchored above the nav that opens it (the square is the
   containing block: `.rp-room .rp-room-square` is position:relative). This replaced three CSS-gated
   copies of the same markup (top-bar popover / portrait bottom sheet / landscape side panel), each of
   which needed its own containing-block trick and its own show-hide specificity war. Honest-data: only
   "Слайды · {M}" (Slide carries no deck title) — "Сменить колоду…" always renders since the picker only
   ever mounts for the tutor. */
.rp-room-picker {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 58px;
  z-index: 13;
  /* border-box so the 14px padding + 1px border are inside the width/max-height budgeted against the
     stage — under content-box a "320px" panel laid out at 350px and its max-height overshot the square's
     top edge, so it was silently clipped by the stage's overflow:hidden on a 375px phone. */
  box-sizing: border-box;
  width: min(320px, calc(100% - 24px));
  max-height: calc(100% - 76px);
  display: flex;
  flex-direction: column;
  padding: 14px;
  background: var(--popover);
  color: var(--popover-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.rp-room-picker-head { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-shrink: 0; }
.rp-room-picker-title {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rp-room-picker-changedeck {
  height: 28px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--secondary);
  color: var(--secondary-foreground);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
/* Always visible now that the picker is an in-stage panel on every breakpoint: the stage must never dim
   (LOCKED), so there is no dimming backdrop to tap through and the ✕ is the explicit way out. (The
   transparent rp-room-pickerbackdrop still closes it on an outside click.) */
.rp-room-picker-close {
  display: inline-flex;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--secondary);
  color: var(--secondary-foreground);
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
/* align-content:start keeps row height column-driven: the panel has a definite (tall) max-height, and a
   stretched row would re-derive cell WIDTH from the stretched height via aspect-ratio:1/1 and overflow. */
.rp-room-picker-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  align-content: start;
  overflow-y: auto;
  min-height: 0;
}
.rp-room-picker-num {
  aspect-ratio: 1 / 1;
  border: 2px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--muted);
  color: var(--muted-foreground);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.rp-room-picker-num--active { border-color: var(--primary); color: var(--primary); }

/* Transparent click-catcher closing the picker on an outside click. Scoped to the stage (absolute inside
   `.rp-room-square`, not fixed over the room) on EVERY breakpoint now — the picker is an in-stage panel,
   so "outside" means "elsewhere on the stage", and the bottom bar / tiles / chat stay live behind it. */
.rp-room-pickerbackdrop { position: absolute; inset: 0; z-index: 11; }

/* SlideDeckPickerDialog body (T5) — the dialog chrome itself (head/title/close, empty state) reuses the
   generic rp-covw-dlg/-empty shell shared across the cabinet; only the tree list below is new. */
.rp-deckpick-tree { display: flex; flex-direction: column; gap: 2px; max-height: 360px; overflow-y: auto; }
.rp-deckpick-course { font-size: 12.5px; font-weight: 700; margin: 10px 0 2px; }
.rp-deckpick-course:first-child { margin-top: 0; }
.rp-deckpick-module { font-size: 11.5px; color: var(--muted-foreground); margin: 6px 0 2px; }
.rp-deckpick-lesson {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 10px;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--foreground);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.rp-deckpick-lesson:hover { background: var(--secondary); }
.rp-deckpick-loading { display: flex; justify-content: center; padding: 24px 0; }
.rp-deckpick-loading .rp-spin {
  border-color: color-mix(in srgb, var(--muted-foreground) 30%, transparent);
  border-top-color: var(--muted-foreground);
}

.rp-room-scenerow { display: none; }

/* Content row: 52px rail / flexible square / 260–360px side column on desktop (CSS Grid auto-placement —
   the mobile-only rp-room-scenerow is display:none there so it never consumes a track); a single ordered
   flex column on mobile. */
.rp-room-main {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 52px 1fr minmax(260px, 340px);
  gap: 12px;
  padding: 12px;
}

/* The `.rp-room .rp-room-square` compound selector is no longer load-bearing for specificity (it once
   outranked a since-removed single-class app.css rule) — kept as-is to avoid churn. `container-type:size`
   lives here so the rp-room-slide-* cqmin rules above resolve with no separate container element.

   Height cap (wave-7 final review, Important-2) — mirrors the phone-landscape grid's `min(100%,
   calc(100vw - 248px))` cap above for the SAME reason: `height:100%` and `width:auto` +
   `aspect-ratio:1/1` + `max-width:100%` are independent constraints, so on a portrait/narrow-tall
   desktop viewport (this grid applies at ANY width ≥960px, portrait or landscape — the orientation-
   gated blocks below only cover <960px) the row-height-driven `height:100%` can exceed the 1fr
   column's available width; `max-width:100%` then clips the WIDTH but nothing shrinks the height, so
   the box stops being square (e.g. a 1024×1366 iPad Pro portrait rendered ~660×1226 before this fix).
   The cap derives the column's actual available width from `.rp-room-main`'s own grid (`grid-template-
   columns: 52px 1fr minmax(260px, 340px)`, `gap: 12px`, `padding: 12px`): CSS Grid's "maximize tracks"
   step grows the minmax range track to its max BEFORE the 1fr track gets anything, and every viewport
   this rule applies to (≥960px) has far more than 340px + a near-zero square minimum to spare, so the
   side column is effectively always 340px (its ceiling) here — unlike the landscape grid above, where
   the flexible track is the SIDE column and 200px there is a floor, not an always-hit ceiling. So the
   square's width is `100vw − 52 (rail) − 12×2 (gaps) − 12×2 (.rp-room-main padding) − 340 (side column
   max) = 100vw − 440`: 52+24+24+340 = 440. Capping height to that same budget keeps the square exactly
   as wide as it is tall with no clipping. Verified: 1440×900 ordinary desktop → natural height (100dvh
   − 52px top bar − 64px bottom bar − 24px .rp-room-main padding = 760px) is already ≤ 1440−440=1000px,
   so `min()` picks the 100% branch and the cap never engages (unchanged from before this fix); 1024×1366
   iPad Pro portrait → natural height 1366−140=1226px > 1024−440=584px, so the cap engages and the stage
   renders a true 584×584 square with the side column intact at its full 340px. `align-self: center`
   (new here, matching the landscape stage's own centering) keeps the now possibly-shorter-than-row
   square vertically centered in `.rp-room-main`'s row instead of pinned to its top edge — the row's
   own height is unchanged, so a capped square would otherwise leave dead space below it. */
.rp-room .rp-room-square {
  position: relative;
  overflow: hidden;
  container-type: size;
  /* border-box, so the 1px border is INSIDE every width/height/max-width the rules below compute. There
     is no global reset in this stylesheet: under the default content-box the narrow rule's
     `height: min(100vw - 24px, …)` produced a 351px content box + 2px border = 353px of layout width
     against 351px of available space, and `max-width: 100%` (also a content-box limit) could not clamp
     it — a 2px page-wide horizontal scroll that survived every other overflow fix. */
  box-sizing: border-box;
  /* Бумага доски — светлая в ОБЕИХ темах (--wb-paper, не --card): холст .wb-surface внутри
     квадрата прозрачен, так что видимый фон штрихов даёт именно этот фон, а палитра маркеров
     захардкожена под светлое (tokens.css). В тёмной теме --card давал чёрный маркер на почти
     чёрном фоне. Рамка остаётся токеном темы — она часть обвязки комнаты, а не листа. */
  background: var(--wb-paper);
  color: var(--wb-ink);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  flex-shrink: 0;
  height: min(100%, calc(100vw - 440px));
  width: auto;
  aspect-ratio: 1 / 1;
  max-width: 100%;
  margin-inline: auto;
  align-self: center;
}
/* When a slide is shown the whiteboard surface must be transparent so the slide shows through — WhiteboardSurface
   mounts unconditionally alongside SlideStage (both are position/z-index:auto "positioned" descendants painted in
   tree order, so it paints AFTER and would otherwise fully occlude the slide beneath it). */
.rp-room-square .wb-surface { background: transparent; }
/* Transient notices — reconnecting / waiting / floor-granted / raised hand — stack in one centered
   column at the top of the stage. A column, not per-notice `top` offsets: two notices can be live at
   once (a raised hand during a reconnect), and the old fixed `top:12px`/`top:56px` pair placed them by
   hand, so any third notice needed another magic number and any two that shared an offset overlapped.
   The column never takes pointer input; its children do. */
.rp-room-overlays {
  position: absolute;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  max-width: calc(100% - 24px);
  pointer-events: none;
}
.rp-room-overlays > * { pointer-events: auto; max-width: 100%; }

/* Every padded overlay that caps itself against the stage must measure border-box. There is no global
   reset in this stylesheet, so a `max-width` is a CONTENT cap by default and the element's own padding
   and border spill past it: the raised-hand banner laid out at 384px inside the 364px column it was
   supposed to fit, on a 390px stage. Harmless for a short name, a clipped control for a long one — and
   the same trap that produced a 2px page-wide horizontal scroll on the stage itself and a clipped slide
   picker, so it is collected here rather than patched a fourth time. */
.rp-room-overlays,
.rp-room-overlays > *,
.rp-room-stagenav,
.rp-room-tile-name { box-sizing: border-box; }
/* The stage is arbitrary tutor content — and the slide sheet is white in BOTH themes (--wb-paper), so a
   chip sitting on it must announce itself as chrome rather than as a line of the slide. A toneless chip
   used to be fully transparent here, which is how «Ученики ещё не подключились» ended up reading as part
   of the slide copy; the fill alone doesn't finish the job either, since every chip surface (--secondary
   and the --*-muted tones alike) is a pale tint that measures ~1.1:1 against white paper.
   The ring does the separating: `currentColor` is the tone's own foreground, so one rule gives every
   variant a tone-coloured outline — neutral grey, amber for a warning, green for success — without
   restating a border colour per modifier or overriding the fill (which would cost the tone signal). At
   50% it measures 4.3:1 against white paper in the light theme (16:1 in dark) for every tone, clearing
   the 3:1 WCAG minimum for a UI-component boundary. */
.rp-room-overlay-chip {
  border: 2px solid color-mix(in srgb, currentColor 50%, transparent);
  box-shadow: var(--shadow-md);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

.rp-room-side {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-height: 0;
  min-width: 0;
}

/* Tiles: 2-column grid on desktop, horizontal-scroll strip on mobile — same markup, CSS-only reflow. */
.rp-room-tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.rp-room-tile {
  position: relative;
  aspect-ratio: 16 / 10;
  background: var(--muted);
  border: 2px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Active speaker — primary ring, mirrors the mockup's active-speaker treatment. */
.rp-room-tile--speaking { border-color: var(--primary); }
.rp-room-tile video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Self-view mirror: flip only the local participant's camera (selfie-style), not remote tiles. */
.rp-room-tile--self video { transform: scaleX(-1); }
.rp-room-tile-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--secondary);
  color: var(--secondary-foreground);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 600;
}
.rp-room-tile-name {
  position: absolute;
  left: 6px;
  bottom: 5px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--foreground) 78%, transparent);
  color: var(--background);
  font-size: 10.5px;
  font-weight: 600;
  max-width: calc(100% - 12px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rp-room-tile-micoff {
  position: absolute;
  right: 6px;
  bottom: 5px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--foreground) 78%, transparent);
  color: var(--background);
  display: flex;
  align-items: center;
  justify-content: center;
}
.rp-room-tile-hand {
  position: absolute;
  right: 6px;
  top: 5px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--warning);
  color: var(--warning-foreground);
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Tutor-only hover actions on a REMOTE tile (LOCKED-3): "Дать маркер"/"Забрать" + mute. Visible on
   hover/focus — this is a desktop mouse interaction per the mockup; a touch equivalent is future polish. */
.rp-room-tile-actions {
  position: absolute;
  right: 5px;
  top: 5px;
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity 0.15s ease;
}
.rp-room-tile:hover .rp-room-tile-actions,
.rp-room-tile:focus-within .rp-room-tile-actions { opacity: 1; }
.rp-room-tile-act {
  height: 24px;
  padding: 0 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--foreground);
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.rp-room-tile-act--primary { border: 0; background: var(--primary); color: var(--primary-foreground); }
.rp-room-tile-mute {
  width: 24px;
  height: 24px;
  border: 0;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--foreground) 78%, transparent);
  color: var(--background);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

/* The single raised-hand banner (LOCKED-3) — only the first raiser is named, "и ещё N" covers the rest.
   Tutor-only, and a stage notice rather than a row in the side column: on a phone the column below the
   stage is fully spoken for, so the banner landed under the fold — the tutor had to scroll a live lesson
   to find out someone had a question. As a stage notice it is unmissable and costs the column nothing.
   Rounded like the chips it stacks with, and shadowed for the same reason (it sits over tutor content). */
.rp-room-hand {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding: 6px 6px 6px 12px;
  background: var(--warning-muted);
  border: 1px solid color-mix(in srgb, var(--warning) 30%, transparent);
  border-radius: 999px;
  box-shadow: var(--shadow-md);
  color: var(--warning-muted-foreground);
}
.rp-room-hand-text {
  flex: 0 1 auto;
  min-width: 0;
  font-size: 13px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* flex-shrink:0 + nowrap: the text beside it flexes, so a long «Рука поднята: Александра и ещё 2» used
   to squeeze this button until «Дать слово» broke onto a second line inside a 30px pill. */
.rp-room-hand-btn { flex: 0 0 auto; height: 30px; padding: 0 14px; font-size: 12.5px; white-space: nowrap; }

/* The ONE RoomChatPanel host (LOCKED: never two instances) — desktop: a normal column; narrow portrait:
   a full-screen panel that slides up on the --open modifier (RoomPage's `_chatOpen`); phone landscape:
   an absolute inset:0 panel over the side column (see the landscape media query near the end of this
   file). The panel is always mounted; only its CSS position/visibility differs.
   The `@media` blocks below are the TRUE COMPLEMENT of the narrow-portrait condition further down —
   `(orientation: landscape), (min-width: 960px)` — so the two never both apply and never leave a gap.
   `min-width: 960px` (not 961) pairs with the portrait block's `max-width: 959.98px`: the .02px split at
   the Sm|Md boundary, so a viewport of exactly 960px is `_isNarrow == false` desktop and lands here.
   Narrow LANDSCAPE also matches the first arm, but the landscape block near the end of the file
   re-overrides these later in the cascade — intended. These blocks hide `.rp-room-chat-close-wrap`
   because a static desktop column has nothing to close; wherever the panel is an overlay the ✕ is the
   only dismissal, since nothing here ever dims the stage (LOCKED). */
.rp-room-chathost {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
@media (orientation: landscape), (min-width: 960px) {
  .rp-room-chathost { flex: 1; }
}
.rp-room-chat-close-wrap { display: inline-flex; }
.rp-room-chat-close {
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--secondary);
  color: var(--secondary-foreground);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
@media (orientation: landscape), (min-width: 960px) {
  .rp-room-chat-close-wrap { display: none; }
}
/* Bottom controls bar — labeled pills on desktop, circular icon-only + a mobile-only chat FAB ≤768px. */
.rp-room-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  height: 64px;
  padding: 0 16px;
  background: var(--card);
  border-top: 1px solid var(--border);
}
.rp-room-bar-spacer { flex: 1; }
/* nowrap + flex-shrink:0 across the whole bar: these are fixed-content controls, and a label breaking
   onto a second line inside a 40px pill is never the right answer — the bar wraps or scrolls instead. */
.rp-room-bar-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 14px;
  flex-shrink: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card);
  color: var(--foreground);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
/* Mic/cam off tone — labels stay "Микрофон"/"Камера" always (LOCKED-9); only the icon + this tone change. */
.rp-room-bar-btn--off {
  border-color: color-mix(in srgb, var(--destructive) 40%, transparent);
  background: var(--destructive-muted);
  color: var(--destructive-muted-foreground);
}
.rp-room-bar-hand--active {
  border-color: color-mix(in srgb, var(--warning) 45%, transparent);
  background: var(--warning-muted);
  color: var(--warning-muted-foreground);
}
.rp-room-bar-chat {
  display: none;
  position: relative;
  flex-shrink: 0;
  width: 46px;
  height: 46px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--card);
  color: var(--foreground);
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.rp-room-bar-chat-dot {
  position: absolute;
  top: 0;
  right: 0;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--destructive);
  border: 2px solid var(--card);
}
.rp-room-bar-leave {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 16px;
  flex-shrink: 0;
  border: 0;
  border-radius: var(--radius-md);
  background: var(--destructive);
  color: var(--destructive-foreground);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

/* State cards: unsupported-browser / join-error full-page fallbacks. */
.rp-room-state {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100dvh;
  padding: 24px;
}
.rp-room-state-card {
  width: 100%;
  max-width: 440px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
  padding: 44px 32px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--background);
  box-shadow: var(--shadow-md);
}
.rp-room-state-title { font-size: 16px; font-weight: 700; }
.rp-room-state-body { font-size: 13.5px; color: var(--muted-foreground); line-height: 1.6; }
.rp-room-state-actions { display: flex; gap: 10px; margin-top: 16px; flex-wrap: wrap; justify-content: center; }

/* ---------- Mobile/tablet portrait (orientation: portrait, <960px — wave 7 Task 6) ---------- */
/* Widened from the shell's flat `max-width:768px` (Видеокомната.dc.html "Планшет · вертикально", 834px)
   to `(orientation:portrait) and (max-width:959.98px)` — the SAME cutoff `_isNarrow`'s MudBlazor
   Sm breakpoint uses (RoomPage.razor), so the CSS layout and the C#-driven RoomChatPanel/RoomToolRail
   visibility contracts never disagree (959.98 not 960: MudBlazor's Md bracket starts AT 960, so a
   viewport of exactly 960px is `_isNarrow == false` desktop and must get the desktop CSS too — fix
   round 2, Minor-4; the .02px split is the Bootstrap/MudBlazor convention at breakpoint boundaries).
   Before this widening there was a real 769–959px gap: `_isNarrow` was already "narrow" there (Sm
   bracket, <960) so the chat sheet/mobile chat-FAB rendered, but this block's CSS didn't apply yet
   (still desktop-shaped `.rp-room-main` grid) — a tablet in portrait fell through the crack. Gating on
   `orientation:portrait` (not just width) also means a genuinely landscape-oriented narrow window
   (e.g. a resized short-but-wide desktop browser) no longer gets this stacked-square treatment — see
   the narrow-landscape block near the end of this file instead. Everything inside stays width-driven
   (vw-based), so it needs no change to keep working at tablet widths — only the trigger condition
   widened. */
@media (orientation: portrait) and (max-width: 959.98px) {
  .rp-room-top { height: 44px; padding: 0 12px; gap: 8px; }
  .rp-room-top-scene { display: none; }
  /* The subtitle repeats the lesson's time window, which the countdown chip already conveys; on a phone
     it was the second line that pushed the title block to 44px inside a 48px bar. */
  .rp-room-top-sub { display: none; }
  .rp-room-top-users { font-size: 12px; }

  .rp-room-scenerow {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    order: 2;
    flex: 0 0 auto;
    min-width: 0;
  }
  .rp-room-scenerow .rp-seg { width: auto; }
  .rp-room-scenerow .rp-seg-tab { flex: 0 0 auto; padding: 7px 14px; white-space: nowrap; }

  /* `overflow-x: hidden` is explicit and load-bearing: `overflow-y:auto` alone leaves overflow-x
     computed as `auto` (a `visible` on one axis becomes `auto` when the other isn't), so ANY too-wide
     child — the tool-rail row, the tile strip — turned the whole content column into a horizontally
     scrollable page. Wide children now scroll inside themselves instead. */
  .rp-room-main {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 8px 12px 10px;
    overflow-y: auto;
    overflow-x: hidden;
  }
  .rp-room-main > * { min-width: 0; }

  /* The stage keeps its 1:1 aspect — whiteboard strokes are normalized per axis (whiteboard.js `norm`),
     so a non-square stage on one peer would warp the drawing everyone else sees. What changed is that
     the square is no longer unconditionally `width:100%`: it is sized by the SMALLER of the available
     width and the height left over once every other row has been paid for. At 375×667 a full-width
     square is 351px — 53% of the viewport — and the rows below it (scene switch, tool rail, tiles) were
     left to fight over ~120px, which is how the video tiles ended up flattened to 4px tall.

     The 320px budget = 44 top bar + 68 control bar + 37 scene row + 44 tool rail + 82 tile strip + 24
     gaps + 18 column padding, rounded up. It is measured, not guessed: at 375×667 it leaves the stage at
     347px with every row intact. The raised-hand banner is deliberately NOT in it — that notice moved
     onto the stage precisely so a conditional row can't blow the budget. If something else does (the
     media-permission banner), `.rp-room-main` scrolls: the tiles keep their height instead of being
     crushed, because everything below the stage is flex:0 0 auto. */
  .rp-room .rp-room-square {
    order: 1;
    flex: 0 0 auto;
    width: auto;
    height: min(calc(100vw - 24px), calc(100dvh - 320px));
    min-height: 190px;
    aspect-ratio: 1 / 1;
    max-width: 100%;
    margin-inline: auto;
  }

  .rp-room-main > .rp-rtool { order: 3; flex: 0 0 auto; }

  .rp-room-side { order: 4; flex: 0 0 auto; gap: 8px; }

  /* Fixed pixel size, not aspect-ratio + flex-basis: the tile's height used to be derived from the
     strip's line height, so once the column ran out of room the whole strip collapsed. */
  .rp-room-tiles {
    display: flex;
    flex-direction: row;
    gap: 6px;
    overflow-x: auto;
    overflow-y: hidden;
    flex: 0 0 auto;
    scrollbar-width: none;
  }
  .rp-room-tiles::-webkit-scrollbar { display: none; }
  .rp-room-tiles .rp-room-tile { flex: 0 0 124px; width: 124px; height: 78px; aspect-ratio: auto; }
  .rp-room-tile-act { height: 22px; padding: 0 6px; font-size: 10.5px; }

  .rp-room-hand { padding: 8px 10px; gap: 8px; }
  .rp-room-hand-text {
    font-size: 12.5px;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Chat is a FULL-SCREEN panel here, not a bottom sheet clinging to the stage's lower edge. The sheet
     had to derive its top from the square's rendered height (the old `--room-sheet-top: 100vw + 40px`
     magic), which stopped being true the moment the square gained a height cap — and on a 667px phone
     it left the chat ~250px tall anyway. A panel you deliberately opened, with its own ✕, may cover the
     stage; that is the ordinary mobile-chat shape and it needs no baseline arithmetic. */
  .rp-room-chathost {
    position: absolute;
    inset: 0;
    height: auto;
    z-index: 30;
    box-shadow: var(--shadow-lg);
    background: var(--card);
    transform: translateY(100%);
    /* visibility, not just pointer-events: `pointer-events: none` stops the mouse but leaves the closed
       panel in the TAB ORDER, and the panel is now a full-viewport box parked one screen below the room
       (`.rp-room`'s scrollHeight is exactly 2×100dvh). Tabbing into its message input would scroll the
       room a full viewport down to reveal the focused field — and since `.rp-room` is overflow:hidden,
       the lesson would just go blank with no scrollbar to explain it. `visibility: hidden` takes it out
       of the tab order, so nothing can focus into it and nothing can trigger that scroll. (The off-screen
       box still counts toward scrollable overflow; that is harmless once it is unreachable.)
       Zero-duration visibility with a DELAY, not a transitioned one: the panel must be visible for the
       whole slide-in (delay 0 when opening) and stay painted for the whole slide-out (delay = the
       transform's duration when closing). Interpolating visibility instead leaves what it reads
       mid-animation up to the engine. */
    visibility: hidden;
    transition: transform 0.2s ease, visibility 0s linear 0.2s;
    pointer-events: none;
  }
  .rp-room-chathost--open {
    transform: translateY(0);
    visibility: visible;
    transition: transform 0.2s ease, visibility 0s linear 0s;
    pointer-events: auto;
  }

  .rp-room-bar { justify-content: center; gap: 10px; padding: 9px 14px 12px; height: auto; }
  .rp-room-bar-spacer { display: none; }
  .rp-room-bar-btn {
    width: 46px;
    height: 46px;
    padding: 0;
    border-radius: 50%;
    justify-content: center;
  }
  .rp-room-bar-lbl { display: none; }
  .rp-room-bar-chat { display: inline-flex; }
  .rp-room-bar-leave {
    width: 56px;
    height: 46px;
    padding: 0;
    justify-content: center;
    border-radius: 999px;
  }
}

/* The connection chip's label is 14–20 characters («Отличная связь · TCP») — on a phone top bar sharing
   375px with a title, a countdown and a participant count there is no width for it. The dot carries the
   tone, `title`/`aria-label` carry the detail, and tapping the chip toasts the same line (RoomPage) since
   a touch device can't hover. Width-only (not orientation-gated): a 390px landscape phone has the same
   problem. */
@media (max-width: 600px) {
  .rp-room-net-lbl { display: none; }
  .rp-room-net { padding: 8px; }
  .rp-room-timer { min-width: 0; padding: 5px 10px; }
}

/* ---------- Phone/narrow landscape (wave 7 Task 6, widened by the T6 re-review) ---------- */
/* Видеокомната.dc.html "Телефон · горизонтально" (844×390): rail (48px, vertical, scrolls y) | square
   stage (height-bound, LOCKED) | right column — header row (title/timer/scene tabs/slide-nav, compact) /
   2×2 16:9 tile grid / hand banner / round controls.

   Review fix (Defect 2): gated on `max-width:959.98px` — the SAME width bracket `_isNarrow` uses (the
   .02px split at the Sm|Md boundary, see the portrait block's comment / fix round 2 Minor-4) — not the
   original `max-height:500px`. The original height gate left a real complement hole: `orientation:
   landscape AND width<960 AND min-height:501px` (e.g. a foldable split-view ~840×673) matched NEITHER this block
   NOR the portrait block above (portrait requires `orientation:portrait`), so it fell through to the
   desktop 3-column grid while `_isNarrow` (width-only, RoomPage.razor) was already "narrow" — the SAME
   disagreement class the file already documents for RoomToolRail below, just for a wider slice of the
   viewport space. Two concrete symptoms this closed: (a) `RoomToolRail` rendered `Horizontal=true` (rail
   row) squeezed into the OLD desktop grid's fixed 52px rail column → overflow; (b) `.rp-room-chathost`
   stayed a permanently-visible desktop column (`ChatVisible` reads `_isNarrow ? _chatOpen : true`, but the
   CSS never hid it) while the chat FAB toggle button was ALSO hidden (mobile-only CSS) — chat was neither
   properly shown nor reachable. Widening the trigger to width-only makes `_isNarrow` and this grid's
   applicability agree on every (orientation × width × height) combination: portrait ≤959.98 → portrait
   block; landscape ≤959.98 → this block (any height); either orientation ≥960 → desktop grid, matching
   `_isNarrow == false` (959.98/960 split mirrors MudBlazor's own Sm|Md boundary — see fix round 2,
   Minor-4). Geometry note for the tall-narrow end of the new range: the stage stays a perfect 1:1 square
   (LOCKED) but its height is capped at `min(100%, calc(100vw - 248px))` (see the stage rule below, fix
   round 2 Important-2), which guarantees the right/"side" column ≥ ~200px on ANY viewport in this range:
   844×390 → stage 390 (min picks 100%, the phone case is untouched), side 406px; 840×673 foldable →
   stage 592 (was an uncapped 673 leaving only 119px), side 200px; ≈955×880 snap-left half-screen →
   stage 707 (was an uncapped 880 leaving a clipped ~27px), side 200px. Squareness survives the cap —
   width follows from aspect-ratio, only the height input shrinks.

   `.rp-room-main` is dissolved via `display:contents` (blueprint-pitfalls П-25 — the same "reflow without
   a DOM change" trick reader.css uses) so its children — the rail, `.rp-room-square`, the always-hidden
   `.rp-room-scenerow`, and `.rp-room-side` — become direct grid items of `.rp-room`, ALONGSIDE its
   already-sibling `.rp-room-top`/`.rp-room-bar`. A 3×3 `grid-template-areas` then places rail+stage
   spanning the full height (column 1/2) and top/side/bar stacked in column 3 — visually identical to
   nesting the header/tiles/controls inside one "right column" wrapper, with zero DOM change.
   `RoomPageRenderTests.Room_main_and_top_bar_nesting_matches_what_the_landscape_grid_relies_on` is the
   structural guard П-25 requires.

   RoomToolRail's `Horizontal` param is still bound to `_isNarrow` (RoomPage.razor, C#, width-only) — the
   ONE place that signal and true orientation disagree, since a phone-landscape width (844px) sits inside
   MudBlazor's Sm ("narrow") bracket, so `Horizontal` renders the row modifier here even though the rail
   must stay a vertical column. Reconciled entirely in CSS, no RoomPage logic change (per the task brief):
   `.rp-room-main > .rp-rtool` (2-class specificity) outranks the single-class `.rp-rtool--row` rule and
   wins regardless of which shape `Horizontal` picked.

   Chat is a genuine `.rp-room-side` descendant already, so it gets the mockup's true `inset:0`/
   `border-left` panel. The slide picker needs nothing here at all any more: it lives inside
   `.rp-room-square`, which is a positioned grid item in this layout exactly as it is a flex/grid child
   everywhere else, so the one base rule already resolves against the stage. That retired a second
   rendered instance (`PickerPanel(overlay: true)`), its `--overlay` modifier, and the compound-selector
   specificity war between the three breakpoint copies. */
@media (orientation: landscape) and (max-width: 959.98px) {
  .rp-room {
    display: grid;
    grid-template-columns: 48px auto 1fr;
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
      "rail stage top"
      "rail stage side"
      "rail stage bar";
  }
  .rp-room-main { display: contents; }

  .rp-room-main > .rp-rtool {
    grid-area: rail;
    width: 48px;
    flex-direction: column;
    padding: 8px 0;
    border: 0;
    border-right: 1px solid var(--border);
    border-radius: 0;
    overflow-y: auto;
    min-height: 0;
  }
  .rp-room-main > .rp-rtool .rp-rtool-sep { width: 26px; height: 1px; margin: 4px 0; }

  /* Stage cap (fix round 2, Important-2): without it, a near-square landscape window ≤960 wide (e.g. a
     snap-left browser half on 1920×1080 → viewport ≈955×880) let the height-bound 1:1 stage eat
     width−48−height ≈ 27px of side column — every control clipped under `.rp-room`'s overflow:hidden.
     `min(100%, calc(100vw - 248px))` keeps the phone case untouched (844×390: 100vw−248 = 596 > the
     100%-of-area 390 → min picks 390, exactly as before) while guaranteeing the side column never drops
     below ~200px: 248 = 48px rail + 200px minimum side column (the grid has no gaps, so the side column
     gets exactly 100vw − 48 − stage-width). Squareness is preserved — width still follows from the
     unchanged aspect-ratio:1/1, only the height input shrinks (840×673 → stage 592, side 200; 955×880 →
     stage 707, side 200). Compound selector: the base `.rp-room .rp-room-square` sets height:100% at
     0,2,0, which a flat `.rp-room-square` here couldn't override. align-self centers the now
     possibly-shorter-than-viewport square vertically in its 3-row area instead of pinning it to the top. */
  .rp-room .rp-room-square {
    grid-area: stage;
    min-width: 0;
    height: min(100%, calc(100vw - 248px));
    align-self: center;
  }

  .rp-room-top {
    grid-area: top;
    min-width: 0;
    height: auto;
    padding: 8px 12px;
    gap: 8px;
    /* The header column is narrow (width − rail − stage); title + timer + scene tabs don't fit on one
       line. Wrapping keeps every control reachable — without it the nowrap flex row's max-content width
       inflates the 1fr track and the whole grid overflows .rp-room (smoke find). The two spacers must
       NOT hold a line open here, so they collapse. */
    flex-wrap: wrap;
  }
  /* The spacers must not survive here: a flex-grow item on a WRAPPED line eats that line's remainder and
     pushes the next control onto a line of its own — with both spacers alive the header column grew to
     three lines (title / segment / chips) and 125px of a 375px-tall screen. */
  .rp-room-top .rp-room-top-spacer { display: none; }
  /* The lesson title is the first thing to go in this layout, on the same reasoning that already drops
     the participant count below: the header column is only `100vw − rail − stage` wide (244px on a
     667×375 handset), every line it wraps to is a line taken from the video tiles under it, and the
     title is the one item you don't need mid-lesson — you know which lesson you are in, and portrait,
     desktop and every screen you arrive from all show it. Dropping it buys a whole line back (125px →
     87px at 667×375, 92px → 54px at 844×390) and lets the stage tabs keep their LABELS, which for this
     audience is the trade worth making.
     Taken out of FLOW, not out of the document (bb-compat.css's .sr-only geometry): `display:none` would
     also take it out of the accessibility tree, so rotating the phone would stop a screen reader from
     ever reaching the lesson name. Absolute positioning removes it from the flex line just as
     completely. */
  .rp-room-top-title {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  /* Compact status chips: the connection chip keeps its tone dot and drops the 20-character label, same
     as on a phone in portrait (the 600px width query doesn't reach an 844px landscape handset), and the
     countdown gives up the width floor it holds elsewhere — tabular figures alone keep it from twitching
     once it is the only thing on its line. */
  .rp-room-net-lbl { display: none; }
  .rp-room-net { padding: 8px; }
  .rp-room-timer { min-width: 0; padding: 5px 10px; }
  /* Icon-only stage tabs. With labels the segment alone is 204px of a 244px column, so tabs + countdown
     + connection dot could not share a line no matter what else was dropped — the header stayed two
     lines and the second one came out of the video tiles. Icons take the segment to ~84px and the whole
     header to one line. The buttons carry aria-label/title (RoomPage.razor) precisely because this rule
     removes their only accessible name; the label markup stays in the DOM for every other breakpoint. */
  .rp-room-top-scene .rp-room-scenetab-lbl { display: none; }
  .rp-room-top-scene .rp-seg-tab { padding: 8px 12px; }
  /* Wrapping stays as the safety net for whatever a future control adds to this column, even though the
     icon-only segment above now fits on one line at the narrowest size in range (667×375). */
  .rp-room-top-scene { flex-wrap: wrap; min-width: 0; }
  .rp-room-top-scene .rp-seg { min-width: 0; max-width: 100%; }
  .rp-room-top-scene .rp-seg-tab { min-width: 0; }
  .rp-room-top-users { display: none; } /* dropped for space — not shown in the mockup's compact header */

  .rp-room-side {
    grid-area: side;
    position: relative;
    min-width: 0;
    overflow-y: auto;
  }
  .rp-room-tiles { gap: 6px; }
  .rp-room-tile { aspect-ratio: 16 / 9; } /* LOCKED: 16:9 here, unlike desktop/portrait's 16:10 */

  .rp-room-bar {
    grid-area: bar;
    min-width: 0;
    height: auto;
    justify-content: center;
    gap: 8px;
    padding: 6px 12px 10px;
  }
  .rp-room-bar-spacer { display: none; }
  .rp-room-bar-btn { width: 40px; height: 40px; padding: 0; border-radius: 50%; justify-content: center; }
  .rp-room-bar-lbl { display: none; }
  .rp-room-bar-chat { display: inline-flex; width: 40px; height: 40px; }
  .rp-room-bar-leave { width: 48px; height: 40px; padding: 0; justify-content: center; }

  /* Chat: true inset:0 panel over the (now-relative) side column, LOCKED. Base .rp-room-chathost stays
     mounted unconditionally (RoomChatPanel is never destroyed, per its own contract) but is display:none
     until the --open modifier lands, since there's no off-screen direction to slide it to here (unlike
     the portrait sheet's translateY). */
  .rp-room-chathost {
    display: none;
    position: absolute;
    inset: 0;
    border: 0;
    border-left: 1px solid var(--border);
    border-radius: 0;
    box-shadow: none;
    z-index: 21;
  }
  .rp-room-chathost.rp-room-chathost--open { display: flex; }
  .rp-room-chat-close-wrap { display: inline-flex; } /* re-shown — the landscape-arm complement above hides it */

  /* A phone-landscape stage is height-bound and short (390px on an 844×390 handset): pull the on-stage
     slide nav tighter so it doesn't eat the slide's bottom line. */
  .rp-room-stagenav { bottom: 6px; padding: 3px; gap: 2px; }
  .rp-room-stagenav-btn { width: 32px; height: 32px; }
  .rp-room-stagenav-count { height: 32px; padding: 0 10px; }
  .rp-room-picker { bottom: 46px; }
}

