/* ── LMS design system — тёмная минималистичная тема ───────────── */

/* Inter (variable, кириллица) — самохостинг, без внешних CDN */
@font-face {
  font-family: 'Inter';
  src: url('../fonts/InterVariable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('../fonts/InterVariable-Italic.woff2') format('woff2');
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}

/* ── Палитра ───────────────────────────────────────────────────────
   ВСЕ цвета интерфейса живут в этих переменных. Администратор может переопределить
   любую из них (Настройки → Система → Цвета): значения приходят с сервера и ставятся
   инлайном на <html> (см. frontend/js/theme.js). Поэтому:
     • новый компонент красим ТОЛЬКО через var(--…) — иначе он не подхватит палитру;
     • полупрозрачные оттенки акцента/красного — через color-mix(... var(--accent) N%, transparent),
       а не rgba() с числами (иначе останутся зелёными при смене акцента);
     • --on-accent / --accent-hover / --on-accent-dim / --tb-on-accent — ПРОИЗВОДНЫЕ:
       theme.js пересчитывает их от акцента (контрастный текст на кнопке, ховер).
       Значения ниже — дефолт на случай, если JS ещё не отработал. */
:root {
  color-scheme: dark;        /* нативные контролы (дата, селект, автозаполнение) — тёмные */
  --bg: #0a0a0a;
  --surface: #111;
  --surface-2: #161616;
  --border: #222;
  --border-2: #2c2c2c;
  --text: #e8e8e8;
  --text-dim: #a0a0a0;
  --muted: #757575;          /* было #555 — не проходило контраст WCAG AA на тёмном фоне */
  --accent: #6ee7b7;
  --accent-dim: #2f6f59;
  --danger: #f87171;
  --warn: #fbbf24;
  /* Индикация оценки/балла (низкий→средний→высокий). НЕ часть настраиваемой палитры школы:
     это сигнальные цвета «плохо/средне/хорошо», одинаковые при любой цветовой схеме, поэтому
     их нет в theme.js/theme.py и редактор цветов их не трогает. */
  --grade-good: #34d399;
  --slot-done: #74ba6d;      /* таблетка прошедшего слота в расписании админа (макет) */
  --online-dot: #23fd02;     /* точка «в сети» — яркий сигнал с макета, вне палитры школы */
  --grade-mid: #fbbf24;
  --grade-low: #f87171;
  /* Отмеченный рабочий час / свободное окно преподавателя — ОДИН оттенок везде, где этот
     час отмечают и где его смотрят (редактор часов, сетка расписания, образец в пояснении
     ученику). ⚠️ Не 20% от --grade-good: полупрозрачный изумруд поверх серого фона уходил
     в серо-оливковый («милитари» — жалоба владельца продукта). Сочность даёт насыщенная
     основа, а не бóльшая доля: ярче ~40% клетка начинает спорить с чипом «Проведён». */
  --work-fill: color-mix(in srgb, #00dc82 24%, transparent);
  --work-fill-hover: color-mix(in srgb, #00dc82 30%, transparent);
  /* Окна сторон в сетке лобби класса (решение владельца продукта): преподавателя —
     оранжевые, ученика — серые, совпавшие — две половины встык по диагонали клетки (серая сверху слева,
     оранжевая снизу справа; без градиента — ровный срез). `to bottom right`, а не угол в
     градусах: срез идёт ровно из угла в угол при любой пропорции клетки. Тем же серым
     отмечается час в квизе «Когда вам удобно заниматься?»: это те же окна ученика.
     Сигнал, а не оформление: как --work-fill, вне палитры школы. В светлой теме — свои
     пастельные значения (см. :root[data-theme="light"]), сочные выбивались на белой сетке. */
  --win-teacher: #df7701;
  --win-student: #787878;
  --win-both: linear-gradient(to bottom right, var(--win-student) 50%, var(--win-teacher) 50%);
  /* Шильдики совпадений на карточке витрины («Общие окна», «Общие интересы»; макет владельца
     продукта). Пара из перелива язычка «открыть»: фуксия и тёплый тон, но темнее палитры
     liquid.js — белый текст мелкого шильдика обязан держать контраст (фуксия 6.0:1; тёплый
     край 4.1:1 лежит в поле пилюли, а под первой буквой градиент уже ≥4.6:1; у яркого
     #e8703f было бы ≈3:1).
     Подпись белая в обеих темах, поэтому и цвета одни и те же: на белой подложке шильдик
     читается так же, как на тёмной. Вне палитры школы — это фирменная пара, а не акцент. */
  --match-pink: #b32c67;
  --match-warm: #a8724b;
  /* Шапка класса у админа (макет владельца продукта, цвета сняты пипеткой): точка
     присутствия на аватаре — на платформе / нет, и пилюля «Заменить учителя». Чистые
     сигнальные цвета, одни на обе темы: точка лежит в своём кольце цвета поверхности,
     а белая подпись на красном читается и на светлой подложке. */
  --presence-on: #05fd03;
  /* Текст поверх яркой заливки (выбранный инициатор отмены, кнопка «Отменить урок»):
     заливки в обеих темах одинаковые, значит и текст один. */
  --on-presence: #062b06;
  --on-danger: #fff;
  --presence-off: #ff0000;
  --teacher-replace: #ff0000;
  /* Шкала этапов кандидата в «Отборе персонала» (макет владельца, пипеткой): этап пройден /
     не пройден / идёт сейчас / впереди — он же цвет линии между точками. Пункты анкеты под
     шкалой — те же цвета: заполнено и отправлено / не заполнено / заполнено в черновике. */
  --stage-done: #01fc1a;
  --stage-fail: #dc0102;
  --stage-now: #0085ff;
  --stage-todo: #d9d9d9;
  /* Производные от акцента (пересчитываются в theme.js) */
  --on-accent: #04150f;      /* текст/иконка на акцентной кнопке */
  --accent-hover: #8af3c9;   /* акцентная кнопка под курсором */
  --on-accent-dim: #eafff6;  /* текст на приглушённом акценте (свои сообщения в чате) */
  --tb-on-accent: #04150f;   /* текст на акценте в шапке (бейдж непрочитанных) */

  /* ── Дочерние токены (гибкая палитра) ────────────────────────────
     Настраиваемые дети (строки «Тонкой настройки» в редакторе) по умолчанию НАСЛЕДУЮТ
     цвет родителя (var(--parent)), поэтому «из коробки» повторяют мастер-акцент/красный —
     вид не меняется. Админ может задать любому свой цвет (Настройки → Система → Цвета):
     theme.js впишет его инлайном на <html> и перекроет это правило. Зеркало наследования —
     INHERITS в frontend/js/theme.js. НОВЫЙ дочерний токен: добавь сюда, в DETAIL_GROUPS/
     INHERITS (theme.js) и в TOKENS (backend/app/theme.py). */
  --accent-text: var(--accent);       /* акцентный текст, ссылки, числа, баланс */
  --accent-border: var(--accent-dim); /* рамки при наведении, кольцо фокуса */
  --btn-primary: var(--accent);       /* заливка основных кнопок */
  --badge-accent: var(--accent);      /* акцентные бейджи и чипы */
  --chat-mine: var(--accent-dim);     /* фон своих сообщений в чате */
  --correct: var(--accent);           /* правильный ответ */
  /* Шапка недельной сетки (Пн, Вт, Ср…) — отдельная группа «Расписание» в редакторе.
     Дата и сегодняшний день — НЕ токены, а производные: их пересчитывает theme.js (derive),
     здесь только значения до первого запуска скрипта (= прежний вид). */
  --sched-head-bg: var(--surface-2);  /* фон полосы дней */
  --sched-head-text: var(--text-dim); /* название дня */
  --sched-head-date: var(--muted);    /* дата под днём */
  --sched-head-today: var(--text);    /* сегодняшний день (и подкраска его ячейки) */
  --sched-frame: var(--surface);      /* рамка сетки (токен; «как карточка» = рамки нет) */
  --sched-frame-line: transparent;    /* чем рамка РИСУЕТСЯ — производная, см. derive() */
  --sched-grid: var(--surface-2);     /* линии между днями и часами */
  /* ⚠️ Включённый тумблер — ВСЕГДА зелёный, независимо от палитры школы (как индикация
     оценки и рабочие часы): «вкл» — сигнал состояния. Не в theme.js/theme.py. */
  --toggle-on: var(--grade-good);     /* включённый тумблер (фон дорожки) */
  /* Подпись включённого канала уведомлений в «Мой профиль» («E-mail уведомления
     подключены»). Цвет — с макета владельца продукта; тоже сигнал, вне палитры школы.
     На тёмной кнопке окна — 5.2:1 (WCAG AA); в светлой теме свой, темнее. */
  --notify-on: #249f2d;
  /* Искра в заголовке окна генерации урока — золото с макета владельца продукта
     (пипеткой #f1dd82). Значок, а не текст: в светлой теме свой, темнее (4.7:1). */
  --ai-sparkle: #f1dd82;
  /* Статусы домашней работы (views/homework.js) — заливки с макета владельца продукта,
     сняты пипеткой. ⚠️ Одни на обе темы, как чипы расписания: это сигналы состояния, а не
     оформление, в палитру школы не входят (theme.js/theme.py их не знают). */
  --hw-draft: #a9a9a9;  --hw-draft-fg: #2b2b2b;   /* Черновик */
  --hw-sent: #00a7ff;   --hw-sent-fg: #ffffff;    /* Отправлено / На выполнении */
  --hw-check: #ff47e3;  --hw-check-fg: #ffffff;   /* На проверке */
  --hw-back: #ff9500;   --hw-back-fg: #ffffff;    /* На доработке (на макете нет — оранжевый «вернули») */
  --hw-ok: #00e131;     --hw-ok-fg: #0b2e10;      /* Принято */
  --hw-accept: #41e94f; --hw-accept-fg: #000000;  /* кнопка «Принять работу» */
  /* ── ВНУТРЕННИЕ АЛИАСЫ (НЕ токены, в редакторе не настраиваются) ──
     Бывшие самостоятельные токены — дубли рубильников (--selected ≈ акцентный текст,
     --wrong/--btn-danger ≈ --danger, --progress ≈ бейджи, --tb-muted ≈ --tb-text-dim).
     Слиты с родителями; все 60+ использований var(--…) в стилях работают как раньше.
     Из палитры убраны: theme.js (THEME_GROUPS/INHERITS), theme.py (TOKENS), старые правки
     школ снимает RETIRED_VARS (theme.js). Новый слитый токен — по той же схеме. */
  --selected: var(--accent-text);     /* активные вкладки, выбранные пункты */
  --btn-danger: var(--danger);        /* кнопки удаления/опасных действий */
  --progress: var(--badge-accent);    /* полосы прогресса, загрузка */
  --wrong: var(--danger);             /* неверный ответ */
  /* ── Чипы расписания ─────────────────────────────────────────────
     СПЛОШНАЯ заливка статуса + БЕЛЫЙ текст, без рамок: статус урока читается цветом с одного
     взгляда (решение владельца продукта).
     ⚠️ Заливки ОДНИ на обе темы (в отличие от --grade-*): это сигналы, а не оформление, и
     светлая пастель под белым текстом нечитаема в любой теме. Каждый цвет подобран ЗАМЕРОМ —
     самый светлый своего оттенка, на котором белый текст ещё проходит WCAG AA (4.5:1).
     ⚠️ Три цвета из четырёх ФИКСИРОВАНЫ и не входят в палитру школы — как индикация оценки
     (--grade-*) и рабочие часы: «состоялся», «предстоит» и «пробный» — это сигналы, а не
     оформление. Их нет в theme.js/theme.py, редактор цветов их не трогает.
     Настраивается только «неявка» (--noshow) — школьная правка проходит через --lesson-noshow. */
  --lesson-done: #168665;             /* проведённый урок — ВСЕГДА зелёный */
  --diamond-ring: var(--accent);      /* заполнение кольца «Мои алмазы»; в светлой — свой зелёный */
  --lesson-upcoming: #297db1;         /* предстоящий урок — ВСЕГДА голубой */
  --lesson-demo: #da2b83;             /* пробный урок — ВСЕГДА розовый */
  --lesson-nobal: #9a56d5;            /* «нет баланса» */
  --noshow: var(--danger);            /* неявка — единственный настраиваемый чип */
  /* ⚠️ Заливка «неявки» — приглушённый --noshow, а не он сам: красный по умолчанию (#f87171)
     светлый, и белый текст на нём давал бы контраст 2.8:1. Через color-mix школьная правка
     цвета продолжает работать, но не может сделать подпись нечитаемой. */
  --lesson-noshow: color-mix(in srgb, var(--noshow) 76%, #000);
  /* ⚠️ Яркие варианты ТЕХ ЖЕ статусов — ДЛЯ ТЕКСТА, не для заливки. Токены выше подобраны
     как ФОН под белый текст (самые светлые, где белый ещё проходит AA), и ровно поэтому
     как текст на тёмной поверхности они выглядят тусклыми — «синий „Запланирован“ слишком
     тусклый» (жалоба владельца продукта). В светлой теме переопределяются обратно на
     базовые: там «ярче» означало бы нечитаемо на белом. */
  --lesson-upcoming-fg: #5aa9e6;
  --lesson-done-fg: #2fc79a;
  --lesson-nobal-fg: #b98ae8;
  /* Производные для дочерних заливок (theme.js пересчитывает, если задан свой цвет) */
  --on-btn-primary: var(--on-accent);
  --btn-primary-hover: var(--accent-hover);
  --on-chat-mine: var(--on-accent-dim);
  /* Шапка (топбар) — своя группа: по умолчанию тёмная в обеих темах */
  --tb-bg: #0d0d0d;
  --tb-text: #e8e8e8;
  --tb-text-dim: #a0a0a0;
  --tb-accent: #6ee7b7;
  --tb-surface-2: #1c1c1c;
  /* --tb-muted (роль пользователя, стрелка меню) слит с --tb-text-dim: два уровня
     приглушённого текста в шапке не читались как разные. Алиас — см. блок алиасов выше. */
  --tb-muted: var(--tb-text-dim);
  --radius: 14px;
  --radius-sm: 9px;
  --shadow: 0 8px 30px rgba(0,0,0,.4);
  /* Тень карточек главной: сетка недели, панель часов над ней, виджеты, кокпит.
     ⚠️ ОДНА на всех (решение владельца продукта «всему одинаковую тень»): раньше у сетки
     была своя, вдвое плотнее, а виджеты стояли почти плоскими — колонки не читались как
     одна страница. Это прежняя тень сетки с прозрачностью ровно вдвое меньше.
     Светлой теме отдельное значение не нужно: чёрная тень одинаково работает на обоих фонах.
     2026-10-20: плотность дважды +20% (.025→.03→.036, .2→.24→.288) — в светлой теме блоки
     сливались с фоном. Это ЕДИНСТВЕННЫЙ рычаг тени карточек: новая карточка берёт этот
     токен (через список ниже или напрямую), а не свою тень. */
  --card-shadow: 0 1px 2px rgba(0,0,0,.036), 0 16px 34px -22px rgba(0,0,0,.288);
  --font: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
}

/* ── Светлая тема ──────────────────────────────────────────────────
   Переключается атрибутом data-theme="light" на <html> (см. app.js + index.html).
   Тёмная тема — значения по умолчанию выше; здесь только переопределения палитры.
   Переменные шапки (--tb-*) НЕ переопределяются — шапка остаётся тёмной, пока
   администратор не задаст ей свои цвета.
   Точечные правки под захардкоженные цвета — в конце файла (блок «Светлая тема — точечно»). */
:root[data-theme="light"] {
  color-scheme: light;
  /* Окна сторон в светлой теме — ПАСТЕЛЬНЫЕ (правка владельца продукта 2026-10-20): сочные
     #df7701 / #787878 на белой сетке расписания класса «слишком выбивались». Тёмная тема
     остаётся при прежних сигнальных тонах. --win-both собран из этих двух и меняется сам. */
  --win-teacher: #eea357;   /* 2026-10-20: пастель #f4b67c/#c4c4c4 вышла тусклой — на 30% назад к сочным */
  --win-student: #adadad;
  --stage-done: #15803d;
  --stage-fail: #dc2626;
  --stage-now: #0a6ad6;
  --stage-todo: #a3aaa7;       /* нативные контролы (дата, селект, автозаполнение, скроллбар) — светлые */
  --bg: #ebebeb;
  --surface: #ffffff;
  --surface-2: #e9ecea;
  --border: #dadedc;
  --border-2: #cad0cd;
  --text: #14201b;
  --text-dim: #4f5b56;
  --muted: #66716b;          /* проходит контраст WCAG AA на белом */
  --accent: #047857;         /* глубокий изумруд — читается и как текст, и как фон кнопки */
  --accent-dim: #35b98a;     /* средний изумруд — рамки, фокус-кольцо, «вкл» тумблера */
  --danger: #dc2626;
  --warn: #b45309;
  /* Индикация оценки — читаемые на белом варианты (не из палитры школы, см. :root выше) */
  --grade-good: #059669;
  --online-dot: #16c60a;
  --grade-mid: #b45309;
  --grade-low: #dc2626;
  /* Зелёный с макета на белом даёт 3.4:1 — темнее, чтобы подпись читалась (5.6:1). */
  --notify-on: #166b1e;
  --ai-sparkle: #9a6b00;
  /* Свободное окно на белом — свежая мята, а не серо-зелёный (см. :root). */
  --work-fill: color-mix(in srgb, #00c878 19%, transparent);
  --work-fill-hover: color-mix(in srgb, #00c878 25%, transparent);
  /* ⚠️ Статус-текст в светлой теме НЕ осветляем: базовые --lesson-* и так читаются на
     белом, а «яркие» варианты из :root дали бы контраст ниже 3:1. */
  --lesson-upcoming-fg: var(--lesson-upcoming);
  --lesson-done-fg: var(--lesson-done);
  /* Кольцо алмазов — сочный зелёный (правка владельца продукта): акцент светлой темы
     чёрный, и кольцо читалось траурной обводкой, а не «копится». */
  --diamond-ring: #1ec74f;
  --lesson-nobal-fg: var(--lesson-nobal);
  --on-accent: #ffffff;      /* на тёмно-изумрудной кнопке текст белый */
  --accent-hover: #059669;
  --on-accent-dim: #0f3b2c;
  --shadow: 0 10px 34px rgba(20,50,40,.12);
}

/* ── Плавная смена темы ────────────────────────────────────────────
   Тёмная и светлая ПЕРЕТЕКАЮТ друг в друга: браузер снимает кадр старой темы,
   app.js мгновенно перекрашивает страницу под ним, и старый кадр растворяется
   за --theme-fade (View Transitions API, класс .theme-swap ставит applyTheme).
   ⚠️ Кросс-фейд снимка, а НЕ transition на каждом элементе: при смене ТОЛЬКО
   css-переменной свойство с transition, читающее var(), в Chromium застревает
   на старом цвете — ровно поэтому applyTheme() глушит переходы на время свопа.
   ⚠️ mix-blend-mode: normal вместо стандартного plus-lighter: тот СКЛАДЫВАЕТ
   яркости двух кадров, и на переходе «тёмная ↔ светлая» середина вспыхивает
   белым — то самое «бьёт по глазам», от которого мы уходим. Старый кадр лежит
   сверху (z-index) и просто тает над уже готовой новой темой.
   ⚠️ prefers-reduced-motion этот фейд НЕ гасит — движения в нём нет, а у
   владельца «Уменьшение движения» на телефоне включено (см. app.js).
   ⚠️ Кривая — симметричная (cubic-bezier .45,0,.55,1), а НЕ стандартная `ease`:
   та срывается с места быстро и гасит бо́льшую часть старого кадра в первую
   треть — на длинном фейде это читается как рывок в начале и вялый хвост.
   Симметричная трогается мягко, основную работу делает в середине и так же
   мягко доводит до конца.
   ⚠️ Цена длинного фейда: пока он идёт, сверху лежит ЗАМОРОЖЕННЫЙ кадр старой
   темы, и всё, что появилось под ним за это время (тост, новое сообщение,
   спиннер), человек увидит только после. Клики при этом проходят насквозь
   (pointer-events: none), то есть работать интерфейсу фейд не мешает — но
   дальше растягивать длительность уже не стоит. */
:root { --theme-fade: 1.8s;     /* длительность фейда — единственное место, где она задана */
        --theme-ease: cubic-bezier(.45, 0, .55, 1); }   /* её же читает переход «из темноты» (ui.js veilUp) */
:root.theme-swap::view-transition { pointer-events: none; }   /* клик проходит на живую страницу */
:root.theme-swap::view-transition-old(root),
:root.theme-swap::view-transition-new(root) { mix-blend-mode: normal; }
:root.theme-swap::view-transition-new(root) { animation: none; }  /* новая тема уже готова — просто лежит снизу */
:root.theme-swap::view-transition-old(root) {
  z-index: 1;
  animation: theme-dissolve var(--theme-fade) var(--theme-ease) both;
}
@keyframes theme-dissolve { from { opacity: 1 } to { opacity: 0 } }

/* Шторка перехода «из темноты» (анкета → витрина, ui.js veilDown/veilUp). Цвет — фон
   уходящего (всегда тёмного) экрана: JS замораживает его при затемнении, потому что под
   шторкой тема сменится. Прозрачность ведёт WAAPI; без него — чёрный экран и резкое
   появление. */
.screen-veil { position: fixed; inset: 0; z-index: 2000; background: var(--bg); opacity: 0; }

* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }
body {
  font-family: var(--font);
  /* лёгкое «свечение» сверху — глубина вместо плоского чёрного */
  background: radial-gradient(1100px 480px at 50% -12%, color-mix(in srgb, var(--accent) 5%, transparent), transparent 60%), var(--bg);
  background-attachment: fixed;
  color: var(--text);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;          /* режем горизонтальный выход, НЕ делая body скролл-контейнером
                                (overflow-x:hidden + height:100% сломал бы window-скролл) */
  max-width: 100%;
}
.hidden { display: none !important; }
a { color: inherit; text-decoration: none; }

/* ── Телефон/планшет: страница не должна вести себя как обычный сайт ──────────
   Платформу открывают с телефона как приложение, а браузер по умолчанию даёт ей
   «сайтовые» жесты — и они мешают работе:
   1) ДВОЙНОЙ ТАП зумит страницу. На уроке тапают часто и быстро (упражнения, слова,
      кнопки чата), и случайный двойной тап масштабировал вёрстку — после чего экран
      «съезжает» и его надо возвращать пальцами. `manipulation` убирает ТОЛЬКО
      двойной тап-зум: прокрутка и пинч (доступность, и на iOS его всё равно не
      отключить) остаются. Элементы со своим `touch-action: none` (холст доски,
      ползунки, перетаскивание) строже — их это правило не ослабляет.
   2) АВТОЗУМ ПРИ ФОКУСЕ: iOS увеличивает страницу, если у поля шрифт мельче 16px.
      Общий «пол» в 16px уже стоит ниже (@media max-width:760px), но телефон в
      АЛЬБОМНОЙ ориентации и планшет шире 760px — там поля снова мельчали, и ввод
      текста зумил вёрстку. Признак «палец без курсора» точнее ширины: он ловит
      и альбомную ориентацию, и планшет, и не трогает ПК с мышью. */
@media (pointer: coarse) and (hover: none) {
  body { touch-action: manipulation; }
  .input, .textarea, .select, input, textarea, select, .rt-edit { font-size: 16px !important; }
}

/* ── Глобальная полировка ─────────────────────────────────────── */
::selection { background: var(--accent-dim); color: var(--on-accent-dim); }

/* Тёмные тонкие скроллбары (системные светлые на Windows ломают тему) */
* { scrollbar-width: thin; scrollbar-color: var(--border-2) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track, ::-webkit-scrollbar-corner { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border-2); border-radius: 8px;
  border: 2px solid var(--bg); background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background-color: #3d3d3d; }

/* Видимый фокус только с клавиатуры (Tab) — доступность без «рамок» при кликах */
a:focus-visible, button:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--accent-border); outline-offset: 2px; border-radius: var(--radius-sm);
}

/* Единые SVG-иконки (icons.js) — вместо эмодзи, одинаковы на всех ОС */
.ico { display: inline-flex; flex: 0 0 auto; }
.ico svg { width: 16px; height: 16px; display: block; }
.btn > svg, .icon-btn > svg { width: 15px; height: 15px; flex: 0 0 auto; }
.btn-sm > svg { width: 14px; height: 14px; }
.crm-col-menu, .cf-del, .cf-manage, .chat-attach, .fm-crumb {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
}
.crm-col-menu svg, .cf-del svg, .cf-manage svg, .chat-attach svg, .fm-crumb svg {
  width: 15px; height: 15px; flex: 0 0 auto;
}

/* ── Топбар ────────────────────────────────────────────────────── */
.topbar {
  position: sticky; top: 0; z-index: 50;
  /* Цвета шапки — своя группа переменных (--tb-*, объявлены в :root): по умолчанию шапка
     тёмная в обеих темах, но администратор может перекрасить её отдельно от остального UI. */
  background: var(--tb-bg);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid transparent;
  transition: transform .25s ease;
}
.topbar.tb-hidden { transform: translateY(-100%); }   /* авто-скрытие при прокрутке вниз (мобиле) */
body.room-view .topbar { display: none; }              /* в комнате класса топбар не нужен (есть «✕ Выйти») */
body.board-view .topbar { display: none; }             /* доска — полноэкранная, свой топбар */
.topbar-inner {
  max-width: 1180px; margin: 0 auto; padding: 0 24px;
  height: 54px; display: flex; align-items: center; gap: 18px;
}
.brand {
  font-size: 1.3em; font-weight: 300; letter-spacing: 4px; color: var(--tb-text); flex: 0 0 auto;
}
/* nav: при тесноте скроллится горизонтально, не выдавливая правую часть */
.nav { display: flex; gap: 4px; flex: 1 1 auto; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.nav::-webkit-scrollbar { display: none; }
.nav a {
  display: inline-flex; align-items: center; white-space: nowrap; flex: 0 0 auto;
  padding: 8px 13px; border-radius: var(--radius-sm); font-size: .9em;
  color: var(--tb-text-dim); transition: background .15s, color .15s;
}
.nav a:hover { color: var(--tb-text); background: var(--tb-surface-2); }
.nav a.active { color: var(--tb-accent); background: var(--tb-surface-2); }
/* Контейнер меню со стрелками прокрутки (когда не влезает) */
.nav-scroll { position: relative; flex: 1 1 auto; min-width: 0; display: flex; align-items: center; }
.nav-scroll .nav { flex: 1 1 auto; }
.nav-arrow { position: absolute; top: 0; bottom: 0; width: 34px; z-index: 6; padding: 0;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  border: none; background: none; color: var(--tb-text); font-size: 1.4em; line-height: 1; }
.nav-arrow-l { left: 0; justify-content: flex-start;
  background: linear-gradient(to right, var(--tb-bg) 55%, transparent); }
.nav-arrow-r { right: 0; justify-content: flex-end;
  background: linear-gradient(to left, var(--tb-bg) 55%, transparent); }
.nav-arrow:hover { color: var(--tb-accent); }
.topbar-right { display: flex; align-items: center; gap: 14px; flex: 0 0 auto; position: relative; }
.who { display: flex; align-items: center; gap: 9px; font-size: .82em; color: var(--tb-muted); white-space: nowrap; }
.who b { color: var(--tb-text-dim); font-weight: 500; }
.who-btn { cursor: pointer; border-radius: 99px; padding: 3px 9px 3px 3px; transition: background .15s; }
.who-btn:hover, .who-btn.open { background: var(--tb-surface-2); }
.who-btn:hover .who-text b, .who-btn.open .who-text b { color: var(--tb-accent); }
.who-caret { display: inline-flex; align-items: center; color: var(--tb-muted); font-size: .9em;
  transition: color .15s, transform .2s ease; }
.who-btn:hover .who-caret, .who-btn.open .who-caret { color: var(--tb-accent); }
.who-btn.open .who-caret { transform: rotate(180deg); }
/* Выпадающее меню профиля (настройки + выход) */
.who-menu { position: absolute; top: calc(100% + 8px); right: 0; z-index: 60; min-width: 210px;
  background: var(--surface); border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  box-shadow: 0 12px 32px rgba(0,0,0,.45); padding: 6px; display: flex; flex-direction: column; gap: 2px; }
.who-menu-item { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: none; border: none; cursor: pointer; color: var(--text-dim); font-family: var(--font);
  font-size: .9em; padding: 9px 11px; border-radius: var(--radius-sm); transition: background .12s, color .12s; }
.who-menu-item:hover { background: var(--surface-2); color: var(--text); }
.who-menu-item.danger:hover { color: var(--danger); }
.who-menu-ico { display: inline-flex; align-items: center; color: var(--muted); }
.who-menu-item:hover .who-menu-ico { color: inherit; }
/* Переключатель темы (солнце/луна) в меню профиля */
.who-menu-sep { height: 1px; background: var(--border); margin: 4px 3px; }
.who-theme { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 6px 11px 8px; }
.who-theme-label { font-size: .86em; color: var(--text-dim); }
.theme-seg { display: inline-flex; gap: 2px; padding: 3px; background: var(--surface-2);
  border: 1px solid var(--border-2); border-radius: 99px; }
.theme-seg-btn { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 27px;
  border: none; background: none; border-radius: 99px; color: var(--muted); cursor: pointer;
  transition: background .15s, color .15s; }
.theme-seg-btn svg { width: 15px; height: 15px; display: block; }
.theme-seg-btn:hover { color: var(--text); }
.theme-seg-btn.active { background: color-mix(in srgb, var(--selected) 16%, transparent); color: var(--selected); }
/* Отдельная иконка выхода в шапке свёрнута в меню профиля */
.topbar-logout { display: none; }
.avatar.topbar-avatar { width: 30px; height: 30px; font-size: .82em; }
/* Статус учителя «Premium»: маленькая чёрная таблетка (ui.premiumBadge). Чёрная в ОБЕИХ
   темах — это знак статуса, а не цвет интерфейса; на тёмном фоне её держит тонкая рамка.
   Подсказка — плавающий .premium-tip в body (см. ui.js, почему не ::after). */
:root { --premium-bg: #000; --premium-text: #fff; --premium-border: rgba(255,255,255,.22); }
.premium-pill { display: inline-flex; align-items: center; flex: 0 0 auto; align-self: flex-start;
  height: 13px; padding: 0 5px; border-radius: 99px; background: var(--premium-bg);
  color: var(--premium-text); border: 1px solid var(--premium-border);
  font-size: 8px; font-weight: 600; letter-spacing: .06em; line-height: 1;
  white-space: nowrap; cursor: default; }
.premium-tip { position: fixed; z-index: 1000; padding: 6px 10px; border-radius: 8px;
  background: var(--premium-bg); color: var(--premium-text); border: 1px solid var(--premium-border);
  font-family: var(--font); font-size: 12px; font-weight: 500; white-space: nowrap;
  box-shadow: 0 8px 24px rgba(0,0,0,.35); pointer-events: none; }
/* Шапка: имя и таблетка — колонкой справа от аватарки; имя поднимается выше центра. */
.who-text { display: flex; flex-direction: column; gap: 3px; line-height: 1.15; }
.prof-hero-name .premium-pill { display: inline-flex; margin-left: 10px; vertical-align: middle;
  position: relative; top: -3px; }
.cc-name-wrap .premium-pill { align-self: center; }
.avatar.avatar-xs { width: 20px; height: 20px; font-size: .58em; }
img.avatar { object-fit: cover; }
/* «Мой профиль» — модалка редактирования имени/email/фото.
   ⚠️ .mp-photo-row, .mp-avatar и .mp-photo-actions делит с ней окно фото класса
   (views/classes.js) — правки шапки профиля держать на своих классах (.mp-head …). */
.mp-photo-row { display: flex; align-items: center; gap: 24px; margin-bottom: 22px; }
.mp-avatar { width: 84px; height: 84px; flex: 0 0 auto; border-radius: 50%;
  background: var(--surface-2) center/cover no-repeat; border: 1px solid var(--border-2);
  display: grid; place-items: center; font-size: 1.9em; font-weight: 600; color: var(--accent-text); overflow: hidden; }
.mp-photo-actions { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
/* ⚠️ Без этого [hidden] не прячет кнопку: display у .btn перебивает атрибут */
.mp-photo-actions > [hidden], .mp-sub > [hidden] { display: none; }
/* Шапка по макету владельца продукта: фото слева, имя и имейл — колонкой справа ровно на
   высоту фото (поля разнесены к его верху и низу), без подписей над полями. */
.mp-head { display: flex; align-items: center; gap: 20px; }
.mp-head .mp-avatar { width: 96px; height: 96px; font-size: 2.1em; }
/* stretch — колонка тянется до высоты фото; на телефоне поля по 16px чуть выше фото,
   и тогда уже фото встаёт по центру колонки (align-items выше). */
.mp-ident { flex: 1 1 auto; min-width: 0; align-self: stretch; display: flex;
  flex-direction: column; justify-content: space-between; gap: 10px; }
/* Мельче обычного поля, как на макете: два поля встают в высоту фото. На телефоне
   всё равно 16px — правило (pointer: coarse) с !important, иначе iOS зумит при фокусе. */
.mp-ident .input { font-size: .85em; }
/* Под фото: кнопка фото слева, «Сменить пароль» — справа той же строкой. */
.mp-sub { display: flex; align-items: center; gap: 12px; margin: 16px 0 18px; }
.mp-sub .mp-link { margin-left: auto; }
/* Тихая текстовая кнопка: «Сменить пароль», «Отвязать» */
.mp-link { background: none; border: none; padding: 4px 0; font: inherit; font-size: .85em;
  font-family: var(--font); color: var(--muted); cursor: pointer; transition: color .15s; }
.mp-link:hover { color: var(--text); }
.mp-wide { width: 100%; }
/* «Анкета» — рамка с подписью в разрыве. Рамка тут разделитель группы, а не край блока. */
.mp-group { border: 1px solid var(--border-2); border-radius: var(--radius); margin: 6px 0 20px;
  padding: 8px 20px 20px; display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.mp-group > legend { margin: 0 auto; padding: 0 12px; color: var(--muted); font-size: .9em; }
.mp-group .profile-info { margin-bottom: 4px; }
.mp-group .btn { background: var(--bg); }
.mp-group .btn:hover { background: var(--surface-2); }
/* «Уведомления» — та же рамка, но строки на фоне обычной кнопки окна, а не чёрные, как
   «Изменить анкету» (так на макете: это переключатели состояния, а не действие).
   ⚠️ Правила стоят НИЖЕ .mp-group .btn — специфичность равная, побеждает нижнее. */
.mp-notify { gap: 14px; }
.mp-notify .btn { background: var(--surface-2); }
.mp-notify .btn:hover { background: var(--border-2); }
/* Включённый канал — зелёная подпись «… уведомления подключены» (сигнал, не палитра). */
.mp-switch.on { color: var(--notify-on); }
.mp-notify .mp-hint { margin: 0; }
/* Бот заблокирован — плашка-предупреждение на месте переключателя: нажимать тут нечего */
.mp-tg-state { display: flex; align-items: center; justify-content: center; min-height: 42px;
  padding: 11px 20px; border-radius: var(--radius-sm); font-size: .9em; letter-spacing: .3px;
  color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, transparent); }
.mp-tg-meta { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: -8px; font-size: .85em; color: var(--muted); }
.mp-hint { margin: 12px 0 0; text-align: center; color: var(--muted); font-size: .85em; line-height: 1.45; }
/* Подвал: «Отмена» и «Сохранить» — две равные кнопки */
.form-actions.mp-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 26px; }

/* ── Каркас страниц ───────────────────────────────────────────── */
.app { max-width: 1180px; margin: 0 auto; padding: 36px 24px 80px; }
/* Плашка администратору «Боты Telegram не работают» (app.js renderBotsAlert) — над #app. */
.bots-alert { max-width: 1132px; width: calc(100% - 48px); margin: 16px auto 0; padding: 12px 16px; box-sizing: border-box;
  border: 1px solid var(--danger); border-radius: 10px; font-size: .9em; line-height: 1.45;
  background: color-mix(in srgb, var(--danger) 12%, var(--surface)); color: var(--text); }
.bots-alert-title { font-weight: 600; color: var(--danger); margin-bottom: 4px; }
.bots-alert-row { overflow-wrap: anywhere; }
.bots-alert-note { color: var(--muted); margin-top: 6px; font-size: .92em; }
@media (max-width: 760px) { .bots-alert { width: calc(100% - 32px); margin-top: 12px; } }
body.room-view .bots-alert, body.board-view .bots-alert { display: none; }   /* урок и доска — без плашек */
.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 28px; flex-wrap: wrap; }
.page-title { font-size: 1.7em; font-weight: 300; letter-spacing: .5px; }
.page-sub { color: var(--muted); font-size: .85em; margin-top: 4px; }
.crumbs { font-size: .82em; color: var(--muted); margin-bottom: 18px; }
.crumbs a:hover { color: var(--accent-text); }
.crumbs span { color: var(--border-2); margin: 0 6px; }

/* ── Кнопки ───────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; gap: 8px; justify-content: center;
  padding: 11px 20px; border-radius: var(--radius-sm); border: 1px solid transparent;
  background: var(--surface-2); color: var(--text); font-size: .9em; font-family: var(--font);
  line-height: 1.2;   /* одинаковая высота у button/span/a с классом .btn */
  cursor: pointer; transition: border-color .15s, background .15s, transform .1s, color .15s;
  letter-spacing: .3px;
}
.btn:hover { border-color: transparent; background: var(--border-2); }
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--btn-primary); color: var(--on-btn-primary); border-color: var(--btn-primary); font-weight: 600;
  box-shadow: 0 4px 16px color-mix(in srgb, var(--btn-primary) 18%, transparent); }
.btn-primary:hover { background: var(--btn-primary-hover); border-color: var(--btn-primary-hover); color: var(--on-btn-primary);
  box-shadow: 0 6px 22px color-mix(in srgb, var(--btn-primary) 26%, transparent); }
.btn-ghost { background: transparent; border-color: transparent; color: var(--text-dim); }
.btn-ghost:hover { background: var(--surface-2); color: var(--text); }
.btn-danger { color: var(--btn-danger); border-color: transparent; background: transparent; }
.btn-danger:hover { background: color-mix(in srgb, var(--btn-danger) 12%, transparent); border-color: transparent; }
.btn-sm { padding: 7px 13px; font-size: .82em; }
/* Кнопка «+ Создать…» в шапке «Материалов» — на 15% меньше обычной .btn (не .btn-sm,
   там разница почти 30% — крупнее, чем просили). Считано от .btn: padding 11px 20px
   → ×0.85 ≈ 9px 17px, font-size .9em → ×0.85 ≈ .77em. */
.btn-compact { padding: 9px 17px; font-size: .77em; }
.btn:disabled { opacity: .4; cursor: not-allowed; }

/* ── Карточки / сетки ─────────────────────────────────────────── */
.grid { display: grid; gap: 18px; }
.grid-cards { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.card {
  background: var(--surface); border: 1px solid transparent; border-radius: var(--radius);
  padding: 22px; transition: border-color .18s, transform .18s, background .18s;
}
.card.clickable { cursor: pointer; }
.card.clickable:hover { border-color: var(--accent-border); transform: translateY(-3px); background: var(--surface-2); }
.card-title { font-size: 1.1em; font-weight: 400; color: var(--text); margin-bottom: 8px; }
.card-desc { font-size: .85em; color: var(--text-dim); margin-bottom: 16px; min-height: 2.4em; }
.card-meta { display: flex; gap: 16px; font-size: .78em; color: var(--muted); border-top: 1px solid var(--border); padding-top: 14px; }
.card-meta b { color: var(--accent-text); font-weight: 600; }

/* ── Списки строками ──────────────────────────────────────────── */
.list { display: flex; flex-direction: column; gap: 10px; }
.row {
  display: flex; align-items: center; gap: 12px 16px; flex-wrap: wrap;
  background: var(--surface); border: 1px solid transparent; border-radius: var(--radius-sm);
  padding: 14px 18px; transition: border-color .15s, background .15s;
}
.row.clickable { cursor: pointer; }
.row.clickable:hover { border-color: var(--accent-border); background: var(--surface-2); }

/* ⚠️ Вложенная поверхность на тон светлее. У .card/.row фон — var(--surface), тот же,
   что у .card и .modal, и раньше их разделяла ТОЛЬКО рамка: без неё строка внутри
   модалки слилась бы с ней в один прямоугольник. */
.card .card, .card .row, .modal .card, .modal .row { background: var(--surface-2); }

/* ── Тень карточек — ОДНА на всю платформу ───────────────────────
   ⚠️ Решение владельца продукта: «везде в ЛМС» у карточек и блоков, лежащих на фоне
   страницы, одна и та же тень `--card-shadow` (см. :root). Раньше она была только на
   главной — материалы, класс, настройки, чат, CRM стояли плоскими рядом с ней.
   Здесь — СПИСОК таких блоков: новая карточка-блок на фоне страницы — дописать сюда.
   Карточки главной (.tdash-stat, .ck-card, .sched-wrap…) берут тот же токен в своих правилах.
   ⚠️ `:where()` — нулевая специфичность нарочно: любое правило компонента со своей тенью
   (подъём при перетаскивании, доска на весь экран, выделение) перекрывает общую без
   `!important` и без оглядки на порядок в файле.
   Вложенные (карточка в карточке, строка в модалке) — ПЛОСКИЕ: они на тон светлее
   (см. правило выше), и тень внутри карточки дробила бы её на этажи. */
:where(.card, .row, .mat-scope, .course-card, .mteach-card, .board-lib-card, .cv-module, .slide-view,
  .ex-inline-card, .book-ex, .cd-hero, .cd-person, .cd-stat, .cd-prog, .cd-panel,
  .room-nav-card, .room-hw-btn, .room-tools, .room-chat, .room-call, .room-ex, .cka, .lr-change,
  .chat, .crm-col, .crm-lsec, .crm-viewsw, .ss-group, .clr-preview, .tp-filters,
  .demo-stat, .demo-row, .tdemo-card, .demo-home, .tb-unit, .aut-card, .aut-side,
  .tcard-block, .promo-empty) { box-shadow: var(--card-shadow); }
:where(.card, .modal, .cd-panel, .cd-hero, .chat, .crm-col, .crm-lsec, .aut-side,
  .tp-filters, .clr-preview, .room-tools, .room-chat, .slide-view, .cv-module,
  .ex-inline-card, .book-ex, .ss-group, .tcard-block)
  :where(.card, .row, .mat-scope, .course-card, .mteach-card, .board-lib-card, .cv-module, .slide-view,
  .ex-inline-card, .book-ex, .cd-stat, .cd-prog, .cd-panel, .demo-stat, .demo-row,
  .tdemo-card, .tb-unit, .aut-card) { box-shadow: none; }
.row-num { width: 26px; height: 26px; flex: 0 0 auto; border-radius: 50%; background: var(--surface-2);
  border: 1px solid var(--border-2); display: grid; place-items: center; font-size: .75em; color: var(--muted); }
.row-main { flex: 1 1 200px; min-width: 0; }
.row-title { color: var(--text); font-size: .95em; }
.row-sub { color: var(--muted); font-size: .78em; margin-top: 2px; }
.row-actions { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }

/* ── Вкладки ──────────────────────────────────────────────────── */
.tabs { display: flex; gap: 4px; margin-bottom: 24px; border-bottom: 1px solid var(--border); }
.tab {
  padding: 11px 20px; background: transparent; border: none; border-bottom: 2px solid transparent;
  color: var(--text-dim); cursor: pointer; font-size: .92em; font-family: var(--font);
  letter-spacing: .3px; transition: color .15s, border-color .15s; margin-bottom: -1px;
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--selected); border-bottom-color: var(--selected); }
.tab .tab-count { font-size: .82em; color: var(--muted); margin-left: 6px; }
.tab.active .tab-count { color: var(--accent-dim); }

/* ── Навигация страницы «Материалы» ────────────────────────────────
   Два уровня: ЧЬЁ (Мои / Общие / Учителя) — сегментированный переключатель,
   и ТИП (Книги / Доски) — лёгкие текстовые вкладки. Разный визуальный вес и
   держит иерархию, поэтому оба уровня умещаются в ОДНУ строку.
   ⚠️ Панель-«слаб» под вкладками (язычки папки + большой блок на var(--surface-2))
   УБРАНА, и это главная правка вида. Метафора папки требовала, чтобы фон панели
   совпадал с фоном активного язычка, — а у школы со светлым --surface-2 (палитра
   настраивается!) это давало серую плиту во весь экран, на которой пустая вкладка
   читалась как дыра в вёрстке. Контент теперь лежит прямо на странице, как на всех
   остальных экранах платформы, и от палитры школы больше не зависит. */
.mat-nav { display: flex; align-items: center; justify-content: space-between;
  gap: 12px 22px; flex-wrap: wrap; margin-bottom: 22px; }
.mat-scope { display: inline-flex; gap: 2px; padding: 4px; border-radius: 13px;
  background: var(--surface); border: 1px solid var(--border); }
.mat-scope-btn {
  display: inline-flex; align-items: center; gap: 8px; white-space: nowrap;
  padding: 8px 16px; border: 0; border-radius: 9px; cursor: pointer;
  background: transparent; color: var(--text-dim);
  font-family: var(--font); font-size: .85em; letter-spacing: .2px;
  transition: color .15s, background .15s;
}
.mat-scope-btn:hover { color: var(--text); background: color-mix(in srgb, var(--text) 7%, transparent); }
.mat-scope-btn.active { background: color-mix(in srgb, var(--selected) 15%, transparent);
  color: var(--selected); font-weight: 600; }
/* Второй уровень — текст с подчёркиванием, а не пилюли: две группы пилюль подряд
   читались бы как один уровень, и иерархия «чьё → что» терялась. */
.mat-kinds { display: inline-flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.mat-kind {
  position: relative; display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 2px 9px; border: 0; background: none; cursor: pointer;
  color: var(--muted); font-family: var(--font); font-size: .85em; letter-spacing: .2px;
  transition: color .15s;
}
.mat-kind::after { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  border-radius: 2px; background: var(--selected); transform: scaleX(0); transition: transform .18s; }
.mat-kind:hover { color: var(--text-dim); }
.mat-kind.active { color: var(--text); }
.mat-kind.active::after { transform: scaleX(1); }
/* Счётчик — «таблетка», одна и та же на обоих уровнях (число должно выглядеть числом,
   а не частью подписи). tabular-nums, чтобы вкладки не дёргались при смене счёта. */
.mat-n { font-size: .8em; line-height: 1.6; padding: 0 7px; border-radius: 99px;
  background: color-mix(in srgb, var(--text) 10%, transparent); color: var(--muted);
  font-variant-numeric: tabular-nums; }
.mat-scope-btn.active .mat-n, .mat-kind.active .mat-n {
  background: color-mix(in srgb, var(--selected) 20%, transparent); color: var(--selected); }
.mat-note { color: var(--muted); font-size: .82em; line-height: 1.5;
  margin: -8px 0 20px; max-width: 620px; }
/* Пустая вкладка — карточка-заглушка с пунктиром («место, куда что-то встанет»),
   а не пустота посреди экрана. */
.mat-empty {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  padding: 52px 24px; border: 1px dashed var(--border-2); border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface) 45%, transparent);
}
.mat-empty-ico { width: 54px; height: 54px; border-radius: 50%; display: grid; place-items: center;
  background: color-mix(in srgb, var(--selected) 12%, transparent); color: var(--selected); margin-bottom: 16px; }
.mat-empty-ico svg { width: 24px; height: 24px; }
.mat-empty-title { font-size: 1.02em; color: var(--text); margin-bottom: 8px; }
.mat-empty p { color: var(--muted); font-size: .86em; line-height: 1.55; max-width: 430px; margin: 0 0 20px; }
.mat-empty .btn:last-child { margin-top: 0; }
.mat-empty p:last-child { margin-bottom: 0; }

@media (max-width: 760px) {
  .mat-nav { flex-direction: column; align-items: stretch; gap: 14px; }
  /* Сегменты растягиваются поровну — три подписи разной длины иначе дают рваный ряд. */
  .mat-scope { width: 100%; }
  .mat-scope-btn { flex: 1 1 0; justify-content: center; padding: 9px 8px; font-size: .8em; gap: 6px; }
  .mat-kind { font-size: .82em; }
  .mat-kinds { gap: 16px; }
  .mat-note { margin: -4px 0 16px; }
  .mat-empty { padding: 38px 18px; }
}

/* ── «Учителя → Книги»: язык + поиск, карточки преподавателей, окно с их книгами ──
   Цвета — только токены; градиент аватара и обложки задаёт JS (тот же coverGradient,
   что у обложек материалов — тёмный в обеих темах, поэтому светлый текст на нём
   читается и в светлой теме). */
.mteach-tools { display: flex; align-items: center; gap: 10px 12px; flex-wrap: wrap; margin-bottom: 22px; }
.mteach-tools .select { width: auto; min-width: 200px; }
.mteach-tools .search-field { flex: 0 1 260px; min-width: 0; }
.mteach-found { color: var(--muted); font-size: .82em; margin-bottom: 14px; }
.mteach-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
.mteach-card {
  display: flex; flex-direction: column; gap: 16px; padding: 18px; cursor: pointer;
  background: var(--surface); border: 1px solid transparent; border-radius: var(--radius);
  transition: border-color .18s, transform .18s, background .18s;
}
.mteach-card:hover { border-color: var(--accent-border); transform: translateY(-3px); background: var(--surface-2); }
.mteach-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.mteach-head { display: flex; align-items: center; gap: 12px; min-width: 0; }
.mteach-ava { flex: 0 0 auto; width: 42px; height: 42px; border-radius: 50%; display: grid; place-items: center;
  color: rgba(255,255,255,.9); font-size: 1.05em; font-weight: 500; }
.mteach-who { min-width: 0; }
.mteach-name { color: var(--text); font-size: 1.02em; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mteach-count { color: var(--muted); font-size: .8em; margin-top: 2px; }
.mteach-covers { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
.mteach-book { min-width: 0; }
.mteach-cover { aspect-ratio: 3 / 4; border-radius: 8px; background-size: cover; background-position: center;
  display: flex; align-items: center; justify-content: center; }
.mteach-cover .cc-cover-letter { font-size: 1.7em; font-weight: 300; color: rgba(255,255,255,.85); }
.mteach-btitle { margin-top: 6px; font-size: .72em; line-height: 1.3; color: var(--text-dim);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  overflow-wrap: anywhere; }   /* «Китайские иероглифы» в ячейке в четверть карточки иначе обрезалось по слову */
.mteach-more { color: var(--accent-text); font-size: .8em; }
.mteach-modal-sub { color: var(--muted); font-size: .85em; margin: -12px 0 20px; }

@media (max-width: 760px) {
  .mteach-tools .select, .mteach-tools .search-field { flex: 1 1 100%; width: 100%; }
  .mteach-grid { grid-template-columns: 1fr; gap: 14px; }
}

/* ── Бейджи ───────────────────────────────────────────────────── */
.badge {
  display: inline-flex; align-items: center; padding: 3px 10px; border-radius: 99px;
  font-size: .7em; letter-spacing: .6px; text-transform: uppercase; font-weight: 600;
  border: 1px solid var(--border-2); color: var(--text-dim);
}
.badge-accent { color: var(--badge-accent); border-color: var(--accent-border); background: color-mix(in srgb, var(--badge-accent) 7%, transparent); }
.badge-warn { color: var(--warn); border-color: rgba(251,191,36,.3); }
.badge-type { font-size: .65em; }

/* ── Формы ────────────────────────────────────────────────────── */
.field { margin-bottom: 16px; }
.field label { display: block; font-size: .78em; color: var(--text-dim); margin-bottom: 6px; letter-spacing: .3px; }
.input, .textarea, .select {
  width: 100%; padding: 11px 14px; background: var(--bg); color: var(--text);
  border: 1px solid var(--border-2); border-radius: var(--radius-sm); font-size: .92em;
  font-family: var(--font); transition: border-color .15s;
}
.input:focus, .textarea:focus, .select:focus { outline: none; border-color: var(--accent-border);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 9%, transparent); }   /* мягкое фокус-кольцо */
/* iOS Safari: нативные date/time-поля центрируют текст и живут своей высотой —
   снимаем системный вид и приводим к обычному .input */
.input[type="time"], .input[type="date"], .input[type="datetime-local"], .input[type="month"] {
  -webkit-appearance: none; appearance: none;
}
.input::-webkit-date-and-time-value { text-align: left; margin: 0; }
.input::-webkit-datetime-edit { padding: 0; }
.input::-webkit-calendar-picker-indicator { opacity: .5; filter: invert(.8); cursor: pointer; }
@media (max-width: 760px) {
  /* Одна высота с соседними полями (у них 16px-шрифт → 44px), паддинги по вертикали не нужны */
  .input[type="time"], .input[type="date"], .input[type="datetime-local"], .input[type="month"] {
    height: 44px; padding-top: 0; padding-bottom: 0;
    display: inline-flex; align-items: center;
  }
}
.select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  padding-right: 36px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23a0a0a0' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='5 8 10 13 15 8'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center; background-size: 14px;
}
.textarea { resize: vertical; min-height: 90px; line-height: 1.55; }
/* Комбобокс (текстовое поле + свой выпадающий список, напр. выбор модуля) */
.combo-wrap { position: relative; }
.combo-menu { display: none; position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 60;
  max-height: 220px; overflow-y: auto; background: var(--surface); border: 1px solid var(--border-2);
  border-radius: var(--radius-sm); box-shadow: 0 12px 32px rgba(0,0,0,.45); padding: 6px; flex-direction: column; gap: 2px; }
.combo-menu.open { display: flex; }
.combo-item { display: block; width: 100%; text-align: left; background: none; border: none; cursor: pointer;
  color: var(--text-dim); font-family: var(--font); font-size: .9em; padding: 9px 11px;
  border-radius: var(--radius-sm); transition: background .12s, color .12s; }
.combo-item:hover { background: var(--surface-2); color: var(--text); }
.form-actions { display: flex; gap: 10px; margin-top: 22px; }
.nl-actions .btn { flex: 1 1 0; justify-content: center; }
.nl-actions .btn .ico { margin-right: 6px; }
.combo-input { padding-right: 44px; }
.combo-chev { position: absolute; right: 6px; top: 50%; transform: translateY(-50%); width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center; background: none; border: none;
  color: var(--muted); cursor: pointer; border-radius: 8px; }
.combo-chev:hover { color: var(--text); }
.combo-chev svg { width: 18px; height: 18px; }

/* ── Пустое состояние ─────────────────────────────────────────── */
.empty { text-align: center; padding: 70px 20px; color: var(--muted); }
.empty-icon { font-size: 2.6em; opacity: .5; margin-bottom: 14px; }
/* SVG-иконка (icons.js) вместо эмодзи — в размер знака, который тут стоял раньше. */
.empty-icon .ico svg { width: 1em; height: 1em; }
.empty p { font-size: .9em; margin-bottom: 18px; }

/* ── Логин ────────────────────────────────────────────────────── */
/* Логин закрывает весь вьюпорт (fixed) — окно авторизации статично, без прокрутки страницы */
.auth-wrap { position: fixed; inset: 0; display: grid; place-items: center; padding: 24px; overflow: auto; }
.auth-card { width: 100%; max-width: 380px; background: var(--surface); border: 1px solid transparent;
  border-radius: var(--radius); padding: 38px 32px; box-shadow: var(--shadow); }
.auth-brand { text-align: center; font-size: 2.2em; font-weight: 200; letter-spacing: 8px; margin-bottom: 4px; }
.auth-tag { text-align: center; font-size: .72em; color: var(--muted); text-transform: uppercase; letter-spacing: 3px; margin-bottom: 30px; }
.auth-hint { margin-top: 22px; font-size: .74em; color: var(--muted); text-align: center; line-height: 1.7; border-top: 1px solid var(--border); padding-top: 16px; white-space: pre-line; }
.auth-hint code { color: var(--text-dim); }
/* «Забыли пароль?» / «Вернуться ко входу» — кнопка, выглядящая ссылкой (нужен именно
   <button>: тег <a> без href не фокусируется с клавиатуры). */
.auth-link { display: block; width: 100%; margin-top: 14px; padding: 6px; background: none;
  border: none; color: var(--muted); font: inherit; font-size: .82em; text-align: center;
  cursor: pointer; border-radius: 8px; }
.auth-link:hover { color: var(--text); }
.auth-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Карточка входа и ссылки на документы под ней — одной колонкой по центру экрана
   (два прямых ребёнка .auth-wrap поделили бы высоту сетки пополам). */
.auth-col { width: 100%; max-width: 380px; display: flex; flex-direction: column; align-items: stretch; }
.auth-legal { display: flex; flex-direction: column; align-items: center; gap: 10px; margin-top: 22px; }
.auth-legal a { color: var(--muted); font-size: .82em; text-decoration: none; }
.auth-legal a:hover { color: var(--text); }
.auth-hint a { color: var(--text-dim); text-decoration: underline; text-underline-offset: 2px; }
.auth-hint a:hover { color: var(--text); }

/* ── Модалка ──────────────────────────────────────────────────── */
.overlay { position: fixed; inset: 0; background: rgba(0,0,0,.7); backdrop-filter: blur(4px);
  display: grid; place-items: center; z-index: 100; padding: 24px;
  animation: overlay-in .16s ease; }
/* ⚠️ У окна рамки НЕТ (решение владельца продукта): его и так отделяют затемнённая
   подложка и тень — контур поверх них только шумит. Рамка не удалена, а обесцвечена:
   геометрия не едет, а точечные правила (напр. .modal:has(> .board-embed-modal))
   продолжают работать. */
.modal { width: 100%; max-width: 460px; background: var(--surface); border: 1px solid transparent;
  border-radius: var(--radius); padding: 28px; box-shadow: var(--shadow);
  max-height: calc(100vh - 48px); overflow-y: auto;
  animation: modal-in .2s cubic-bezier(.3,1.1,.4,1); }
.modal-wide { max-width: 620px; }
/* Таблица материалов (Память → «Смотреть все материалы») шире обычного «широкого»:
   семь столбцов, которые сравнивают глазами, в 620px не помещаются.
   ⚠️ Правило стоит ЗДЕСЬ, а не в своей секции ниже: по специфичности (0,2,0) оно
   сильнее мобильного `.modal, .modal-wide`, и из нижней части файла перебивало бы
   лист на телефоне — а так его снимает тот же медиазапрос, идущий следом. */
.modal:has(> .mat-modal) { max-width: 1040px; }
/* Окно слота у администратора: три столбца карточки урока (время · люди · действия). */
.modal:has(> .slot-modal) { max-width: 760px; }
/* Редактор страницы документа (оферта, политика): текст и предпросмотр по ширине устройства. */
.modal:has(> .lg-modal) { max-width: 1100px; }
/* Окна карточек кокпита (зарплаты / оплаты / уроки): таблица из трёх-четырёх колонок. */
.modal:has(> .ckd) { max-width: 920px; }
/* Все книги одного преподавателя («Материалы → Учителя»): сетка карточек в 3–4 столбца. */
.modal:has(> .mteach-modal) { max-width: 1040px; }
.modal-title { font-size: 1.2em; font-weight: 500; margin-bottom: 20px; }
/* ⚠️ Поля, селекты и тумблер ВНУТРИ окна — тоже без рамок: фон поля (--bg) и так темнее
   поверхности окна (--surface), контур ничего не добавляет. Глобально с .input этого
   сделать НЕЛЬЗЯ — на страницах поле нередко стоит прямо на --bg, и без рамки оно бы
   пропало; в модалке подложка всегда --surface, поэтому правило и scoped'ится сюда. */
.modal .input, .modal .textarea, .modal .select, .modal .slider,
.modal .stud-sel-pop { border-color: transparent; }
/* ⚠️ .combo-menu из списка ИСКЛЮЧЕНА намеренно: у неё фон --surface, то есть ровно фон
   самой модалки, и без рамки выпадающий список слился бы с окном под ним. */
/* Подсказка под тумблером — без рамки, но тон заливки поднят: на 6% она держалась только
   контуром и без него пропадала бы. */
.modal .recur-hint { border-color: transparent;
  background: color-mix(in srgb, var(--badge-accent) 13%, transparent); }
/* Фокус — состояние, а не «рамка в покое»: кольцо оставляем, оно нужно с клавиатуры. */
.modal .input:focus, .modal .textarea:focus, .modal .select:focus { border-color: var(--accent-border); }
@keyframes overlay-in { from { opacity: 0; } }
@keyframes modal-in { from { opacity: 0; transform: translateY(14px) scale(.98); } }
/* «Ручка» нижнего листа — настоящая <button>, а не div/::before: только у реального
   интерактивного элемента тап на iOS Safari гарантированно доставляет click. На ПК её нет. */
.modal-handle { display: none; }
/* Крестик закрытия. Обёртка НУЛЕВОЙ высоты и `position: sticky`: у .modal своя
   прокрутка, и обычная absolute-кнопка уехала бы вверх вместе с содержимым — у длинного
   окна (профиль-кокпит) закрыть его стало бы нечем. Нулевая высота нужна, чтобы кнопка
   не отодвигала содержимое: она «висит» над ним. z-index — выше карточек профиля. */
.modal-xwrap { position: sticky; top: -4px; z-index: 6; height: 0; display: flex;
  justify-content: flex-end; pointer-events: none; }
.modal-x { pointer-events: auto; width: 34px; height: 34px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  margin: -8px -8px 0 0; padding: 0; cursor: pointer;
  background: var(--surface-2); border: 1px solid transparent; border-radius: 50%;
  color: var(--text-dim); transition: color .15s, background .15s; }
.modal-x svg { width: 17px; height: 17px; }
.modal-x:hover { color: var(--text); background: var(--border-2); }
.modal-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Мобиле: модалка = нижний лист (bottom sheet) с «ручкой», выезжает снизу.
   ⚠️ Само движение листа (вверх при открытии, вниз при закрытии) задаёт JS через
   Web Animations API (`ui.js`), а НЕ CSS-анимация. Причина — реальный баг на телефоне
   владельца: правило `@media (prefers-reduced-motion: reduce) { .modal { animation: none } }`
   стоит ниже по файлу и с той же специфичностью, поэтому с включённым в iOS
   «Уменьшением движения» оно гасило и выезд, и уход вниз — лист просто возникал и
   пропадал рывком. WAAPI этому медиазапросу не подчиняется, и JS сам решает, насколько
   укоротить анимацию (тот же вывод, что и с автовращением сферы онбординга: движение
   при этой настройке УМЕНЬШАЕМ, а не убираем совсем). */
@media (max-width: 760px) {
  .overlay { place-items: end stretch; padding: 0; }
  .modal:has(> .mat-modal) { max-width: none; }
  .modal:has(> .slot-modal) { max-width: none; }
  .modal:has(> .lg-modal) { max-width: none; }
  .modal:has(> .ckd) { max-width: none; }
  .modal:has(> .mteach-modal) { max-width: none; }
  .modal:has(> .bnew) { max-width: none; }
  .modal, .modal-wide {
    max-width: none;
    border-radius: 18px 18px 0 0; border-left: none; border-right: none; border-bottom: none;
    padding: 12px 18px calc(22px + env(safe-area-inset-bottom));
    max-height: calc(100dvh - 36px);
    animation: none;   /* движением рулит JS, см. комментарий выше */
  }
  /* Тянется на всю ширину листа, ПЕРЕКРЫВАЯ боковые padding'и модалки (отсюда +36px к
     ширине): при width:100% отрицательные поля не растягивают кнопку, а сдвигают её
     влево на 18px — полоска уезжала из центра. Полоска центрируется флексом. */
  .modal-handle { display: flex; align-items: center; justify-content: center;
    width: calc(100% + 36px); margin: -6px -18px 8px; padding: 10px 0;
    border: none; background: none; -webkit-appearance: none;
    font: inherit; cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
  .modal-handle::before { content: ''; width: 40px; height: 4px; border-radius: 99px;
    background: var(--border-2); transition: background .15s, width .15s; }
  .modal-handle:hover::before, .modal-handle:active::before { background: var(--muted); width: 48px; }
}
@media (prefers-reduced-motion: reduce) {
  .overlay, .modal { animation: none; }
}

/* ── Тост ─────────────────────────────────────────────────────── */
/* ⚠️ `left: 50%` режет доступную ширину абсолютного блока до половины экрана — без
   `width: max-content` фраза в одно предложение сворачивалась в 3–4 строки, сколько бы ни
   был max-width. Рамки нет (правило «без рамок»): форму держит полупрозрачная заливка
   поверх размытия, глубину — тень. */
.toast { position: fixed; bottom: 26px; left: 50%; transform: translate(-50%, 80px);
  width: max-content; max-width: min(560px, calc(100vw - 32px));
  background: var(--surface-2);   /* запас для браузеров без color-mix */
  background: color-mix(in srgb, color-mix(in srgb, var(--text) 7%, var(--surface-2)) 86%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.25); backdrop-filter: blur(14px) saturate(1.25);
  color: var(--text); text-align: center; text-wrap: balance;
  padding: 10px 18px; border-radius: 14px; font-size: .78em; line-height: 1.4; letter-spacing: .005em;
  z-index: 200; opacity: 0;
  transition: transform .25s, opacity .25s;
  box-shadow: 0 12px 32px rgba(0,0,0,.45), 0 2px 8px rgba(0,0,0,.25);
  pointer-events: none; }   /* тост не перехватывает тапы (висит над полем ввода чата снизу) */
.toast.show { transform: translate(-50%, 0); opacity: 1; }
.toast.err { background: color-mix(in srgb, var(--danger) 20%, var(--surface-2));
  background: color-mix(in srgb, color-mix(in srgb, var(--danger) 20%, var(--surface-2)) 88%, transparent);
  color: #fca5a5; }

/* ── Карточка-пуш «подключите Telegram-бот» (tgnudge.js, ученику при входе в комнату) ──
   Всплывающая поверхность над содержимым — рамку сохраняет (см. правило «без рамок»).
   z-index НИЖЕ .overlay (100): открытое окно накрывает карточку, а не наоборот.
   Движение — из JS (WAAPI), поэтому transform здесь не задаём: центрируем margin'ом. */
.tg-nudge { position: fixed; z-index: 95; top: calc(16px + env(safe-area-inset-top)); right: 18px;
  width: 380px; max-width: calc(100vw - 24px); display: flex; gap: 12px; padding: 14px 14px 12px;
  background: var(--surface); border: 1px solid var(--border-2); border-radius: var(--radius);
  box-shadow: var(--shadow); }
.tg-nudge-ico { flex: none; width: 36px; height: 36px; border-radius: 50%; display: grid; place-items: center;
  background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent-text); }
.tg-nudge-ico svg { width: 17px; height: 17px; margin: 1px 1px 0 0; }   /* самолётик оптически смещён */
.tg-nudge-body { flex: 1; min-width: 0; }
.tg-nudge-title { font-weight: 600; font-size: .92em; color: var(--text); margin-top: 1px; }
.tg-nudge-text { margin: 4px 0 12px; font-size: .86em; line-height: 1.45; color: var(--text-dim); }
.tg-nudge-actions { display: flex; justify-content: flex-end; gap: 8px; }
.tg-nudge-actions .btn { white-space: nowrap; }
@media (max-width: 760px) {
  .tg-nudge { top: calc(10px + env(safe-area-inset-top)); left: 0; right: 0; margin: 0 auto; }
  /* Узко: основная кнопка забирает остаток строки (крупная цель для пальца), «Потом» — по тексту */
  .tg-nudge-actions .btn-primary { flex: 1; }
}

/* ── Плеер урока ──────────────────────────────────────────────── */
.player { max-width: 720px; margin: 0 auto; }
.player-progress { height: 4px; background: var(--surface-2); border-radius: 99px; margin-bottom: 28px; overflow: hidden; }
.player-progress > div { height: 100%; background: var(--progress); transition: width .3s; }
.slide-view { background: var(--surface); border: 1px solid transparent; border-radius: var(--radius);
  padding: 16px; min-height: 180px; }
.slide-view h2 { font-weight: 400; font-size: 1.5em; margin-bottom: 18px; }
.slide-view .body-text { white-space: pre-wrap; color: color-mix(in srgb, var(--text) 75%, transparent); line-height: 1.8; }
.choice-opt { display: block; width: 100%; text-align: left; padding: 10px 14px; margin-bottom: 8px;
  background: var(--bg); border: 1px solid transparent; border-radius: var(--radius-sm); color: var(--text);
  cursor: pointer; font-size: .95em; transition: border-color .15s, background .15s; font-family: var(--font); }
.choice-opt:hover { border-color: var(--accent-border); }
.choice-opt.selected { border-color: var(--selected); background: color-mix(in srgb, var(--selected) 8%, transparent); }
.choice-opt.correct { border-color: var(--correct); background: color-mix(in srgb, var(--correct) 14%, transparent); }
.choice-opt.wrong { border-color: var(--wrong); background: color-mix(in srgb, var(--wrong) 12%, transparent); }
/* Вопрос с несколькими верными ответами: варианты выглядят как обычные (без галочек —
   решение владельца), над ними строка «Выберите все верные варианты · [Проверить]».
   Верно/неверно появляется только после «Проверить». */
.choice-multi-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  min-height: 32px; margin: -2px 0 10px; }
.choice-multi-hint, .choice-multi-status { font-size: .84em; color: var(--muted); min-width: 0; }
.choice-multi-status:empty { display: none; }
.choice-multi-status { color: var(--wrong); }
.choice-multi-status.ok { color: var(--correct); }
.choice-multi-bar .choice-check { flex: 0 0 auto; margin-left: auto; }
.player-nav { display: flex; justify-content: space-between; margin-top: 28px; }
.note-box { background: rgba(251,191,36,.06); border: 1px solid transparent; border-radius: var(--radius-sm);
  padding: 12px 16px; font-size: .88em; line-height: 1.6; white-space: pre-wrap;
  /* ⚠️ Текст заметки — того же янтарного тона, что и подложка, только ярче (правка владельца
     продукта): серо-белый текст на янтаре был самым контрастным пятном урока и забирал
     взгляд у упражнений. Подмес --muted гасит насыщенность; в светлой теме --warn тёмный,
     поэтому смесь и там читается на белом. */
  color: color-mix(in srgb, var(--warn) 60%, var(--muted)); }
.result-box { text-align: center; padding: 50px 20px; }
.result-score { font-size: 3em; font-weight: 200; color: var(--accent-text); }

/* ── Прочее ───────────────────────────────────────────────────── */
.spinner { width: 26px; height: 26px; border: 3px solid var(--border-2); border-top-color: var(--progress);
  border-radius: 50%; animation: spin .8s linear infinite; margin: 60px auto; }
@keyframes spin { to { transform: rotate(360deg); } }
.section-title { font-size: .8em; text-transform: uppercase; letter-spacing: 1px; color: var(--muted);
  margin: 32px 0 14px; }
.split { display: grid; grid-template-columns: 1fr 320px; gap: 24px; align-items: start; }
@media (max-width: 820px) {
  .split { grid-template-columns: 1fr; }
  .split > .lesson-side { position: static; max-height: none; }  /* колонка теперь под контентом — липнуть некуда */
}

/* ── Переключатель (switch) ───────────────────────────────────── */
.switch-row { display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 10px 0; font-size: .9em; color: var(--text-dim); cursor: pointer; }
.switch { position: relative; display: inline-block; width: 42px; height: 24px; flex: 0 0 auto; }
.switch input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; z-index: 1; }
/* ⚠️ Выключенная дорожка — полупрозрачный оттенок ТЕКСТА, а не --surface-2 с рамкой: тумблер
   стоит на любой подложке, и на карточке с фоном --surface-2 (секции анкеты преподавателя),
   да ещё в модалке, где .modal .slider снимает рамку, дорожка сливалась с фоном целиком —
   от тумблера оставалась одна точка. Оттенок текста темнее/светлее ЛЮБОЙ поверхности темы. */
.slider { position: absolute; inset: 0; background: color-mix(in srgb, var(--text) 18%, transparent);
  border: 1px solid transparent;
  border-radius: 99px; transition: background .2s, border-color .2s; pointer-events: none; }
.slider::before { content: ''; position: absolute; width: 18px; height: 18px; left: 2px; top: 50%; transform: translateY(-50%);
  background: #cfcfcf; border-radius: 50%; transition: transform .2s, background .2s; box-shadow: 0 1px 2px rgba(0,0,0,.45); }
.switch input:checked + .slider { background: var(--toggle-on); border-color: var(--toggle-on); }
.switch input:checked + .slider::before { transform: translate(18px, -50%); background: #fff; }
/* Подсказка под включённым тумблером (напр. еженедельный повтор урока) */
.recur-hint { font-size: .82em; color: var(--badge-accent); background: color-mix(in srgb, var(--badge-accent) 6%, transparent);
  border: 1px solid color-mix(in srgb, var(--badge-accent) 20%, transparent); border-radius: var(--radius-sm); padding: 9px 12px; }

/* ── Окно генерации урока (views/course.js generateLesson) ─────────
   По макету владельца продукта (октябрь 2026): подписи — внутри полей, параметры —
   сеткой 2×2, сверху панель Magic Lessons с тумблером. Панель и поля — на --bg (темнее
   окна --surface), без рамок, как всё в модалке.
   ⚠️ Блок стоит ПОСЛЕ .btn/.btn-primary/.slider: специфичность у части правил та же,
   и выше по файлу их перебили бы базовые. */
.ai-gen { display: flex; flex-direction: column; gap: 12px; }
.ai-gen-title { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; font-weight: 400; }
.ai-gen-title .ico { display: inline-flex; color: var(--ai-sparkle); }
.ai-gen-title .ico svg { width: 22px; height: 22px; }
.ai-gen-lead { margin: 0 0 10px; color: var(--text-dim); font-size: .92em; line-height: 1.45; }
.ai-gen-quota { margin: -10px 0 4px; color: var(--muted); font-size: .8em; }
.ai-gen-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px; }
.ai-gen .textarea { min-height: 110px; }
.ai-gen-status { margin: 0; }
.ai-gen-status:empty { display: none; }
.ai-gen-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 4px; }
/* «Отмена» — серая заливка (с макета: 14% текста поверх окна), а не прозрачная ghost. */
.ai-gen-cancel { background: color-mix(in srgb, var(--text) 14%, var(--surface));
  border-color: transparent; color: var(--text); }
.ai-gen-cancel:hover { background: color-mix(in srgb, var(--text) 20%, var(--surface));
  border-color: transparent; color: var(--text); }

.ai-magic { position: relative; overflow: hidden; isolation: isolate; display: block;
  padding: 16px 18px; border-radius: var(--radius); background: var(--bg); cursor: pointer; }
/* Холст перелива (.liquid-canvas) — первый ребёнок панели и кнопки: его в общий
   подъём слоёв не включаем, иначе он перестал бы быть подложкой во всю площадь. */
.ai-magic > :not(.liquid-canvas), .ai-gen-run > :not(.liquid-canvas) { position: relative; z-index: 1; }
.ai-magic-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.ai-magic-name { font-weight: 700; color: var(--text); }
.ai-magic-text { margin: 14px 0 0; color: var(--text-dim); font-size: .82em; line-height: 1.45; }
/* Выключенная дорожка светлее обычной (макет): на тёмной панели 18% текста терялись. */
.ai-magic .slider { background: color-mix(in srgb, var(--text) 50%, transparent); }
.ai-gen-run { position: relative; overflow: hidden; isolation: isolate; }

/* Включённый Magic: панель и «Начать» переливаются как «Войти в класс» (js/liquid.js,
   BUTTON_PALETTE). ⚠️ Цвета ФИКСИРОВАННЫЕ и одинаковые в обеих темах — это не
   палитра школы, а тот же самостоятельный «жидкий» градиент (см. .cd-enter). Градиент
   ниже — фолбэк без WebGL; с шейдером его закрывает холст. Подпись белая с тенью:
   на светлых нитях перелива без тени она теряется. */
.ai-magic.is-on, .ai-gen-run.is-magic, .ai-gen-run.is-magic:hover {
  background: linear-gradient(120deg, #a01a54, #d92d6b 45%, #e8703f);
  border-color: transparent; color: #fff;
  text-shadow: 0 1px 5px rgba(0, 0, 0, .45), 0 0 1px rgba(0, 0, 0, .35); }
.ai-gen-run.is-magic:hover { filter: brightness(1.06); }
.ai-magic.is-on .ai-magic-name { color: #fff; }
.ai-magic.is-on .ai-magic-text { color: rgba(255, 255, 255, .92); }

/* ── Рабочие часы мессенджера (Настройки → Школа) ───────────────
   Строка дня недели: тумблер + имя + окно «с – до». На узком экране время
   переносится на вторую строку ЕДИНЫМ блоком (.mh-time), а не рвётся по полям. */
.mh-day { display: flex; align-items: center; gap: 12px; padding: 9px 0;
  border-bottom: 1px solid var(--border); }
.mh-day:last-of-type { border-bottom: none; }
.mh-name { flex: 1; min-width: 90px; font-size: .88em; color: var(--text-dim); }
.mh-time { display: flex; align-items: center; gap: 8px; }
.mh-time .input { width: 104px; padding: 6px 8px; }
@media (max-width: 560px) {
  .mh-day { flex-wrap: wrap; }
}

/* ── Профиль ──────────────────────────────────────────────────── */
.avatar.avatar-lg { width: 56px; height: 56px; font-size: 1.4em; }
.profile-info { display: flex; flex-direction: column; gap: 2px; }
.pinfo-row { display: flex; justify-content: space-between; gap: 16px; padding: 9px 0;
  border-bottom: 1px solid var(--border); font-size: .88em; }
.pinfo-k { color: var(--muted); }
.pinfo-v { color: var(--text); text-align: right; }
.profile-hint { color: var(--muted); font-size: .85em; line-height: 1.6; margin: 0; }
/* Имя и имейл в «Основном» (profile.js identityForm): форма над строками «Зарегистрирован…»,
   от них отделена одиночной чертой — сами строки живут своей сеткой, как раньше. */
.prof-ident { padding-bottom: 14px; margin-bottom: 4px; border-bottom: 1px solid var(--border); }
.prof-ident .field { margin-bottom: 12px; }
.prof-ident .hint { margin: 0 0 12px; }

/* ── Профиль-«кокпит» (модалка админа) ────────────────────────────
   Окно во всю ширину страницы, но НЕ во весь экран: 1080px против 1180px у .app —
   контент читается как страница, а подложка по краям продолжает говорить «это окно».
   Специфичность :has выше, чем у .modal-wide, поэтому порядок правил не важен. */
.modal:has(> .prof) { max-width: 1080px; padding: 22px 24px 24px; }

/* Линию под шапкой рисует полоса вкладок (.prof-tabs): две подряд читались бы как ошибка. */
.prof-hero { display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  padding: 4px 0 18px; }
/* Составной селектор: у .avatar-lg своя специфичность 0,2,0, и одноклассового правила
   ниже по файлу не хватило бы предсказуемо — размер зависел бы от порядка. */
.prof-hero .avatar.avatar-lg { width: 72px; height: 72px; font-size: 1.9em;
  border-radius: 20px; flex: 0 0 auto; }
.prof-hero-main { flex: 1 1 240px; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.prof-hero-name { font-size: 1.42em; font-weight: 600; color: var(--text); line-height: 1.2; }
.prof-hero-mail { font-size: .85em; color: var(--text-dim); overflow-wrap: anywhere; }
.prof-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 3px; }
.prof-chip { font-size: .72em; letter-spacing: .04em; text-transform: uppercase;
  padding: 4px 9px; border-radius: 99px; color: var(--text-dim);
  background: var(--surface-2); border: 1px solid var(--border); }
.prof-chip.ok { color: var(--accent-text); border-color: var(--accent-border);
  background: color-mix(in srgb, var(--accent) 12%, transparent); }
.prof-chip.off { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 35%, transparent);
  background: color-mix(in srgb, var(--danger) 10%, transparent); }
.prof-chip.dim { color: var(--muted); font-variant-numeric: tabular-nums; }
.prof-hero-acts { display: flex; gap: 8px; flex-wrap: wrap; margin-left: auto; }

/* auto-fit, а НЕ `1fr 1fr`: у менеджера правая колонка пустая (`:empty` её скрывает), и с
   жёсткими двумя треками карточки жались бы в половину окна рядом с пустотой. */
.prof-grid { display: grid; gap: 16px; align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 360px), 1fr)); }
.prof-col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.prof-col:empty { display: none; }
.prof-card { background: var(--surface-2); border: 1px solid transparent;
  border-radius: var(--radius); padding: 16px 18px; min-width: 0; }
.prof-card-t { margin: 0 0 12px; font-size: .78em; font-weight: 600; letter-spacing: 1px;
  text-transform: uppercase; color: var(--muted); }
/* Внутри карточки заголовки разделов идут своим кеглем — общий .section-title с его
   32px сверху рвал бы карточку пополам. */
.prof-card .section-title { margin: 16px 0 10px; }
/* Дети родителя — кнопки в их профили (views/profile.js, ветка isParent). */
.prof-kids { display: flex; flex-wrap: wrap; gap: 8px; }
.prof-card > .pinfo-row:last-child, .prof-card .profile-info .pinfo-row:last-child { border-bottom: none; }
/* Вложенные поверхности внутри карточки: она сама уже на --surface-2, и плитки на нём
   слились бы с фоном (см. правило про фон вместо рамок в начале файла). */
.prof-card .sc-metric, .prof-card .sc-ex, .prof-card .ins-row, .prof-card .ins-state,
.prof-card .txn-row, .prof-card .ins-soon { background: var(--surface); }
/* ── Вкладки профиля ─────────────────────────────────────────────
   Полоса — общий .tabs (подчёркивание активной, на телефоне скроллится). Скрытая вкладка
   остаётся в DOM (собирается один раз), поэтому [hidden] подкреплён правилом: у содержимого
   вкладки свой display (grid, flex), и атрибут сам по себе его бы не пережил. */
.prof-tabs { margin: 0 0 18px; }
.prof-tabpane[hidden] { display: none; }
/* Окно по центру экрана: без нижней границы высоты оно сжималось бы до короткой вкладки и
   шапка с полосой вкладок «прыгала» бы при каждом переключении туда-обратно. */
.prof-stage { min-height: min(56vh, 520px); }
.prof-narrow { display: flex; flex-direction: column; gap: 16px; max-width: 640px; }
/* Расписание преподавателя (views/schedule.js teacherScheduleSection): сетка — та же
   «таблетками» .cd-sched, здесь только шапка с итогом, копированием и неделями. */
.tsch-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 10px 16px; margin-bottom: 12px; }
.tsch-sum { font-size: .9em; color: var(--text-dim); }
.tsch-sum b { color: var(--text); font-weight: 600; }
.tsch-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }
.tsch-copy, .tsch-add { display: inline-flex; align-items: center; gap: 6px; }
.tsch-copy svg, .tsch-add svg { width: 15px; height: 15px; }
.tsch-note { font-size: .85em; color: var(--muted); }
/* Окно «Новый урок» из профиля преподавателя (openTeacherLessonCreate): вид урока и дни
   недели — сегменты на всю ширину, дата — своей строкой, под ней время и минуты (втроём
   в ряд они в окне не помещаются). Дни недели — тот же .seg, но выбрать можно несколько. */
.tlc-kind { display: flex; flex-wrap: nowrap; width: 100%; margin: 0 0 16px; }
.tlc-kind .seg-btn { flex: 1; text-align: center; }
.tlc-when { display: flex; flex-wrap: wrap; gap: 0 12px; }
.tlc-when .field { flex: 1 1 0; min-width: 0; }
.tlc-when .tlc-date { flex-basis: 100%; }
.tlc-when .tlc-dur { flex: 0 0 auto; }
.tlc-days { display: flex; flex-wrap: nowrap; width: 100%; }
.tlc-days .seg-btn { flex: 1; padding: 8px 0; text-align: center; }
.tlc .recur-hint { margin-bottom: 4px; }
.tlc > .cd-book-sub { margin-top: 10px; font-size: .8em; }
@media (max-width: 760px) {
  .tsch-copy span { display: none; }
  .tsch-msk { display: none; }
}
.prof-foot { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border);
  display: flex; justify-content: flex-end; }
.prof-del { color: var(--danger); }
.prof-del:hover { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, transparent);
  background: color-mix(in srgb, var(--danger) 10%, transparent); }

/* Данные входа: логин / пароль / адрес страницы одной сеткой — подпись, значение и
   действие всегда на своих местах, поэтому строки читаются столбиком, а не вразнобой. */
.prof-cred { display: flex; flex-direction: column; gap: 2px; }
.prof-cred-row { display: grid; grid-template-columns: 78px minmax(0, 1fr) auto;
  align-items: center; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--border);
  font-size: .88em; }
.prof-cred-k { color: var(--muted); }
.prof-cred-v { color: var(--text); overflow-wrap: anywhere; }
.prof-cred-link { color: var(--accent-text); text-decoration: none; }
.prof-cred-link:hover { text-decoration: underline; }
/* Пароль моноширинным: его переписывают глазами и диктуют голосом — «l» и «1» в
   пропорциональном шрифте выглядят одинаково. */
.prof-cred-pwd { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 1.02em; letter-spacing: .04em; user-select: all;
  /* Пароль не переносится: в узкой колонке он рассыпался бы по знаку в строку,
     а прочитать его глазами нужно одной строкой. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.prof-cred-pwd.masked { color: var(--muted); letter-spacing: .12em; user-select: none; }
/* Пароль не выдавался школой — «нет данных», обычным шрифтом и без разрядки. */
.prof-cred-pwd.unset { color: var(--muted); font-family: inherit; letter-spacing: 0;
  font-size: 1em; user-select: none; }
.prof-cred-copy { width: 30px; height: 30px; flex: 0 0 auto; display: inline-flex;
  align-items: center; justify-content: center; padding: 0; cursor: pointer;
  background: var(--surface); border: 1px solid var(--border-2); border-radius: 9px;
  color: var(--text-dim); transition: color .15s, border-color .15s; }
.prof-cred-copy svg { width: 15px; height: 15px; }
.prof-cred-copy:hover:not(:disabled) { color: var(--text); border-color: var(--muted); }
.prof-cred-copy:disabled { opacity: .4; cursor: default; }
/* Два действия в подвале карточки: выдать пароль и отправить доступы. Главным (зелёным)
   по очереди становится то, которого ждут сейчас — до выдачи пароля копировать нечего. */
.prof-cred-foot { margin-top: 14px; display: flex; gap: 8px; flex-wrap: wrap; }
.prof-cred-foot .btn { flex: 1 1 auto; justify-content: center; }
.prof-cred-all { flex: 2 1 160px; }
.prof-cred-note { margin: 10px 0 0; font-size: .8em; line-height: 1.55; color: var(--muted); }
.prof-cred-note.warn { color: var(--warn); }
/* Ученик: доступы слева, памятка справа. auto-fit сам складывает колонки друг под друга,
   когда окно уже двух минимальных ширин — отдельный брейкпоинт не нужен. */
.prof-cred-wrap { display: grid; gap: 18px 28px; align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
.prof-memo { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.prof-memo-l { font-size: .85em; color: var(--muted); }
.prof-memo-text { min-height: 190px; font-size: .88em; }
.prof-memo-foot { display: flex; align-items: flex-start; gap: 10px 14px; flex-wrap: wrap; }
.prof-memo-save { flex: 0 0 auto; }
.prof-memo-foot .hint { flex: 1 1 200px; margin: 0; }

@media (max-width: 760px) {
  .modal:has(> .prof) { padding: 12px 16px calc(22px + env(safe-area-inset-bottom)); }
  .prof-hero { gap: 12px; }
  .prof-hero .avatar.avatar-lg { width: 56px; height: 56px; font-size: 1.5em; border-radius: 16px; }
  .prof-hero-name { font-size: 1.2em; }
  /* Кнопки шапки — своей строкой во всю ширину: втиснутые справа от имени они на
     телефоне съезжали под аватар и висели в воздухе. */
  .prof-hero-acts { margin-left: 0; flex: 1 0 100%; }
  .prof-hero-acts .btn { flex: 1 1 0; justify-content: center; }
  .prof-cred-row { grid-template-columns: 70px minmax(0, 1fr) auto; }
}

/* ── Центр статистики ученика ─────────────────────────────────── */
.sc-top { display: flex; gap: 14px; flex-wrap: wrap; }
.sc-metric { flex: 1 1 140px; background: var(--surface-2); border: 1px solid transparent; border-radius: var(--radius-sm); padding: 14px; text-align: center; }
.sc-metric-val { font-size: 1.7em; font-weight: 800; color: var(--accent-text); line-height: 1.1; font-variant-numeric: tabular-nums; }
.sc-metric-lbl { font-size: .8em; color: var(--text-dim); margin-top: 4px; }
.sc-metric-sub { font-size: .72em; color: var(--muted); margin-top: 2px; }
.sc-ex-grid { display: flex; gap: 14px; flex-wrap: wrap; }
.sc-ex { flex: 1 1 180px; background: var(--surface-2); border: 1px solid transparent; border-radius: var(--radius-sm); padding: 14px; }
.sc-ex-title { font-size: .85em; color: var(--text-dim); }
.sc-ex-acc { font-size: 2em; font-weight: 800; line-height: 1.3; font-variant-numeric: tabular-nums; color: var(--text-dim); }
.sc-ex-acc.good { color: var(--accent-text); }
.sc-ex-acc.mid { color: var(--warn); }
.sc-ex-acc.low { color: var(--danger); }
.sc-ex-meta { display: flex; gap: 12px; font-size: .8em; color: var(--text-dim); margin-top: 4px; flex-wrap: wrap; }
.sc-ex-meta b { color: var(--text); }
.sc-ex-count { margin-left: auto; }
.balance-box { display: flex; align-items: baseline; gap: 10px; background: var(--bg);
  border: 1px solid transparent; border-radius: var(--radius-sm); padding: 16px 20px; }
.balance-num { font-size: 2em; font-weight: 200; color: var(--badge-accent); }
.balance-cap { font-size: .8em; color: var(--muted); }
.txn-list { display: flex; flex-direction: column; gap: 2px; max-height: 220px; overflow: auto; }
.txn-row { display: flex; align-items: center; gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: .82em; }
.txn-delta { width: 38px; font-weight: 600; }
.txn-delta.pos { color: var(--accent-text); }
.txn-delta.neg { color: var(--danger); }
.txn-reason { flex: 1; color: var(--text-dim); }
.txn-date { color: var(--muted); font-size: .92em; }

/* ── Журнал активности (модалка «Профиль», admin-only) ───────────── */
.act-modal { min-height: 140px; }
.act-list { display: flex; flex-direction: column; max-height: 60vh; overflow: auto; }
.act-row { display: flex; align-items: center; gap: 10px; padding: 8px 0;
  border-bottom: 1px solid var(--border); font-size: .85em; }
.act-row:last-child { border-bottom: none; }
.act-ico { flex: 0 0 auto; width: 17px; height: 17px; color: var(--muted); }
.act-ico svg { width: 100%; height: 100%; }
.act-text { flex: 1 1 auto; min-width: 0; color: var(--text); }
.act-time { flex: 0 0 auto; color: var(--muted); font-size: .88em; font-variant-numeric: tabular-nums; }
.act-empty { color: var(--muted); font-size: .88em; text-align: center; padding: 28px 0; }
.act-foot { margin-top: 10px; }
.act-foot .act-more { width: 100%; }

/* ── Тарифы ───────────────────────────────────────────────────── */
.tariff-card { padding: 18px 20px; }
.tariff-head { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 14px; }
.tariff-lang { font-size: 1.05em; color: var(--text); font-weight: 500; }
.tariff-dur { font-size: .78em; color: var(--muted); margin-top: 2px; }
.pkg-list { display: flex; flex-direction: column; gap: 6px; }
.pkg-row { display: grid; grid-template-columns: 1fr auto auto; gap: 18px; font-size: .86em;
  color: var(--text-dim); padding: 4px 0; }
.pkg-row .pkg-price { color: var(--text); text-align: right; min-width: 110px; }
.pkg-row .pkg-months { color: var(--muted); text-align: right; min-width: 60px; }
.pkg-edit-list { display: flex; flex-direction: column; gap: 8px; }
.pkg-edit-row { display: flex; gap: 8px; align-items: center; }

/* ── Расписание ───────────────────────────────────────────────── */
.week-nav { display: flex; align-items: center; gap: 8px; }
.week-nav .btn-today { order: -1; }   /* «Сегодня» — слева от стрелок, чтобы они не скакали при появлении кнопки */
.week-label { font-size: .9em; color: var(--text-dim); min-width: 150px; text-align: center;
  white-space: nowrap; }   /* «03 авг. — 09 авг.» — одной строкой, иначе на мобиле ломается на три */
/* ── Сетка расписания (компактная, адаптивная) ────────────────── */
/* ⚠️ По умолчанию без внешней рамки (решение владельца продукта): обводка снаружи всей
   таблицы спорила с внутренними линиями. Форму держат скругление и тень (`--card-shadow`).
   Рамку школа включает сама — «Рамка расписания» в настройках темы. Рисуется КОЛЬЦОМ
   box-shadow, а не `border`: кольцо не сдвигает раскладку ни на пиксель, идёт по скруглению
   и не даёт шва у угла (см. пометку у .sched-toolbar). Без правки `--sched-frame-line`
   прозрачный — кольцо есть, но его не видно. */
.sched-wrap { border-radius: var(--radius); overflow-x: auto;
  -webkit-overflow-scrolling: touch; background: var(--surface);
  box-shadow: 0 0 0 1px var(--sched-frame-line), var(--card-shadow);
  scrollbar-width: thin; scrollbar-color: var(--border-2) transparent; }
.sched-wrap::-webkit-scrollbar { height: 8px; }
.sched-wrap::-webkit-scrollbar-track { background: transparent; margin: 0 6px; }
.sched-wrap::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 99px;
  border: 2px solid var(--surface); background-clip: padding-box; }
.sched-wrap::-webkit-scrollbar-thumb:hover { background: var(--accent-dim); background-clip: padding-box; }
/* `--today-line` — рамка сегодняшнего дня. Считается от токенов темы, поэтому светлая
   тема получает свой оттенок сама: тон чуть светлее обычных линий сетки — достаточно,
   чтобы колонку было видно, и недостаточно, чтобы она спорила с чипами. Приглушена ещё
   раз (color-mix с transparent) — по правке владельца продукта, полноценная яркость
   читалась слишком заметно на фоне остальной сетки; затем дважды по 30% прозрачнее
   (55% → 38.5% → 27%). */
.sched-grid { min-width: 680px;
  --today-line: color-mix(in srgb, color-mix(in srgb, var(--border-2) 65%, var(--text-dim) 35%) 27%, transparent); }
.sched-row { display: grid; grid-template-columns: 46px repeat(7, minmax(72px, 1fr)); }
/* Последняя колонка строки не получает свою рамку — без неё правого края таблицы
   касалась бы ещё одна линия там, где сетка и так обрывается. */
.sched-row > *:last-child { border-right: none !important; }
/* Фон и текст полосы дней, линии сетки — токены группы «Расписание» (`--sched-*`, :root). */
.sched-head { background: var(--sched-head-bg); border-bottom: 1px solid var(--sched-grid); }
/* ⚠️ Внутренние линии по умолчанию — ТОГО ЖЕ цвета, что и заливка шапки (оба наследуют
   `--surface-2`), а не производная от `--border`: разные тона рамки и шапки читались как
   два разных элемента дизайна. Школа может развести их своими правками. */
.sched-time { padding: 4px 8px; font-size: .66em; color: var(--muted); text-align: right;
  border-right: 1px solid var(--sched-grid);
  font-variant-numeric: tabular-nums; position: sticky; left: 0; z-index: 2; background: var(--surface); }
.sched-head .sched-time { background: var(--sched-head-bg); z-index: 6; }
.sched-day { padding: 8px 4px 7px; text-align: center; position: relative;
  border-right: 1px solid var(--sched-grid); }
/* ⚠️ Сегодняшний день выделен СЕРОЙ РАМКОЙ ВО ВСЮ КОЛОНКУ, сверху донизу (решение
   владельца продукта). Прежняя заливка акцентом (16%) красила только шапку: в сетке,
   где сами уроки нарисованы цветными чипами, красная полоска наверху читалась как ещё
   один статус, а не как «вы здесь». Шапка колонки при этом чуть светлее соседей —
   нейтрально, тем же тоном текста.
   ⚠️ Рамка колонки — ОДНИМ элементом-оверлеем поверх всей сетки (шапка + все часы), а НЕ
   тенью на каждой ячейке отдельно: тень рвалась чёрными линиями сетки на границе каждого
   часа и в шапке казалась другим оттенком серого, чем в теле — «один цвет» ломался.
   Оверлей — свой мини-грид с теми же колонками, что у строк, растянутый на всю высоту
   `.sched-grid`; рисуется после строк, поэтому ложится НАД чёрными линиями сетки. */
.sched-grid { position: relative; }
.sched-today-overlay { position: absolute; inset: 0; display: grid;
  grid-template-columns: 46px repeat(7, minmax(72px, 1fr)); pointer-events: none; }
.sched-today-overlay > span { box-shadow: inset 0 0 0 1px var(--today-line); }
/* ⚠️ Крайняя правая колонка упирается в скруглённые углы `.sched-wrap`: прямоугольная рамка
   срезалась его overflow ровно на скруглении. Повторяем тот же радиус — линия идёт по дуге. */
.sched-today-overlay > span.edge-end { border-radius: 0 var(--radius) var(--radius) 0; }
.sched-head .sched-day.today { background: color-mix(in srgb, var(--sched-head-today) 7%, transparent); }
.sched-dow { display: block; font-size: .7em; color: var(--sched-head-text); text-transform: uppercase; letter-spacing: .05em; }
.sched-date { display: block; font-size: .74em; color: var(--sched-head-date); margin-top: 1px; font-variant-numeric: tabular-nums; }
.sched-day.today .sched-dow, .sched-day.today .sched-date { color: var(--sched-head-today); font-weight: 600; }
.sched-row:not(.sched-head) { border-top: 1px solid var(--sched-grid); }
.sched-cell { min-height: 32px; border-right: 1px solid var(--sched-grid);
  padding: 2px; cursor: pointer; transition: background .15s ease; display: flex; flex-direction: column; gap: 2px; }
.sched-cell:hover { background: color-mix(in srgb, var(--accent) 5%, var(--surface-2)); }
/* Базовый чип = SCHEDULED (нейтральный). Статусы перекрашивают.
   ⚠️ РАМОК У ЧИПОВ НЕТ (решение владельца продукта): обводка того же тона читалась вторым
   контуром вокруг заливки и дробила сетку. Снятый 1px компенсирован паддингом (2/6 → 3/7),
   поэтому размер чипа не изменился. Заливка СПЛОШНАЯ (не color-mix с прозрачностью), а текст
   поверх — тёмный тон той же краски (--on-*): на полупрозрачной подложке цветной текст того
   же тона сливался, и приходилось подбирать ему свой оттенок в каждой теме. */
.lesson-chip { background: var(--surface-2); border: 0; border-radius: 7px;
  padding: 3px 7px; font-size: .68em; color: var(--text-dim); cursor: pointer; display: flex; align-items: center;
  gap: 3px; overflow: hidden; white-space: nowrap; transition: background .12s, box-shadow .12s, transform .12s; }
.lesson-chip:hover { background: var(--surface);
  box-shadow: 0 3px 10px rgba(0,0,0,.12); transform: translateY(-1px); }
/* Предстоящий урок — ВСЕГДА голубой (--lesson-upcoming вне палитры школы). */
.lesson-chip.upcoming { background: var(--lesson-upcoming); color: #fff; }
.lesson-chip.completed { background: var(--lesson-done); color: #fff; }
.lesson-chip.noshow { background: var(--lesson-noshow); color: #fff; }
.lesson-chip.nobalance { background: var(--lesson-nobal); color: #fff; }
/* Пробный урок — розовый, поверх любого статуса: он бесплатный и живёт по своим правилам,
   а знакомство с новым учеником должно быть видно с одного взгляда. */
.lesson-chip.demo { background: var(--lesson-demo); color: #fff; }
/* ⚠️ Отменённых чипов НЕТ (решение владельца продукта): отменённые уроки не рисуются
   в сетке вовсе (фильтр до группировки byCell в schedule.js) — перечёркнутая серая
   плитка читалась как мусор, а не как информация. */
/* ⚠️ Ховер цветного чипа — ЯРКОСТЬ, а не своя заливка: правила статусов стоят ПОСЛЕ
   `.lesson-chip:hover` и при равной специфичности перебивают его фон. */
.lesson-chip.upcoming:hover, .lesson-chip.completed:hover, .lesson-chip.noshow:hover,
.lesson-chip.nobalance:hover, .lesson-chip.demo:hover { filter: brightness(1.1); }
/* Две строки: программа (класс) сверху, человек снизу. Обе режутся многоточием — колонка узкая. */
.chip-lines { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; line-height: 1.25; overflow: hidden; }
.chip-prog { overflow: hidden; text-overflow: ellipsis; font-weight: 600; }
.chip-name { overflow: hidden; text-overflow: ellipsis; }
.chip-prog + .chip-name { font-size: .92em; opacity: .8; }
.chip-name-s { display: none; }
/* На сплошной заливке подпись берёт цвет чипа — свой оттенок тут не читался бы. */
.chip-nobal { margin-left: auto; font-size: .92em; font-weight: 600; color: inherit; opacity: .85; white-space: nowrap; }
/* Буквенная метка урока: D — пробный, R — повторяющийся, S — разовый. Цвет как у текста чипа.
   ⚠️ Не крупнее 1.05em: буква большего кегля вылезает за высоту чипа (повтор раньше был
   глифом ⟳ в 1.5em — тот размер подбирался под мелкий значок, не под букву). */
.chip-mark { flex: 0 0 auto; align-self: center; font-weight: 800; font-size: 1.05em;
  line-height: 1; color: inherit; }
/* Урок не с начала часа (12:10, 12:30) — чип сдвинут вниз на долю часа (--chip-off = минуты/60)
   и заходит в клетку следующего часа. Шаг = высота чипа + то, что отделяет его от такого же
   места в следующей строке (паддинги клетки и линия; в лобби — зазор). ⚠️ Свойством
   `translate`, а не `transform`: ховер чипа сам двигает его через transform и затёр бы сдвиг.
   position+z-index — чтобы клетки следующей строки (они в DOM позже) не рисовались поверх. */
.lesson-chip.off { --chip-step: calc(100% + 5px); translate: 0 calc(var(--chip-off) * var(--chip-step));
  position: relative; z-index: 1; }
.sched-cell:has(> .lesson-chip.off) { overflow: visible; }
.sched-cell.past { cursor: default; }
/* Прогрессивная штриховка: диагональные штрихи заполняют ячейку слева направо
   пропорционально прошедшему времени часа (--fill = 0..1, каждый штрих ≈ 10 мин). */
.sched-cell.fill {
  background-color: color-mix(in srgb, var(--text) 1%, transparent);
  background-image: repeating-linear-gradient(45deg,
    color-mix(in srgb, var(--text) 5%, transparent) 0, color-mix(in srgb, var(--text) 5%, transparent) 1.5px,
    transparent 1.5px, transparent 7px);
  background-repeat: no-repeat;
  background-position: left center;
  background-size: calc(var(--fill, 1) * 100%) 100%;
}
.sched-cell.fill:hover { background-color: color-mix(in srgb, var(--text) 2.5%, transparent); }

/* Мобиле: расписание влезает без горизонтального скролла; урок = тонкая рамка [программа/имя + баланс] */
@media (max-width: 760px) {
  .sched-wrap { overflow-x: hidden; }
  .sched-grid { min-width: 0; }
  .sched-row { grid-template-columns: 30px repeat(7, minmax(0, 1fr)); }
  .sched-today-overlay { grid-template-columns: 30px repeat(7, minmax(0, 1fr)); }
  .sched-time { padding: 2px 1px; font-size: .54em; }
  .sched-day { padding: 5px 1px 6px; }
  .sched-dow { font-size: .6em; letter-spacing: .02em; }
  .sched-date { font-size: .56em; }
  .sched-cell { min-height: 24px; padding: 1px; gap: 1px; }
  /* Шрифт на пару пунктов меньше десктопного (.68em → .5em ≈ 8px): в колонку ~45px так влезает
     на 2-3 символа больше, а вместо инициала показываем сами программу и имя (что влезло). */
  .sched-cell .lesson-chip { padding: 1px 2px; min-height: 16px; gap: 2px; font-size: .5em;
    border-radius: 4px; align-items: center; }
  .sched-cell .lesson-chip:hover { transform: none; box-shadow: none; }
  .sched-cell .lesson-chip.off { --chip-step: calc(100% + 3px); }   /* на тач-экране «взлёт» чипа не нужен */
  /* Метку урока (R/D/S) на телефоне ПОКАЗЫВАЕМ: место освободилось от прогноза остатка,
     а одна буква в колонку ~45px влезает. Прячется только у отменённого — там и так всё ясно. */
  .sched-cell .lesson-chip .chip-mark { font-size: .95em; }
  .sched-cell .lesson-chip .chip-lines { line-height: 1.2; }
  .sched-cell .lesson-chip .chip-name { display: none; }          /* полное имя не влезает… */
  .sched-cell .lesson-chip .chip-name-s { display: block; overflow: hidden; text-overflow: ellipsis; }  /* …показываем только имя */
  .sched-cell .lesson-chip .chip-prog ~ .chip-name-s { font-size: .95em; opacity: .8; }
  /* «нет баланса» в ячейку шириной ~45px не влезает и обрезается посреди слова.
     На мобиле статус несёт цвет чипа (фиолетовый), как у проведённого/неявки — они тут
     тоже без подписи; подробности — по тапу в модалке урока. */
  .sched-cell .lesson-chip .chip-nobal { display: none; }
  .sched-cell.has .slot-badge { padding: 0 5px; min-width: 32px; height: 19px; font-size: .75em; }
}

/* ── Админ-сводка по слотам ───────────────────────────────────── */
.sched-cell.has { cursor: pointer; align-items: center; justify-content: center; }
.slot-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; height: 22px;
  box-sizing: border-box; padding: 0 9px; border: 0; border-radius: 99px;
  background: color-mix(in srgb, var(--warn) 12%, transparent); }
.sched-cell.has:hover .slot-badge { background: color-mix(in srgb, var(--warn) 22%, transparent); }
/* Слот прошёл — таблетка зелёная (оттенок с макета: приглушённый зелёный, не --lesson-done) */
.slot-badge.is-past { background: color-mix(in srgb, var(--slot-done) 30%, transparent); }
.sched-cell.has:hover .slot-badge.is-past { background: color-mix(in srgb, var(--slot-done) 42%, transparent); }
/* В слоте ИДЁТ урок (просьба владельца 29.10.2026) — таблетка переливается так же, как кнопка «Лобби класса»:
   тот же шейдер и та же палитра (js/liquid.js, BUTTON_PALETTE), плюс блик (`enter-sheen`). Шейдер один
   на все таблетки (`mountLiquidStrip`) и кладёт холст `.liquid-canvas` под цифру; градиент ниже — фолбэк
   без WebGL, те же цвета и то же медленное течение, что у язычков классов (`tg-tab-flow`).
   ⚠️ Цвета ФИКСИРОВАННЫЕ и одинаковые в обеих темах (как у кнопки), цифра белая с тенью: на светлых нитях
   перелива без тени она теряется. Правила стоят в два селектора — наведение на ячейку (`:hover`) красит
   обычную таблетку специфичнее, и без парного правила оно перекрыло бы перелив. */
.sched-cell.has .slot-badge.is-live, .sched-cell.has:hover .slot-badge.is-live {
  position: relative; overflow: hidden; isolation: isolate; color: #fff;
  background: linear-gradient(180deg, #a01a54, #d92d6b 30%, #e8703f 58%, #2bb3a8 82%, #a01a54) 0 0 / 100% 300%;
  animation: tg-tab-flow 14s linear infinite;
  transition: filter .18s ease; }
.sched-cell.has .slot-badge.is-live.has-liquid { animation: none; }
.slot-badge.is-live > :not(.liquid-canvas) { position: relative; z-index: 3; }
.slot-badge.is-live .slot-count { color: #fff; font-weight: 600;
  text-shadow: 0 1px 5px rgba(0, 0, 0, .45), 0 0 1px rgba(0, 0, 0, .35); }
/* Блик — та же диагональная полоса раз в несколько секунд, что на кнопке (keyframes `enter-sheen`). */
.slot-badge.is-live::after { content: ''; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(105deg, transparent 38%, color-mix(in srgb, #fff 62%, transparent) 50%, transparent 62%);
  transform: translateX(-140%); will-change: transform;
  animation: enter-sheen 6.5s ease-in-out infinite; }
.sched-cell.has:hover .slot-badge.is-live { filter: brightness(1.06) saturate(1.06); }
/* Светлая тема — тот же осветляющий фильтр, что у кнопки: тёмная фуксия на белом «слишком бросалась в глаза». */
[data-theme="light"] .slot-badge.is-live { filter: brightness(1.16) saturate(.82); }
[data-theme="light"] .sched-cell.has:hover .slot-badge.is-live { filter: brightness(1.22) saturate(.88); }
/* ⚠️ «Уменьшение движения» ЗАМЕДЛЯЕТ перелив, а не гасит (на телефоне владельца оно включено всегда —
   см. `.cd-enter`): шейдер сам идёт медленнее (`CALM_SPEED`), здесь — фолбэк и блик. */
@media (prefers-reduced-motion: reduce) {
  .sched-cell.has .slot-badge.is-live { animation-duration: 40s; }
  .slot-badge.is-live::after { animation-duration: 14s; }
}

/* Шапка расписания: одна строка из трёх колонок — часы слева, баланс уроков
   строго по центру экрана (крайние колонки 1fr равны), навигация справа. */
.sched-phead { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; }
.sched-phead > .sched-sub { justify-self: start; }
.sched-phead > .sched-bal { justify-self: center; }
.sched-phead > .week-nav { justify-self: end; }
/* Шапка админа (часы + листание недель) стоит ближе к сетке: общий `.page-head` оставляет 28px,
   а на кокпите это пустое место отнимало высоту у расписания и баянов (просьба владельца
   28.10.2026: «на 80% меньше» → 6px). Скоуп `.sched-page >` — общий `.page-head` других
   экранов не трогаем; на ≤760px шапка `display: contents`, там поле не действует. */
.sched-page > .sched-phead { margin-bottom: 6px; }
/* Ширина правой колонки кокпита — ОДНА на сетку (`.ck-main`) и на шапку (`.ck-phead`): шапка повторяет
   сетку, поэтому капсулы статусов кончаются ровно там, где кончается расписание. */
.sched-page { --ck-side: clamp(270px, 20vw, 330px); --ck-bar-h: 36px; }
/* И от топбара кокпит стоит ближе: верхнее поле страницы — как у чата (`body.msg-view .app`:
   18px вместо общих 36px, на ≤680px — 10px), просьба владельца 28.10.2026 («как сделан чат»).
   `.sched-page` есть только у главной админа (schedule.js), поэтому `:has` не задевает ни
   другие экраны, ни дашборд учителя и ученика (`.tdash`). Нижнее поле не трогаем: страница
   у кокпита прокручивается, и баянам под сеткой нужен запас снизу. */
.app:has(> .sched-page) { padding-top: 18px; }
@media (max-width: 680px) { .app:has(> .sched-page) { padding-top: 10px; } }
/* Кошелёк ученика: баланс уроков и алмазы — два счёта ОДНОГО И ТОГО ЖЕ (алмазы копят
   ради урока), поэтому они собраны в одну плашку с разделителем.
   ⚠️ У .sched-bal не было ни flex, ни gap: круглая «+» упиралась прямо в рамку алмазов,
   и две разнокалиберные фигуры без зазора читались как случайно слипшиеся.
   ⚠️ Подложка НЕЙТРАЛЬНАЯ, а не акцентная: акцент здесь несут числа, «+» и метка
   «на урок!»; вторая акцентная рамка вокруг них превращала бы кошелёк в предупреждение
   (проверено в палитре школы с красным акцентом).
   ⚠️ Ровно `--surface` + `--border-2`, как у `.btn`: в этой же строке справа стоят кнопки
   перелистывания недели, и плашка обязана читаться с ними одним набором. Плюс `--surface-2`
   в СВЕТЛОЙ теме почти равен фону страницы (#e9ecea против #ebebeb) — плашка там просто
   пропадала бы. */
.sched-bal { display: inline-flex; align-items: center; padding: 3px 5px;
  border-radius: 99px; background: var(--surface); border: 1px solid var(--border-2); }
/* У админа и учителя счётов нет вовсе (el() отбрасывает null'ы) — пустой плашки быть не должно.
   ⚠️ Именно снимаем оформление, а не `display:none`: шапка — грид `1fr auto 1fr`, и убранный
   элемент перестаёт быть его ячейкой — навигация по неделям уезжает из третьей колонки во
   вторую и встаёт вплотную к часам вместо правого края (поймано в браузере). */
.sched-bal:empty { padding: 0; border: 0; background: none; }
@media (max-width: 760px) {
  /* Мобильная главная (все роли): экономим место — компактная верхняя строка
     [баланс · часы], сетка сразу под ней, а навигация по неделям уезжает ВНИЗ.
     Раскладываем страницу flex-переносом; шапка «растворяется» (display:contents),
     чтобы часы, баланс, сетка и навигация встали в общий поток и переупорядочились. */
  .sched-page { display: flex; flex-wrap: wrap; align-items: center;
    justify-content: space-between; gap: 10px 0; }
  .sched-page .sched-phead { display: contents; }
  .sched-page .sched-bal { order: 0; }                   /* баланс — слева сверху */
  /* Часы — справа сверху. ⚠️ margin-left:auto не дубль space-between: когда кошелёк
     с меткой «потратить» занимает всю строку, часы переносятся на вторую — и без
     auto-поля уезжали бы к ЛЕВОМУ краю, то есть прыгали бы через весь экран
     при появлении метки. С ним они остаются под своим местом. */
  .sched-page .sched-sub { order: 1; margin-left: auto; }
  .sched-page .sched-wrap { order: 2; flex-basis: 100%; }        /* сетка — на всю ширину */
  .sched-page .week-nav { order: 3; flex-basis: 100%; justify-content: flex-end; gap: 6px; }
  .sched-phead .msk-sec { display: none; }               /* секунды скрыты — экономим место */
  .sched-phead .week-label { min-width: 0; font-size: .82em; }
  .sched-phead .week-nav .btn-sm { padding: 5px 9px; }   /* кнопки перелистывания поуже */
  /* Кокпит админа: СНАЧАЛА расписание, потом цифры (решение владельца продукта).
     Обёртка растворяется, чтобы сетка (order 2) и навигация по неделям (order 3) остались
     соседями, как без кокпита, — а карточки встали ПОСЛЕ навигации: навигация относится
     к сетке, и под пятью карточками её было бы не найти. */
  .ck-page .ck-main { display: contents; }
  .ck-page .ck-side { order: 4; flex-basis: 100%; }
  .sched-page > .cka { order: 5; flex-basis: 100%; }      /* баяны — в самом низу */
}

/* ── Светофор в шапке кокпита: «Серверы / Каналы / ИИ» (modules/cockpit/status.js) ──
   Макет владельца 29.10.2026: небольшие аккуратные панельки мелким шрифтом левее листания недель,
   у каждой — название и строки «точка + имя» в два ряда колонками. Цвета точек — токены оценок
   (`--grade-*`): они ВНЕ палитры школы, и «горит красным» не меняет смысл вместе с фирменным цветом.
   ⚠️ Подпись показывается подсказкой (`.ckst-tip`, position: fixed, зажата в окно), а не `title`:
   нативная появляется через секунду и на телефоне не появляется вовсе. */
/* Шапка повторяет сетку под ней: слева часы и капсулы (до края расписания), справа — листание недель
   над колонкой цифр. ⚠️ Третья колонка `minmax(side, auto)`: с кнопкой «Сегодня» листание шире колонки,
   и оно отодвигает капсулы влево, а не наезжает на них. Капсулы не помещаются — переносятся внутри своей
   ячейки, за край расписания не выходят. Высота капсул и кнопок листания — одна (`--ck-bar-h`). */
.ck-phead { grid-template-columns: auto minmax(0, 1fr) minmax(var(--ck-side), auto); column-gap: 16px; }
.ck-phead > .sched-sub { grid-column: 1; }
.ck-phead > .ckst { grid-column: 2; justify-self: end; }
.ck-phead > .week-nav { grid-column: 3; }
.ck-phead .week-nav .btn-sm { height: var(--ck-bar-h); padding-top: 0; padding-bottom: 0;
  display: inline-flex; align-items: center; justify-content: center; }
.ckst { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px 8px; min-width: 0; }
/* Место под панельки держим, пока ждём первый ответ: иначе сетка прыгала бы вниз на высоту панели. */
.ckst.loading { min-height: var(--ck-bar-h); }
.ckst:empty:not(.loading) { display: none; }
/* Цифры кокпита не загрузились — правой колонки нет, и шапка прежняя (3 колонки): капсулы по центру. */
.sched-phead:not(.ck-phead) > .ckst { justify-self: center; justify-content: center; }
.ckst-panel { box-sizing: border-box; min-height: var(--ck-bar-h); display: flex; align-items: center; gap: 12px;
  min-width: 0; padding: 3px 14px; border: 1px solid transparent; border-radius: 18px;
  background: var(--surface); box-shadow: var(--card-shadow); }
.ckst-title { margin: 0; font-size: 11.5px; font-weight: 400; white-space: nowrap; color: var(--text); }
.ckst-items { list-style: none; margin: 0; padding: 0; display: grid; grid-auto-flow: column;
  grid-template-rows: repeat(2, auto); gap: 0 14px; min-width: 0; }
.ckst-it { display: flex; align-items: center; gap: 5px; min-width: 0; font-size: 10px; line-height: 1.25;
  color: var(--text-dim); white-space: nowrap; cursor: default; border-radius: 6px; }
.ckst-it:hover { color: var(--text); }
.ckst-it:focus-visible { outline: 2px solid var(--accent-border); outline-offset: 2px; }
.ckst-name { overflow: hidden; text-overflow: ellipsis; }
.ckst-x { margin-left: 2px; color: var(--muted); font-variant-numeric: tabular-nums; }
.ckst-dot { flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; background: var(--muted); }
.ckst-dot.ok { background: var(--grade-good); }
.ckst-dot.bad { background: var(--grade-low);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--grade-low) 25%, transparent); }
.ckst-tip { position: fixed; z-index: 120; box-sizing: border-box; max-width: min(320px, calc(100vw - 16px));
  padding: 9px 12px; border-radius: 12px; background: var(--surface-2); box-shadow: var(--shadow);
  font-size: 12px; line-height: 1.45; color: var(--text); pointer-events: none; }
.ckst-tip[hidden] { display: none; }
.ckst-tip-h { display: flex; align-items: center; gap: 7px; }
.ckst-tip-h span { color: var(--text-dim); }
.ckst-tip-b { margin-top: 5px; color: var(--text-dim); white-space: pre-line; overflow-wrap: anywhere; }
/* ≤1000px сетка кокпита встаёт в одну колонку, и правой колонки под листанием больше нет: часы и
   листание — первой строкой, капсулы — второй, во всю ширину. */
@media (max-width: 1000px) {
  .ck-phead { grid-template-columns: 1fr auto; row-gap: 8px; }
  .ck-phead > .sched-sub { grid-column: 1; grid-row: 1; }
  .ck-phead > .week-nav { grid-column: 2; grid-row: 1; }
  .ck-phead > .ckst { grid-column: 1 / -1; grid-row: 2; justify-self: stretch; justify-content: flex-start; }
}
/* Телефон: панели друг под другом, после цифр и перед баянами (порядок потока — как у баянов, ничья по
   порядку в документе). Строки идут в поток, а не двумя колонками: на 390px так читается без обрезки. */
@media (max-width: 760px) {
  .sched-page .ckst { order: 5; flex-basis: 100%; justify-content: flex-start; }
  .ckst.loading { min-height: 0; }
  .ckst-panel { flex: 1 1 100%; align-items: flex-start; padding: 7px 14px; border-radius: 16px; }
  .ckst-title { min-width: 56px; padding-top: 1px; }
  .ckst-items { display: flex; flex-wrap: wrap; gap: 3px 14px; }
  .ckst-it { font-size: 11px; }
}

/* ── Баян «Активность пользователей» (modules/cockpit/activity.js) ──
   Макет владельца: под расписанием, на ВСЮ ширину страницы (как сетка + колонка цифр вместе —
   раньше был в половину ширины, и справа оставался пустой «воздух»). Свёрнутый — одна строка-
   заголовок с шевроном, раскрытый — три колонки рядом, «Учителя», «Ученики», «Родители», у каждой своя
   лента с вертикальным скроллом. Строки ленты мелким кеглем — чтобы на экран влезало больше.
   Цвет строки — по смыслу события (`actTone` в activity-lines.js), только токенами темы. */
.cka { margin-top: 16px; width: 100%;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden;
  --cka-fresh: color-mix(in srgb, var(--accent) 16%, transparent); }
.cka-head { all: unset; box-sizing: border-box; width: 100%; cursor: pointer;
  display: flex; align-items: center; gap: 10px; padding: 4px 16px;
  font-size: 15px; font-weight: 500; color: var(--text); }
.cka-head:hover { background: var(--surface-2); }
.cka-head:focus-visible { outline: 2px solid var(--accent-border); outline-offset: -2px; }
.cka-title { flex: 1 1 auto; min-width: 0; }
/* Точка «живая лента» — без пульсации: только состояние, не движение. */
.cka-live { width: 6px; height: 6px; border-radius: 50%; background: var(--presence-on);
  display: none; }
.cka.open .cka-live { display: block; }
.cka-chev { display: inline-flex; width: 18px; height: 18px; color: var(--text-dim); }
.cka-chev svg { width: 100%; height: 100%; }
.cka.open .cka-chev { transform: rotate(180deg); }
/* ⚠️ minmax(0, 1fr), а не 1fr: длинная строка без пробелов иначе растянула бы свою колонку и
   сдвинула соседние (у 1fr минимум — min-content). */
.cka-body { display: none; padding: 2px 14px 12px; border-top: 1px solid var(--border); }
.cka.open .cka-body { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: 28px; }
/* Средний экран: три ленты в ряд слишком узкие — учителя и ученики рядом, родители под ними
   на всю ширину. */
@media (max-width: 1280px) {
  .cka.open .cka-body { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cka.open .cka-body > .cka-col:last-child { grid-column: 1 / -1; }
}
.cka-col { min-width: 0; }
/* Баян «Уроки» (modules/cockpit/lessons.js): внутри один график на всю ширину, без лент. */
.cka-solo.open .cka-body { display: block; }
/* График присутствия над лентами — на обе колонки, низкий и схематичный. Цвета — по просьбе
   владельца: учителя красным, ученики синим; только алиасы токенов темы. */
.ckc { grid-column: 1 / -1; min-width: 0; margin-top: 10px;
  --ckc-teacher: var(--danger); --ckc-student: var(--stage-now); --ckc-parent: var(--lesson-done-fg);
  --ckc-held: var(--lesson-done-fg); }
.ckc-legend { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 6px 18px; margin-bottom: 6px; font-size: 12px; color: var(--text-dim); }
.ckc-keys { display: flex; flex-wrap: wrap; gap: 2px 14px; }
/* Чекбокс линии: своя кнопка, как .tc-check (нативный в тёмной теме красится браузером, мимо
   палитры), а цвет рамки и заливки — цвет самой линии. Выключенная — пустая рамка того же цвета. */
.ckc-key { display: inline-flex; align-items: center; gap: 7px; padding: 3px 2px; border: 0;
  background: none; font: inherit; color: var(--muted); cursor: pointer; }
.ckc-key:hover, .ckc-key.on { color: var(--text); }
.ckc-box { position: relative; width: 14px; height: 14px; flex: 0 0 auto; border-radius: 4px;
  border: 1.5px solid var(--ckc-tone); transition: background .12s; }
.ckc-key.on .ckc-box { background: var(--ckc-tone); }
.ckc-key.on .ckc-box::after { content: ''; position: absolute; left: 3.5px; top: .5px; width: 3.5px;
  height: 7px; border: solid var(--on-accent); border-width: 0 2px 2px 0; transform: rotate(45deg); }
.ckc-key:focus-visible, .ckc-per:focus-visible { outline: 2px solid var(--accent-border); outline-offset: 2px; }
/* Справа: «макс. 7 в час · 7 дней» и выбор периода. */
.ckc-side { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end;
  gap: 6px 12px; margin-left: auto; }
.ckc-peak { color: var(--muted); }
.ckc-pers { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 4px; }
.ckc-per { padding: 3px 10px; border-radius: 999px; font: inherit; cursor: pointer;
  color: var(--text-dim); border: 1px solid var(--border-2); background: transparent; }
.ckc-per:hover { color: var(--text); border-color: var(--accent-border); }
.ckc-per.on { color: var(--selected); border-color: var(--selected);
  background: color-mix(in srgb, var(--selected) 10%, transparent); }
/* Свои даты — отдельной строкой под легендой, только когда выбран «Свой». */
.ckc-range { display: none; flex-wrap: wrap; align-items: center; justify-content: flex-end;
  gap: 6px 8px; margin-bottom: 8px; font-size: 12px; color: var(--muted); }
.ckc.custom .ckc-range { display: flex; }
.ckc-date { width: auto; padding: 4px 8px; font-size: 12px; }
@media (max-width: 760px) {
  .ckc-date { font-size: 16px; }   /* iOS приближает страницу при фокусе в поле мельче 16px */
  .ckc-side, .ckc-pers { justify-content: flex-start; margin-left: 0; }
}
.ckc.loading .ckc-plot, .ckc.loading .ckc-days { opacity: .45; transition: opacity .15s; }
/* Все линии выключены — вместо пустого поля подсказка. */
.ckc-none { display: none; position: absolute; inset: 0; align-items: center; justify-content: center;
  color: var(--muted); font-size: 12px; pointer-events: none; }
.ckc.empty .ckc-none { display: flex; }
.k-teacher { --ckc-tone: var(--ckc-teacher); }
.k-student { --ckc-tone: var(--ckc-student); }
.k-parent { --ckc-tone: var(--ckc-parent); }
.k-held { --ckc-tone: var(--ckc-held); }
.ckc-plot { position: relative; height: 56px; cursor: crosshair; }
.ckc-svg { display: block; width: 100%; height: 100%; overflow: visible; }
.ckc-line { fill: none; stroke: var(--ckc-tone); stroke-width: 1.5; stroke-linejoin: round; }
/* Одна линия (баян «Уроки») — с лёгкой заливкой под ней, график там не штрих, а главное. */
.ckc-area { fill: var(--ckc-tone); fill-opacity: .14; stroke: none; }
.ckc-tall .ckc-plot { height: 150px; }
.ckc-tall .ckc-line { stroke-width: 2; }
/* Подпись единственной линии: без чекбокса (выключать нечего), только цвет и название. */
.ckc-lbl { display: inline-flex; align-items: center; gap: 7px; padding: 3px 2px; color: var(--text); }
.ckc-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ckc-tone); }
.ckc-grid { stroke: var(--border); stroke-width: 1; }
.ckc-days { position: relative; height: 16px; margin-top: 3px; }
.ckc-day { position: absolute; top: 0; padding-left: 3px; font-size: 10.5px; color: var(--muted);
  white-space: nowrap; }
.ckc-cross { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--text-dim);
  opacity: .5; pointer-events: none; display: none; }
.ckc-tip { position: absolute; bottom: calc(100% + 4px); z-index: 3; min-width: 140px; padding: 6px 9px;
  border-radius: 8px; background: var(--surface-2); border: 1px solid var(--border);
  font-size: 12px; line-height: 1.45; color: var(--text); pointer-events: none; display: none;
  white-space: nowrap; }
.ckc.hovering .ckc-cross, .ckc.hovering .ckc-tip { display: block; }
.ckc-tip-h { color: var(--muted); }
.ckc-tip-r { display: flex; align-items: center; gap: 6px; }
.ckc-tip-r::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--ckc-tone); }
/* Заголовок колонки — пилюля, как были вкладки, но это НЕ кнопка: обе ленты видны всегда. */
.cka-colhead { display: inline-block; margin: 10px 0 4px; padding: 4px 14px; border-radius: 999px;
  background: var(--border-2); color: var(--text); font-size: 13px; font-weight: 500; }
.cka-list { height: clamp(240px, 48vh, 480px); overflow-y: auto; overscroll-behavior: contain;
  padding-right: 4px; }
.cka-day { position: sticky; top: 0; z-index: 1; background: var(--surface);
  padding: 8px 0 3px; font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); }
.cka-row { display: flex; align-items: flex-start; gap: 7px; padding: 2px 6px;
  border-radius: 6px; font-size: 12.5px; line-height: 1.45; }
.cka-row:hover { background: var(--surface-2); }
.cka-time { flex: 0 0 auto; width: 2.9em; color: var(--muted); font-variant-numeric: tabular-nums; }
.cka-ico { flex: 0 0 auto; width: 13px; height: 13px; margin-top: 2px; color: var(--cka-tone); }
.cka-ico svg { width: 100%; height: 100%; }
.cka-text { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
/* Имя в строке — кнопка в профиль (окно поверх главной): выглядит текстом, подчёркивается
   при наведении. ⚠️ Выше .cka-who: `all: unset` иначе снял бы жирность имени. */
.cka-person { all: unset; cursor: pointer; border-radius: 3px; }
.cka-person:hover { text-decoration: underline; text-underline-offset: 2px; }
.cka-person:focus-visible { outline: 2px solid var(--accent-border); outline-offset: 1px; }
.cka-kid { color: var(--text-dim); }
.cka-who { font-weight: 600; color: var(--text); }
.cka-id { margin: 0 6px 0 5px; padding: 0 5px; border-radius: 5px; font-size: .85em;
  color: var(--text-dim); background: var(--surface-2); font-variant-numeric: tabular-nums; }
.cka-what { color: var(--cka-tone); }
.cka-note { margin-right: 6px; color: var(--muted); }
.cka-row { --cka-tone: var(--text-dim); }
.cka-row.t-presence { --cka-tone: var(--lesson-upcoming-fg); }
.cka-row.t-lesson   { --cka-tone: var(--lesson-done-fg); }
.cka-row.t-move     { --cka-tone: var(--warn); }
.cka-row.t-problem  { --cka-tone: var(--danger); }
.cka-row.t-reward   { --cka-tone: var(--lesson-nobal-fg); }
.cka-row.t-create   { --cka-tone: var(--ai-sparkle); }
.cka-row.t-demo     { --cka-tone: var(--lesson-demo); }
.cka-row.t-muted    { --cka-tone: var(--text-dim); }
.cka-empty { color: var(--muted); text-align: center; padding: 32px 0; font-size: 13px; }
.cka-more { display: flex; justify-content: center; padding: 10px 0; }
/* Узкий экран: две колонки не помещаются — ленты друг под другом, каждая ниже обычной высоты. */
@media (max-width: 760px) {
  .cka.open .cka-body { grid-template-columns: minmax(0, 1fr); row-gap: 8px; }
  .cka-list { height: clamp(220px, 38vh, 360px); }
}
@media (max-width: 560px) {
  .cka-head { padding: 8px 14px; }   /* на пальце строка не ниже ~38px (на ПК — 30px) */
  .cka-body { padding: 2px 10px 12px; }
}

/* ── Кокпит администратора (главная админа, modules/cockpit/cockpit.js) ──
   Макет владельца продукта: слева та же сетка, только уже, справа колонка карточек
   «название → крупное число → подпись», ровно по высоте сетки.
   ⚠️ Высоту строки задаёт ТОЛЬКО сетка: `contain: size` обнуляет собственную высоту колонки,
   и `align-self: stretch` растягивает её ровно до сетки (раньше пять карточек были выше
   сетки и тянули строку за собой — колонка вылезала ниже расписания). Сетка всегда
   ≥ 18 часов, карточки в неё влезают с запасом; `min-height: auto` у карточек — страховка
   от обрезки. JS-выравнивания (как `fitSideToWeek` у учителя) не нужно.
   Узкая колонка — просьба владельца 22.10.2026 («поуже и поаккуратнее»). */
.ck-main { display: grid; gap: 16px; align-items: start;
  grid-template-columns: minmax(0, 1fr) var(--ck-side); }
.ck-main > .sched-wrap { min-width: 0; }
.ck-side { align-self: stretch; contain: size; min-width: 0;
  display: flex; flex-direction: column; gap: 10px; }
/* `container-type` — чтобы кегль считался от ширины КАРТОЧКИ (`cqi`), а не экрана:
   подпись обязана влезать в одну строку при любой ширине колонки. */
.ck-card { flex: 1 1 0; min-width: 0; container-type: inline-size;
  display: flex; flex-direction: column; justify-content: space-between; gap: 6px;
  padding: 12px 16px; background: var(--surface); border: 1px solid transparent;
  border-radius: var(--radius); box-shadow: var(--card-shadow); }
.ck-title, .ck-val, .ck-sub { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ck-title { font-size: 14px; font-size: clamp(13px, 5.4cqi, 16px); font-weight: 500;
  color: color-mix(in srgb, var(--text) 78%, var(--surface)); letter-spacing: .01em; }
.ck-val { font-size: 24px; font-size: clamp(20px, 9cqi, 28px); font-weight: 600; line-height: 1.1;
  color: color-mix(in srgb, var(--text) 84%, var(--surface)); font-variant-numeric: tabular-nums; }
.ck-val.na { color: var(--muted); }
/* ⚠️ ОДНА строка без переноса (требование владельца продукта). Самая длинная фраза —
   ~42 знака («На 15 уроков меньше, чем на прошлой неделе»), это ~23em: 3.8cqi оставляют
   запас ~10% ширины. Многоточие — только страховка, полный текст в `title`. */
.ck-sub { font-size: 11px; font-size: clamp(10px, 3.8cqi, 12px); color: color-mix(in srgb, var(--text-dim) 80%, var(--surface)); }
.ck-lead.up { color: color-mix(in srgb, var(--grade-good) 80%, var(--surface)); }
.ck-lead.down { color: color-mix(in srgb, var(--grade-low) 80%, var(--surface)); }
.ck-lead.warn { color: color-mix(in srgb, var(--warn) 80%, var(--surface)); }
/* Одна колонка: цифры ПОД сеткой (решение владельца продукта «сначала расписание, потом
   виджеты» — в отличие от главной учителя, где показатели наверху), плитками по ширине.
   Минимум плитки 270px — меньше одна строка подписи не помещается. */
@media (max-width: 1000px) {
  .ck-main { grid-template-columns: minmax(0, 1fr); }
  .ck-side { contain: none; display: grid; gap: 10px;
    grid-template-columns: repeat(auto-fit, minmax(270px, 1fr)); }
  /* В плитках ряд тянется по самой высокой карточке — содержимое к верху, иначе число
     у карточки без подписи («Оплаты») уезжало бы вниз и не стояло на линии с соседями. */
  .ck-card { padding: 12px 14px; justify-content: flex-start; }
}
/* Телефон: пять карточек ДВУМЯ колонками (просьба владельца 28.10.2026: «очень много места
   просто пропадает»). Столбиком они съедали ~1100px высоты, а `auto-fit` с минимумом 270px
   давал вторую колонку только от ~578px экрана. Пятая — одинокая — тянется на обе колонки.
   ⚠️ Подпись здесь МОЖЕТ переноситься на 2–3 строки: правило «строго одна строка» писалось
   для узкой колонки на ПК и в плитках ≥270px, а в плитке ~150px любая фраза («На 20 уроков
   больше, чем за 1–28 сентября») в одну строку не влезет даже мелким кеглем. Название и число
   стоят друг под другом (рядом они в плитку не помещались). */
@media (max-width: 600px) {
  .ck-side { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ck-card { padding: 12px; }
  .ck-card:last-child:nth-child(odd) { grid-column: 1 / -1; }
  .ck-title { font-size: 12px; font-size: clamp(11.5px, 9.6cqi, 13px); }   /* 320px: плитка 115px, «Оплаты за октябрь» при 13px — 124px */
  .ck-val { font-size: 22px; }
  .ck-sub { font-size: 10.5px; line-height: 1.3; white-space: normal; overflow: visible; }
}
/* ── Окна карточек кокпита: «Зарплаты / Оплаты / Уроки за месяц» (modules/cockpit/drilldown.js) ──
   Клик по карточке раскрывает то, из чего сложено число. Только токены темы: светлая тема
   перекрашивается сама. Таблица — сетка из div (а не <table>): под строкой раскрывается панель
   «Детали / Подробнее» во всю ширину, а на телефоне колонки перестраиваются в две строки. */
.ck-card-btn { cursor: pointer; transition: background .15s; }
.ck-card-btn:hover { background: var(--surface-2); }
.ck-card-btn:focus-visible { outline: 2px solid var(--accent-border); outline-offset: 2px; }
.ckd { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.ckd-sum { display: flex; align-items: baseline; gap: 4px 12px; flex-wrap: wrap; }
.ckd-sum-v { font-size: 1.9em; font-weight: 600; line-height: 1.1; font-variant-numeric: tabular-nums; }
.ckd-sum-v.na { color: var(--muted); }
.ckd-sum-k { font-size: .84em; color: var(--text-dim); }
.ckd-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.ckd-pick { flex: 0 1 230px; width: auto; min-width: 0; }
.ckd-search { flex: 1 1 180px; width: auto; min-width: 0; }
/* Таблица прокручивается сама (шапка прилипает), а не уводит окно: раскрытые «Детали» бывают длинными. */
.ckd-table { --ckd-cols: minmax(0, 1fr); display: flex; flex-direction: column;
  max-height: 56vh; overflow-y: auto; border-radius: var(--radius-sm); }
.ckd-cols-pay { --ckd-cols: minmax(0, 1fr) 92px 130px 112px; }
.ckd-cols-paid { --ckd-cols: 116px minmax(0, 1fr) 130px 124px; }
.ckd-cols-load { --ckd-cols: minmax(0, 1.2fr) minmax(0, 1fr) minmax(150px, 1fr); }
.ckd-head, .ckd-row { display: grid; grid-template-columns: var(--ckd-cols); align-items: center;
  gap: 10px; padding: 9px 12px; }
.ckd-head { position: sticky; top: 0; z-index: 1; background: var(--surface); padding-top: 4px;
  padding-bottom: 6px; font-size: .72em; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
.ckd-item:nth-child(even) > .ckd-row { background: color-mix(in srgb, var(--text) 5%, transparent);
  border-radius: var(--radius-sm); }
.ckd-item.open > .ckd-row { border-radius: var(--radius-sm) var(--radius-sm) 0 0; }
.ckd-name { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.ckd-name b { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ckd-name i { font-style: normal; font-size: .82em; color: var(--muted); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.ckd-when { display: flex; gap: 6px; align-items: baseline; white-space: nowrap; font-size: .92em; }
.ckd-when b { font-weight: 600; }
.ckd-when i { font-style: normal; color: var(--muted); font-size: .9em; }
.ckd-muted { min-width: 0; color: var(--text-dim); font-size: .92em; overflow-wrap: anywhere; }
.ckd-num { text-align: right; font-variant-numeric: tabular-nums; color: var(--text-dim); }
.ckd-u { display: none; }
.ckd-sum-cell { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ckd-sum-cell.pos { color: var(--grade-good); }
.ckd-sum-cell.warn { color: var(--warn); font-weight: 500; }
.ckd-act { display: flex; justify-content: flex-end; }
.ckd-toggle { gap: 4px; }
.ckd-chev { display: inline-flex; width: 14px; height: 14px; transition: transform .18s ease; }
.ckd-chev svg { width: 100%; height: 100%; }
.ckd-item.open .ckd-chev { transform: rotate(180deg); }
.ckd-more { padding: 4px 8px 10px; border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  background: color-mix(in srgb, var(--text) 3%, transparent); }
/* Общий спиннер оставляет 60px полей — в раскрывающейся строке это пустой ящик, который потом схлопывается. */
.ckd-more .spinner { margin: 14px auto; }
.ckd-hist-head { margin: 6px 4px 8px; font-size: .8em; color: var(--text-dim); }
.ckd-empty { padding: 22px 4px; text-align: center; }
.ckd-note { margin: 0 2px; font-size: .76em; line-height: 1.5; color: var(--muted); }
.ckd-note.warn { color: var(--warn); }
/* Колонка «Уроков»: число и полоска — чем длиннее, тем больше нагрузка (от самого загруженного в списке). */
.ckd-load { min-width: 0; display: flex; align-items: center; justify-content: flex-end; gap: 10px; }
.ckd-bar { flex: 1 1 auto; height: 6px; border-radius: 99px; overflow: hidden;
  background: color-mix(in srgb, var(--text) 8%, transparent); }
.ckd-bar i { display: block; height: 100%; border-radius: 99px; background: var(--selected); }
.ckd-load b { min-width: 2.3em; text-align: right; font-variant-numeric: tabular-nums; }
.ckd-sort { all: unset; cursor: pointer; display: inline-flex; align-items: center; gap: 4px; }
.ckd-sort.ckd-num { justify-content: flex-end; }
.ckd-sort:hover, .ckd-sort.active { color: var(--text); }
.ckd-sort:focus-visible { outline: 2px solid var(--accent-border); outline-offset: 2px; }
@media (max-width: 620px) {
  /* Телефон: у платёжных таблиц шапки нет — ячейки встают в две строки по две. */
  .ckd-table { max-height: 52vh; }
  /* У платёжных таблиц шапки нет, прилипать нечему — прокручивается сам лист, без второй полосы. */
  .ckd-cols-pay, .ckd-cols-paid { max-height: none; overflow: visible; }
  .ckd-cols-pay .ckd-head, .ckd-cols-paid .ckd-head { display: none; }
  .ckd-cols-pay, .ckd-cols-paid { --ckd-cols: minmax(0, 1fr) auto; }
  .ckd-cols-pay .ckd-row > :nth-child(1) { grid-column: 1; grid-row: 1; }
  .ckd-cols-pay .ckd-row > :nth-child(2) { grid-column: 1; grid-row: 2; text-align: left; font-size: .84em; }
  .ckd-cols-pay .ckd-row > :nth-child(3) { grid-column: 2; grid-row: 1; }
  .ckd-cols-pay .ckd-row > :nth-child(4) { grid-column: 2; grid-row: 2; }
  .ckd-cols-paid .ckd-row > :nth-child(1) { grid-column: 1; grid-row: 2; font-size: .84em; }
  .ckd-cols-paid .ckd-row > :nth-child(2) { grid-column: 1; grid-row: 1; }
  .ckd-cols-paid .ckd-row > :nth-child(3) { grid-column: 2; grid-row: 1; }
  .ckd-cols-paid .ckd-row > :nth-child(4) { grid-column: 2; grid-row: 2; }
  .ckd-u { display: inline; }
  /* Нагрузка: имя, под ним «N учеников» / преподаватель, справа число с полоской. */
  .ckd-cols-load { --ckd-cols: minmax(0, 1fr) minmax(96px, 38%); }
  .ckd-cols-load .ckd-head > :nth-child(2) { display: none; }
  .ckd-cols-load .ckd-row > :nth-child(1) { grid-column: 1; grid-row: 1; }
  .ckd-cols-load .ckd-row > :nth-child(2) { grid-column: 1; grid-row: 2; text-align: left; font-size: .84em; }
  .ckd-cols-load .ckd-row > :nth-child(3) { grid-column: 2; grid-row: 1 / span 2; }
  .ckd-sum-v { font-size: 1.6em; }
}
/* Живые московские часы в шапке расписания */
.sched-sub { display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.msk-clock { display: inline-flex; align-items: center; gap: 6px; color: var(--accent-text); font-variant-numeric: tabular-nums; }
.msk-clock .msk-time { font-weight: 600; letter-spacing: .4px; }
.msk-cap { font-size: .82em; color: var(--text-dim); letter-spacing: .5px; }
.msk-pulse { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); flex: 0 0 auto;
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 50%, transparent); animation: msk-pulse 1.8s ease-out infinite; }
@keyframes msk-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  70% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--accent) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent); }
}
/* ── Главная учителя (расписание) ──────────────────────────────────
   Раскладка: приветствие во всю ширину → две колонки. Слева неделя (панель с часами
   и навигацией + сетка), справа — три показателя дня и лента последних сообщений.
   Ниже 1000px колонки складываются в одну: показатели уходят НАВЕРХ (они важнее сетки),
   сообщения — вниз; ниже 760px показатели встают парой под «Следующим уроком». */
.tdash { display: flex; flex-direction: column; gap: 16px; }
/* Шапка делится на те же колонки, что и главная: «Рабочие часы» встают над сеткой
   расписания, «Виджеты» — над колонкой виджетов, каждая кнопка над своим блоком. */
.tdash-head { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 16px;
  align-items: center; }
.tdash-head-l { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; min-width: 0; }
.tdash-head-r { display: flex; justify-content: flex-end; }
/* Группа «Набор учеников» + «Рабочие часы» — прижата вправо, к краю колонки расписания.
   ⚠️ Общей «полки» (подложка + рамка вокруг всей группы) БОЛЬШЕ НЕТ, как нет и рамки у
   самой кнопки «Рабочие часы» (решение владельца продукта «убраны рамки у кнопки и у
   всей части»): рамка в рамке давала в шапке три вложенных контура подряд. Кнопку от
   фона страницы отделяет теперь только мягкая заливка — та же, что была у полки. */
.tdash-head-acts { flex: 0 0 auto; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.tdash-greet { min-width: 0; }
/* Кнопки шапки: «Рабочие часы» и «Виджеты» */
.tdash-cfg { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  font: inherit; font-size: .82em; padding: 7px 12px; border-radius: 99px;
  background: transparent; border: 1px solid var(--border-2); color: var(--muted);
  transition: color .12s, border-color .12s, background .12s; }
.tdash-cfg:hover { color: var(--accent-text); border-color: var(--accent-border); background: var(--surface); }
.tdash-cfg svg { width: 15px; height: 15px; }
/* Действия рядом с приветствием — без рамки (см. выше). Рамка осталась только у
   шестерёнки «Виджеты»: она стоит особняком, над своей колонкой, и без контура
   потерялась бы в пустоте. */
.tdash-head-acts .tdash-cfg { border-color: transparent;
  background: color-mix(in srgb, var(--surface-2) 70%, transparent); }
.tdash-head-acts .tdash-cfg:hover { border-color: transparent; background: var(--surface-2);
  color: var(--accent-text); }
/* ⚠️ «Виджеты» — ТОЛЬКО значок, без подписи (решение владельца продукта): на ПК подпись
   была единственным текстом в этом углу и перевешивала саму шестерёнку. Имя кнопки живёт
   в `aria-label`/`title` — см. dashHead() в schedule.js. */
.tdash-cfg-ico { padding: 8px; }
.tdash-cfg-ico svg { width: 17px; height: 17px; }
/* ⚠️ Приветствие на 15% мельче прежнего (решение владельца продукта «приветствие чуть
   меньше»): строка во всю ширину экрана и без того самая заметная на странице. */
.tdash-hello { font-size: clamp(1.15rem, 1.8vw, 1.5rem); font-weight: 600; line-height: 1.2;
  letter-spacing: -.02em; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.tdash-wave { font-size: .85em; }
.tdash-date { margin-top: 6px; font-size: .86em; color: var(--muted); }

/* Две колонки: слева неделя, справа ВСЯ колонка виджетов одним узлом `.tdash-side` (одна
   раскладка у ученика и у учителя: studentDash и teacherHero собирают колонку общим
   `composeSide`). Именованные области — чтобы «все виджеты выключены» убирало правую колонку
   одним правилом, не трогая разметку.
   ⚠️ Одна строка грида, а не пара «показатели / лента»: пустая строка нулевой высоты всё равно
   несёт зазор грида (16px), и правая колонка начиналась ниже панели недели (жалоба владельца
   26.10.2026: «домашка не дотягивает»). Колонка обязана стоять вровень с расписанием сверху и
   снизу при ЛЮБОМ порядке виджетов. */
.tdash-main { display: grid; gap: 16px; align-items: start;
  grid-template-columns: minmax(0, 1fr) 300px;
  grid-template-rows: auto;
  grid-template-areas: "week side"; }
/* Все виджеты выключены — правой колонки нет, сетка занимает всю ширину */
.tdash-bare .tdash-main { grid-template-columns: minmax(0, 1fr); grid-template-areas: "week"; }
.tdash-bare .tdash-head { grid-template-columns: minmax(0, 1fr) auto; }
.tdash-week { grid-area: week; min-width: 0; display: flex; flex-direction: column; gap: 12px; }
.tdash-stats { min-width: 0; display: flex; flex-direction: column; gap: 12px; }
/* ⚠️ Правая колонка ВЫРАВНИВАЕТСЯ ПО ВЫСОТЕ РАСПИСАНИЯ (просьба владельца продукта).
   Одной сеткой это не делается: в контейнере АВТО-высоты `1fr` не ужимает строку ниже её
   содержимого — свободного места, которое `fr` распределяет, там просто нет, и лента
   вылезала на два десятка пикселей ниже сетки. Потолок ленте считает JS
   (`fitSideToWeek` в schedule.js) и ставит инлайн `max-height`; здесь — только то, что
   позволяет ленте ужаться: колонка-флекс, обнулённый минимум и своя прокрутка у списка. */
.tdash-msgs { min-width: 0; align-self: stretch;
  display: flex; flex-direction: column; min-height: 0; }
/* ⚠️ `margin: 0 -10px` — список выходит в поля панели ровно на боковой отступ строки
   ПЛЮС её прозрачную рамку (9 + 1): аватарки и время встают под заголовок ленты и под
   ссылку «Все» точно, без пиксельного сдвига, а подложка наведения остаётся шире текста
   (см. `.tmsg-row`). У панели отступ 14px, так что за её край список не вылезает. */
.tdash-msgs .tmsg-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; margin: 0 -10px; }
/* В правой колонке не одна лента, а несколько виджетов (показатели, награды, «следующий
   урок» + «домашка», сообщения — в личном порядке): они лежат в одном узле `.tdash-side`, а
   высоту между ними делит `fitSideToWeek` (лента ужимается, остальные остаются как есть).
   На телефоне узел раскрыт (`display: contents`), см. ниже. */
.tdash-side { grid-area: side; min-width: 0; align-self: stretch;
  display: flex; flex-direction: column; gap: 16px; min-height: 0; }
/* ⚠️ Лента РАСТЯГИВАЕТСЯ до низа колонки (просьба владельца продукта — «виджет чата
   подстраивается по высоте под сетку расписания»): между ней и низом стоят другие виджеты,
   и без flex-grow под лентой оставалась бы пустая треть колонки. Потолок ей всё равно
   считает `fitSideToWeek` — flex-grow не умеет ужимать содержимое. */
.tdash-side > .tdash-msgs { align-self: stretch; flex: 1 1 auto; min-height: 0; }

/* Виджет «Баланс уроков» (макет владельца продукта): заголовок сверху слева, крупное
   число с кнопкой «+» — в правом нижнем углу. Заголовок того же кегля и веса, что у
   соседней карточки алмазов: в ряду 2×2 они читаются парой, и разный вес подписей
   разваливал бы ряд на «карточку» и «плитку». */
.bal-card .tdash-stat-main { height: 100%; }
/* Плашка в ОДНУ строку на ПК (макет владельца продукта): заголовок слева, число с «+»
   справа на той же линии — двухэтажная раскладка давала карточку выше, чем в ней текста.
   ⚠️ Только при живом балансе: на нуле под заголовком стоит пояснение, и ему нужна колонка.
   ⚠️ Только на ПК: в половине ряда 2×2 на телефоне заголовок и число в строку не влезают. */
@media (min-width: 761px) {
  .bal-card:not(.low) .tdash-stat-main { display: flex; flex-direction: row; align-items: center; gap: 12px; }
  .bal-card:not(.low) .bal-card-t { flex: 1 1 auto; }
  .bal-card:not(.low) .bal-card-row { margin-top: 0; padding-top: 0; }
}
.bal-card-t { font-size: 1.02em; font-weight: 600; color: var(--text); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ⚠️ Пояснение ПЕРЕНОСИТСЯ, а не обрезается многоточием (общее правило `.tdash-stat-sub`
   как раз обрезает, и «Пополните, чтобы заниматься д…» переставало быть инструкцией). */
.bal-card-note { font-size: .78em; color: var(--text-dim); line-height: 1.35; }
/* `margin-top: auto` прижимает число к низу — работает там, где карточку растянули по
   высоте соседа (ряд 2×2 на телефоне); на ПК высота по содержимому, и правило холостое. */
.bal-card-row { margin-top: auto; padding-top: 10px; display: flex; align-items: center;
  justify-content: flex-end; gap: 10px; }
/* ⚠️ Число ОБЫЧНЫМ цветом текста, а не акцентом (макет владельца продукта): акцент в
   карточке один — залитая кнопка «+», и второй рядом отбирал бы у неё внимание.
   На нуле число красное: это не «мало», а «дальше заниматься не получится». */
.bal-card .tdash-stat-val { font-size: 1.9em; line-height: 1; color: var(--text); }
.bal-card.low .tdash-stat-val { color: var(--danger); }
/* Здесь «+» — главное действие карточки, а не значок у подписи: крупнее и с заливкой.
   ⚠️ Пара «заливка --badge-accent + глиф --on-accent» — та же, что у метки «обменять на
   урок» (.dmw-flag); наведение через `filter`, потому что производного hover-цвета у
   --badge-accent нет, а --accent-hover считается от ДРУГОГО токена. */
.bal-card .bal-add { width: 32px; height: 32px; border-color: transparent;
  background: var(--badge-accent); color: var(--on-accent); }
.bal-card .bal-add:hover { background: var(--badge-accent); filter: brightness(1.12); }
.bal-card .bal-add svg { width: 17px; height: 17px; }

.tdash-stat { min-width: 0; display: flex; align-items: center; gap: 12px;
  padding: 14px; text-align: left;
  background: var(--surface); border: 1px solid transparent; border-radius: var(--radius);
  box-shadow: var(--card-shadow); }
.tdash-stat-main { flex: 1 1 auto; min-width: 0; }
.tdash-stat-lbl, .tdash-stat-val, .tdash-stat-sub {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tdash-stat-lbl { font-size: .74em; color: var(--muted); letter-spacing: .01em; }
.tdash-stat-val { font-size: 1.3em; font-weight: 700; line-height: 1.25; color: var(--text);
  font-variant-numeric: tabular-nums; margin-top: 1px; }
/* «Следующий урок»: значение — длинная дата, крупный кегль в узкую карточку не влезает */
.tdash-stat-val.sm { font-size: 1.04em; }
.tdash-stat-sub { font-size: .75em; color: var(--text-dim); margin-top: 2px; }
/* Двухстрочное уточнение (время + имя ученика у «Следующего урока») */
.tdash-stat-sub.wrap { white-space: normal; line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
/* Карточка-кнопка («Следующий урок» — клик ведёт в класс): выделена как основное действие */
.tdash-stat-btn { font: inherit; color: inherit; cursor: pointer; align-items: flex-start;
  transition: background .14s, border-color .14s, transform .1s, box-shadow .14s; }
.tdash-stat-btn:hover { background: var(--surface-2); border-color: var(--accent-border);
  box-shadow: 0 8px 20px -12px color-mix(in srgb, var(--accent) 45%, transparent); }
.tdash-stat-btn:active { transform: translateY(1px); }
.tdash-stat-go { flex: 0 0 auto; align-self: flex-start; display: inline-flex; align-items: center; gap: 3px;
  color: var(--muted); transition: color .14s, transform .14s; }
.tdash-stat-go-t { font-size: .74em; white-space: nowrap; }
.tdash-stat-go-i { display: inline-flex; }
.tdash-stat-go svg { width: 16px; height: 16px; }
.tdash-stat-btn:hover .tdash-stat-go { color: var(--accent-text); transform: translateX(2px); }

/* «Следующий урок» + «Домашка» — две небольшие плитки в ОДНОМ ряду колонки виджетов ученика
   (макет владельца продукта). Собирает пару `build()` в studentDash; одиночная плитка (второй
   виджет выключен или сбойнул — `tdash-pair-solo`) занимает всю ширину.
   ⚠️ `minmax(0, 1fr)`, а не `1fr`: у «Следующего урока» длинное значение («через 1 ч 34 мин»)
   иначе раздвинуло бы свою колонку и сжало «Домашку». Значение здесь ПЕРЕНОСИТСЯ (в
   половинной плитке в одну строку оно не помещается), а «В класс ›» убрана — карточка и так
   целиком кнопка, а 16px под стрелку отнимали бы у текста. */
.tdash-pair { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; }
.tdash-pair.tdash-pair-solo { grid-template-columns: minmax(0, 1fr); }
.tdash-pair > .tdash-stat { align-items: stretch; }
.tdash-pair .tdash-stat-go { display: none; }
.tdash-pair .tdash-stat-val { white-space: normal; line-height: 1.2; text-wrap: balance; }
/* «Следующий урок» в паре повторяет анатомию «Домашки» (макет владельца продукта): подпись —
   сверху, вровень с верхом кружка-счётчика, а значение с уточнением прижаты КНИЗУ, и последняя
   строка уточнения стоит на одной базовой линии со словом «Домашка».
   ⚠️ Поправки в `em` сняты замером на Inter, а не подобраны на глаз: у подписи над буквами
   половина интерлиньяжа (line-height 1.5) — без −.36em верх «С» на 4px ниже кружка; у уточнения
   (.75em при 1.35) нижний полуинтерлиньяж меньше, чем у «Домашки» (1.12em при 1.5), — без
   .265em оно садится на 3px ниже. В `em` — чтобы держались при любом кегле страницы. */
.tdash-pair .tdash-stat-main { display: flex; flex-direction: column; }
.tdash-pair .tdash-stat-main > .tdash-stat-lbl { margin: -.36em 0 6px; }
.tdash-pair .tdash-stat-main > .tdash-stat-val { margin-top: auto; }
.tdash-pair .tdash-stat-main > .tdash-stat-sub { margin-bottom: .265em; }

/* Плитка «Домашка»: серый кружок со счётчиком слева сверху, подпись справа снизу. Кружок —
   тот же размер, что у залитой «+» в карточке баланса (32px, макет владельца), но СЕРЫЙ: акцент
   в колонке один — «+», и второй рядом отбирал бы у неё внимание. Серый считается от
   `--text`, поэтому светлая тема получает свой оттенок сама. */
.hwh-card { flex-direction: column; align-items: stretch; justify-content: space-between; gap: 10px;
  min-height: 86px; }
.hwh-count { flex: 0 0 auto; width: 32px; height: 32px; border-radius: 50%;
  display: inline-grid; place-items: center; font-size: .95em; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--text);
  background: color-mix(in srgb, var(--text) 18%, var(--surface)); }
.hwh-card .hwh-count { align-self: flex-start; }
.hwh-title { align-self: flex-end; font-size: 1.12em; font-weight: 700; color: var(--text); }
/* Окно «Домашка»: классы ученика, у каждого свой счётчик */
.hwh-hint { font-size: .85em; color: var(--text-dim); margin-bottom: 12px; }
.hwh-list { display: flex; flex-direction: column; gap: 8px; }
.hwh-row { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  font: inherit; color: inherit; cursor: pointer; padding: 10px 12px;
  background: var(--surface-2); border: 1px solid transparent; border-radius: var(--radius-sm);
  transition: border-color .14s, background .14s; }
.hwh-row:hover { border-color: var(--accent-border); }
.hwh-row.locked { opacity: .75; }
.avatar.hwh-av { flex: 0 0 auto; width: 44px; height: 44px; font-size: 1.1em; border-radius: 14px;
  object-fit: cover; }
.hwh-row-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.hwh-row-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hwh-row-sub { font-size: .78em; color: var(--muted); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
/* Нет невыполненного — кружок пустой, одним контуром: «ноль» не должен спорить с классом, где
   домашка ждёт. */
.hwh-count.none { background: transparent; color: var(--muted);
  box-shadow: inset 0 0 0 1px var(--border-2); }
.hwh-lock { flex: 0 0 auto; display: inline-flex; color: var(--muted); }
.hwh-lock svg { width: 18px; height: 18px; }
.hwh-empty { color: var(--muted); text-align: center; padding: 18px 0; }
@media (max-width: 340px) { .tdash-pair { grid-template-columns: minmax(0, 1fr); } }

/* Панель недели над сеткой: часы слева, перелистывание справа.
   flex-wrap — чтобы на узком экране навигация уезжала на вторую строку целиком,
   а не сжимала метку недели до переноса по словам. */
/* ⚠️ Без рамки и с overflow:hidden (было: `border: 1px solid transparent`): у прежнего
   варианта на некоторых экранах у скруглённого угла был виден тонкий тёмный шов — стык
   отдельно антиалиased фона и «прозрачной» рамки редко, но рендерился не пиксель в
   пиксель. Рамка нигде не перекрашивается (нет :hover/:active), поэтому не нужна вовсе;
   overflow:hidden заставляет браузer обрезать заливку строго по скруглению одним
   проходом — шва при этом не остаётся физически.
   ⚠️ Заливка РОВНАЯ, без градиента (решение владельца продукта): панель — такая же
   карточка, как сетка и виджеты, и с той же тенью; акцентный перелив слева делал её
   единственным «цветным» блоком страницы. Тень `overflow:hidden` не режет — она снаружи. */
.sched-toolbar { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; row-gap: 8px;
  padding: 10px 14px; border-radius: var(--radius); overflow: hidden;
  background: var(--surface); box-shadow: var(--card-shadow); }
/* Панель ученика СОЕДИНЕНА с сеткой в одну карточку (макет владельца продукта 26.10.2026):
   у панели прямой низ, она продолжается под кнопками ещё на ~10px и упирается прямо в шапку
   дней — зазора между ними нет. У сетки остаются СВОИ скруглённые верхние углы, а в «карманах»
   этих углов видна заливка панели — так и нарисовано на макете.
   ⚠️ Панель НЕ просто прижата к сетке, а заходит под неё на высоту скругления: нижний край
   панели прячется под сеткой (сетка идёт в разметке позже и рисуется поверх), а торчит из-под
   неё только заливка в углах. Прижми панель встык — в углах остались бы просветы фона.
   `padding-bottom` несёт и этот скрытый запас (радиус), и видимые 15px; `margin-bottom`
   гасит зазор колонки (12px у .tdash-week) и тянет сетку наверх на тот же радиус.
   ⚠️ Строка «время · Мои окна · неделя» стоит РОВНО по центру видимой панели (просьба
   владельца 26.10.2026: «чуть пониже»): сверху 15px и снизу до шапки дней тоже 15px. Раньше
   было 10 и 20 — строка жалась к верху, потому что низ панели длиннее на высоту «хвоста». Общая
   высота панели не изменилась (10 + 20 = 15 + 15). */
.sched-toolbar.sched-join { border-bottom-left-radius: 0; border-bottom-right-radius: 0;
  padding-top: 15px; padding-bottom: calc(15px + var(--radius));
  margin-bottom: calc(-12px - var(--radius)); }
/* Левая часть панели ученика: часы и «Мои окна для учителей» сразу за ними (макет владельца
   продукта). Навигация по неделям остаётся справа — `space-between` у самой панели. */
.sched-tb-l { display: flex; align-items: center; gap: 12px; min-width: 0; }
/* Кнопка — обычная `.btn.btn-sm`, как стрелки недели: высота и цвет те же, своё только
   значок и подпись в одну строку. */
.sched-hours { flex: 0 0 auto; white-space: nowrap; }

/* Лента последних сообщений */
.tdash-msgs { background: var(--surface); border: 1px solid transparent;
  border-radius: var(--radius); padding: 14px; box-shadow: var(--card-shadow); }
.tmsg-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.tmsg-title { font-size: 1.02em; font-weight: 600; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tmsg-unread { flex: 0 0 auto; padding: 1px 7px; border-radius: 99px; font-size: .68em; font-weight: 700;
  background: var(--btn-primary); color: var(--on-btn-primary); }
/* ⚠️ Своей кнопки-пилюли у «Все» БОЛЬШЕ НЕТ (решение владельца продукта «убрать рамки»):
   это `.tdash-link` — та же ссылка-действие со стрелкой, что «История ›» и «Классы ›» в
   карточках рядом (разметку даёт общий `linkAside`, см. schedule.js). Здесь остаётся
   только место в строке. */
.tmsg-all { margin-left: auto; }
.tmsg-list { display: flex; flex-direction: column; gap: 4px; }
/* ⚠️ Аватарки стоят ровно под буквой «П» заголовка «Последние сообщения» (решение
   владельца продукта). Отступ строки при этом НЕ снят, а ВЫНЕСЕН НАРУЖУ: сам список
   сдвинут на те же 9px в поля панели (см. `.tdash-msgs .tmsg-list` выше) — так текст
   встаёт под заголовок, а подложка наведения остаётся шире текста, а не режет его
   впритык. Сдвигать приходится СПИСОК, а не строки: у списка своя прокрутка, и
   отрицательные поля внутри него обрезались бы его же переполнением. */
.tmsg-row { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  font: inherit; color: inherit; cursor: pointer; padding: 8px 9px;
  background: transparent; border: 1px solid transparent; border-radius: var(--radius-sm);
  transition: background .14s, border-color .14s; }
.tmsg-row:hover { background: var(--surface-2); border-color: var(--accent-border); }
.tmsg-av { flex: 0 0 auto; width: 32px; height: 32px; font-size: .82em; }
.tmsg-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.tmsg-top { display: flex; align-items: baseline; gap: 8px; }
.tmsg-name { flex: 1 1 auto; min-width: 0; font-size: .9em; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tmsg-when { flex: 0 0 auto; font-size: .72em; color: var(--muted); font-variant-numeric: tabular-nums; }
.tmsg-text { font-size: .8em; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Непрочитанное: имя ярче, текст обычным цветом — строка «звучит» громче остальных */
.tmsg-row.is-unread .tmsg-name { color: var(--accent-text); }
.tmsg-row.is-unread .tmsg-text { color: var(--text-dim); }
.tmsg-badge { flex: 0 0 auto; min-width: 19px; padding: 1px 6px; border-radius: 99px;
  font-size: .68em; font-weight: 700; text-align: center;
  background: var(--btn-primary); color: var(--on-btn-primary); }
/* Пустая лента в вытянутой панели центрируется по остатку высоты — иначе заглушка
   жалась бы к заголовку, а под ней зияла бы половина колонки. */
/* Пустая лента в вытянутой панели центрируется по остатку высоты — иначе заглушка
   жалась бы к заголовку, а под ней зияла бы половина колонки. ⚠️ `min-height: 0`
   обязателен: у флекс-элемента минимум по умолчанию равен min-content, и заглушка
   не давала панели ужаться до высоты расписания (низ колонки уезжал на 22px ниже). */
.tmsg-empty { text-align: center; padding: 22px 10px; color: var(--muted);
  flex: 1 1 auto; min-height: 0; overflow: hidden;
  display: flex; flex-direction: column; justify-content: center; }
.tmsg-empty-t { font-size: .92em; color: var(--text-dim); }
.tmsg-empty-s { font-size: .78em; margin-top: 4px; line-height: 1.5; }

/* ── Виджеты главной УЧЕНИКА ───────────────────────────────────────
   ⚠️ Своей РАСКЛАДКИ (`.sdash`, `.sdash-main`, `.sdash-week`, `.sdash-next`, `.sdash-side`)
   у главной ученика больше НЕТ: она была копией учительской `.tdash-*`, и каждую правку
   приходилось делать дважды — а половину из них (выравнивание виджетов по высоте сетки,
   приветствие, шестерёнка настроек) у ученика просто забывали. Теперь обе роли рисуются
   на `.tdash`, различаются только действия в шапке и набор виджетов.
   Остались классы САМИХ карточек: их рисуют виджеты алмазов и наград. */

/* Общий вид виджета-карточки (алмазы, награды) — тот же, что у ленты сообщений. */
.sdash-card { background: var(--surface); border: 1px solid transparent;
  border-radius: var(--radius); padding: 14px; box-shadow: var(--card-shadow); }
.sdash-card-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.sdash-card-t { font-size: 1.02em; font-weight: 600; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ⚠️ Ссылка «слово + стрелка» БЕЗ рамки-таблетки (макет владельца продукта) — тот же вид,
   что у «История ›» (.tdash-link) и «Все ›» в ленте сообщений. */
.sdash-card-all { margin-left: auto; flex: 0 0 auto; display: inline-flex; align-items: center;
  gap: 2px; font: inherit; font-size: .78em; cursor: pointer; padding: 0; border: 0;
  background: none; color: var(--muted); white-space: nowrap;
  transition: color .14s, transform .14s; }
.sdash-card-all:hover { color: var(--accent-text); transform: translateX(1px); }
.sdash-card-all-i { display: inline-flex; }
.sdash-card-all svg { width: 15px; height: 15px; }

/* Виджет «Мои алмазы»: счёт внутри кольца прогресса до бесплатного урока.
   ⚠️ Кольцо, а не полоса: у полосы уже есть своё место — окно кошелька, которое эта
   карточка и открывает; кольцо со счётом в центре читается как один предмет. */
.dmw-main { display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  font: inherit; color: inherit; cursor: pointer; padding: 2px; background: none; border: 0;
  border-radius: var(--radius-sm); transition: transform .1s; }
.dmw-main:active { transform: translateY(1px); }
.dmw-dial { position: relative; flex: 0 0 auto; width: 84px; height: 84px;
  display: inline-flex; align-items: center; justify-content: center; }
.dmw-ring { position: absolute; inset: 0; }
/* Поворот на −90°: заполнение начинается сверху, с 12 часов, а не справа. */
.dmw-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.dmw-ring circle { fill: none; stroke-width: 7; stroke-linecap: round; }
.dmw-track { stroke: var(--border-2); }
.dmw-fill { stroke: var(--diamond-ring); transition: stroke-dasharray .45s ease; }
/* Накопилось на урок — кольцо красится цветом основного действия: это уже не «прогресс»,
   а «можно нажать». */
.dmw.is-ready .dmw-fill { stroke: var(--btn-primary); }
[data-theme="light"] .dmw.is-ready .dmw-fill { stroke: var(--diamond-ring); }
.dmw-face { position: relative; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.dmw-face-ico { width: 15px; height: 15px; }
.dmw-num { font-size: 1.45em; font-weight: 700; line-height: 1;
  color: var(--badge-accent); font-variant-numeric: tabular-nums; }
.dmw-side { min-width: 0; display: flex; flex-direction: column; gap: 7px; }
.dmw-note { font-size: .8em; color: var(--text-dim); line-height: 1.4; }
.dmw-sub { font-size: .72em; color: var(--muted); }
.dmw-flag { align-self: flex-start; padding: 3px 9px; border-radius: 99px; font-size: .68em;
  font-weight: 700; letter-spacing: .02em;
  background: var(--btn-primary); color: var(--on-btn-primary); }

/* Четыре награды в колонке виджетов: ширина плитки — ЧЕТВЕРТЬ карточки при любом их числе.
   ⚠️ Не `flex: 1 1 0`: одна награда растягивалась на всю ширину, квадратная сцена давала
   карточку в полэкрана высотой, и правая колонка вылезала ниже расписания — лента
   сообщений ужимается `fitSideToWeek` только до своего минимума. Пустое состояние и
   спиннер занимают всю строку. */
.rw-mini { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; }
.rw-mini > :not(.rw-tile) { grid-column: 1 / -1; }
/* ⚠️ Карточка ровно той же высоты, что «Мои алмазы» над ней (макет владельца: «такая же по
   высоте»): на 1440px замерено 152px у обеих. Зазор под шапкой уменьшен на 1px именно под
   это — при правке плитки или кегля подписи высоту сверить заново. */
.rw-widget .sdash-card-head { margin-bottom: 9px; }
/* Подпись под медалью — ТОНКАЯ и мелкая (просьба владельца продукта): четыре награды в ряд
   с жирным .78em-шрифтом не помещаются, и названия рвались бы по буквам. */
.rw-mini .rw-tile-name { font-size: .6em; font-weight: 400; line-height: 1.2; color: var(--text-dim); }
/* Метка «НОВАЯ» — мельче и у самого края медали: в плитке в четверть карточки обычная
   перекрывала бы половину модели. */
.rw-mini .rw-tile-badge { padding: 1px 5px; font-size: .52em; }
/* Страница «Все награды» (#/rewards): сетка во всю ширину. */
.rw-grid { display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }

@media (max-width: 760px) {
  .rw-mini { gap: 6px; }
  .dmw-dial { width: 74px; height: 74px; }
}

/* Что показывать только на телефоне: на ПК тумблер и «Рабочие часы» стоят в шапке сами.
   ⚠️ Обёртка выноски (`.wh-nudge-wrap`) объявлена НИЖЕ по файлу со своим `display`, поэтому
   для неё нужен составной селектор — иначе при равной специфичности выигрывает она. */
.tdash-mob, .wh-nudge-wrap.tdash-mob { display: none; }

/* Строка-переход в листе настроек («Рабочие часы ›») — рядом со строками-тумблерами */
.hs-nav { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  font: inherit; color: inherit; cursor: pointer; background: transparent; border: 0;
  border-bottom: 1px solid var(--border); }
.hs-nav:hover .hs-go { color: var(--accent-text); transform: translateX(2px); }
.hs-go { flex: 0 0 auto; display: inline-flex; color: var(--muted); transition: color .14s, transform .14s; }
.hs-go svg { width: 17px; height: 17px; }

/* Тумблер «Набор учеников» в панели недели */
.sched-recruit { display: inline-flex; align-items: center; gap: 9px; cursor: pointer;
  font-size: .84em; color: var(--text-dim); }
.sched-recruit .switch { width: 38px; height: 22px; }
.sched-recruit .slider::before { width: 16px; height: 16px; }
.sched-recruit input:checked + .slider::before { transform: translate(16px, -50%); }

/* Разметка рабочих часов: пустая недельная сетка, клик по ячейке = час рабочий */
.wh-bar { display: flex; flex-direction: column; gap: 10px;
  padding: 16px 18px; border: 1px solid transparent; border-radius: var(--radius);
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--grade-good) 9%, var(--surface)), var(--surface) 62%); }
.wh-bar-top { display: flex; align-items: center; justify-content: space-between;
  gap: 12px 16px; flex-wrap: wrap; }
.wh-heading { display: flex; align-items: center; gap: 10px; min-width: 0; }
.wh-ico { flex: 0 0 auto; width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center;
  background: color-mix(in srgb, var(--grade-good) 16%, transparent); color: var(--grade-good); }
.wh-ico svg { width: 17px; height: 17px; }
.wh-title { font-size: 1.06em; font-weight: 700; }
.wh-bar-acts { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; }
.wh-count { display: inline-flex; align-items: center; gap: 5px; padding: 5px 11px 5px 8px;
  border-radius: 99px; font-size: .78em; font-weight: 600; white-space: nowrap;
  font-variant-numeric: tabular-nums; color: var(--grade-good);
  background: color-mix(in srgb, var(--grade-good) 14%, transparent); }
.wh-count.empty { color: var(--muted); background: var(--surface-2); }
.wh-count-ico { display: inline-flex; }
.wh-count-ico svg { width: 13px; height: 13px; }
.wh-sub { font-size: .82em; color: var(--text-dim); line-height: 1.55; max-width: 70ch; }
/* Что означает отметка часа — тёплый callout с левой полосой, без крикливого оранжевого текста */
.wh-note { font-size: .8em; line-height: 1.55; color: var(--text-dim); max-width: 82ch;
  padding: 9px 12px; border-radius: 10px;
  background: color-mix(in srgb, var(--warn) 8%, transparent);
  border-left: 3px solid color-mix(in srgb, var(--warn) 55%, transparent); }
.wh-note b { color: var(--text); font-weight: 600; }
.wh-mode .sched-cell { cursor: pointer; min-height: 34px; transition: background .12s; }
.wh-mode .sched-cell:hover { background: color-mix(in srgb, var(--grade-good) 8%, var(--surface-2)); }
/* ⚠️ Рабочие часы — ВСЕГДА зелёные, независимо от палитры школы (как индикация оценки):
   «отмечен = доступен» — сигнальный цвет, а не элемент оформления. Поэтому --grade-good,
   а не --accent. Ровная заливка без рамки у каждой ячейки — смежные часы читаются одним
   блоком, а не россыпью наклеек (их делят только тонкие линии сетки). */
.wh-cell.wh-on { background: var(--work-fill); }
.wh-cell.wh-on:hover { background: var(--work-fill-hover); }
.wh-daylbl, .wh-rowlbl { cursor: pointer; user-select: none; transition: color .12s; }
.wh-daylbl:hover, .wh-rowlbl:hover { color: var(--grade-good); }
/* Рабочие часы учителя в обычной сетке — зелёная подсветка (остальное время не запрещено).
   ⚠️ Тон — ТОТ ЖЕ токен, что у отмеченной ячейки в редакторе часов (`--work-fill`), а не
   вдвое слабее: на 10% поверх диагональной штриховки прошедшего времени зелёный уходил в серо-
   зелёную грязь, и владелец продукта не узнавал в ней свои отмеченные часы. Один и тот же
   час обязан выглядеть одинаково там, где его отмечают, и там, где его смотрят. */
.sched-cell.work { background-color: var(--work-fill); }
.sched-cell.work:hover { background-color: var(--work-fill-hover); }
.win-legend { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 14px;
  font-size: .8em; line-height: 1.4; color: var(--text-dim); }
.win-legend-i { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
.win-legend-sw { flex: none; width: 22px; height: 10px; border-radius: 4px; }
.win-legend-sw.work { background: var(--win-teacher); }
.win-legend-sw.stu-win { background: var(--win-student); }
.win-legend-sw.work.stu-win { background: var(--win-both); }
/* Окна УЧЕНИКА — серые, а не зелёные (решение владельца продукта): на его главной зелёным
   рисуется и проведённый урок, и два зелёных рядом путались. Переопределяем сам токен в
   обёртке главной ученика и его редактора окон (`.stu-own`), поэтому клетки, легенда и
   редактор меняются разом. От --text — значит, светлая тема получает свой серый сама.
   ⚠️ Окна ПРЕПОДАВАТЕЛЯ (страница класса, запись на пробный) остаются зелёными. */
.stu-own { --work-fill: color-mix(in srgb, var(--text) 13%, transparent);
  --work-fill-hover: color-mix(in srgb, var(--text) 19%, transparent); }
/* Легенда под недельной сеткой на главной ученика — ПОДВАЛ той же карточки: у сетки
   снимаются нижние скругления, отступ колонки (gap 12px у .tdash-week) гасится, линия
   между ними — как между строками сетки. Своя тень обязательна: подвал рисуется поверх
   тени сетки, и без неё карточка «обрывалась» бы на последнем часе.
   Образцы — ТЕ ЖЕ заливки, что у клеток и чипов (см. studentLegend в schedule.js). */
.sched-wrap.with-legend { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.sched-wrap.with-legend .sched-today-overlay > span.edge-end { border-bottom-right-radius: 0; }
.sched-legend { margin-top: -12px; padding: 10px 16px 11px;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 20px;
  background: var(--surface); border-top: 1px solid var(--sched-grid);
  border-radius: 0 0 var(--radius) var(--radius);
  /* Рамка сетки продолжается вокруг подвала — слева, справа и снизу, БЕЗ верха: там
     линия-разделитель, а нижний край кольца сетки подвал перекрывает своим фоном.
     Три сдвинутые копии формы вместо кольца — у кольца сторону не выключить. */
  box-shadow: -1px 0 0 0 var(--sched-frame-line), 1px 0 0 0 var(--sched-frame-line),
    0 1px 0 0 var(--sched-frame-line), var(--card-shadow);
  font-size: .72em; line-height: 1.3; color: var(--text-dim); }
.sched-legend-i { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.sched-legend-sw { flex: none; width: 16px; height: 9px; border-radius: 3px; }
.sched-legend-sw.work { background: var(--work-fill); }
.sched-legend-sw.upcoming { background: var(--lesson-upcoming); }
.sched-legend-sw.completed { background: var(--lesson-done); }
.sched-legend-sw.noshow { background: var(--lesson-noshow); }
.sched-legend-sw.nobalance { background: var(--lesson-nobal); }
.sched-legend-sw.demo { background: var(--lesson-demo); }
/* Телефон: четыре постоянных пункта («Ваши окна», «Предстоящие», «Проведённые», «Неявки») стоят
   в ОДНУ строку по центру (решение владельца продукта — две строки выглядели небрежно). Размер
   шрифта плывёт с шириной экрана (clamp по vw), а отступы и образец заданы в `em` — вся строка
   сжимается целиком и не вылезает ни на 320, ни на 360, ни на 390. «Не хватило баланса» и
   «Пробный урок» появляются не всегда; с ними строка честно переносится (flex-wrap остаётся),
   по центру. */
@media (max-width: 760px) {
  .sched-legend { padding: 8px 8px 9px; gap: .5em 1em; font-size: clamp(8px, 2.65vw, 10px); }
  .sched-legend-i { gap: .4em; }
  .sched-legend-sw { width: 1.1em; height: .75em; }
}
/* Ученик на странице класса: зелёная клетка — свободное окно преподавателя, по клику
   ставится урок. Всё остальное у него НЕ кликается — и курсор, и подсветка под мышью это
   говорят: клетка «с рукой», которая ничего не делает, читается как поломка. */
.sched-cell.no-pick { cursor: default; }
.sched-cell.no-pick:not(.fill):hover { background-color: transparent; }
.sched-cell.no-pick.fill:hover { background-color: color-mix(in srgb, var(--text) 1%, transparent); }

/* ── Сетка расписания в лобби класса — «таблетками», как в квизе ────────────────────────
   Решение владельца продукта: стиль сетки «Когда вам удобно заниматься?» (`.slots-cell`)
   перенесён сюда. Разметка прежняя (`.sched-row` / `.sched-cell`), меняется только вид:
   вместо линий — зазоры, каждая клетка — скруглённая плашка.
   ⚠️ Цвет клетки — переменными (`--cell`, `--cell-hover`, `--win-img`, `--hatch`), а не
   отдельным `background` на каждое состояние: состояния сочетаются (окно × прошедший час ×
   «не кликается»), и правила с равной специфичностью перебивали бы друг друга по порядку
   в файле. Состояние задаёт переменную, рисует одно правило.
   ⚠️ Штриховка прошедшего часа и переход «совпавшего» окна — ОБА фоновые картинки, поэтому
   лежат двумя слоями (штрихи сверху, у каждого слоя свой размер): раньше диагональ у
   прошедшего часа не рисовалась вовсе, чтобы не стереть штрихи. */
.cd-sched .sched-grid { --gap: 4px; padding-bottom: var(--gap); }
.cd-sched .sched-grid .sched-row { column-gap: var(--gap); padding-right: var(--gap); }
.cd-sched .sched-grid .sched-row:not(.sched-head) { border-top: 0; margin-top: var(--gap); }
.cd-sched .sched-grid .sched-day { border-right: 0; }
.cd-sched .sched-grid .sched-time { border-right: 0; display: flex; align-items: center;
  justify-content: flex-end; padding-top: 0; padding-bottom: 0; }
/* Рамка сегодняшнего дня — те же колонки и зазоры, что у строк; сама рамка шире колонки
   на ползазора с каждой стороны, иначе линия легла бы вплотную к краям плашек. */
.cd-sched .sched-today-overlay { column-gap: var(--gap); padding-right: var(--gap); }
.cd-sched .sched-today-overlay > span { margin: 0 calc(var(--gap) / -2); }
.cd-sched .sched-grid .sched-cell {
  --cell: color-mix(in srgb, var(--text) 7%, transparent);
  --cell-hover: color-mix(in srgb, var(--text) 13%, transparent);
  --win-img: none; --hatch: none;
  --hatch-ink: color-mix(in srgb, var(--text) 13%, transparent);
  border-right: 0; border-radius: 9px; padding: 0; gap: 1px; overflow: hidden;
  background-color: var(--cell);
  background-image: var(--hatch), var(--win-img);
  background-repeat: no-repeat; background-position: left center;
  background-size: calc(var(--fill, 1) * 100%) 100%, 100% 100%; }
.cd-sched .sched-grid .sched-cell.work { --cell: var(--win-teacher);
  --cell-hover: color-mix(in srgb, var(--win-teacher) 84%, var(--text)); }
.cd-sched .sched-grid .sched-cell.stu-win { --cell: var(--win-student);
  --cell-hover: color-mix(in srgb, var(--win-student) 84%, var(--text)); }
.cd-sched .sched-grid .sched-cell.work.stu-win { --win-img: var(--win-both); }
/* Прошедший час — штрихи ВНУТРИ плашки, слева направо по доле истёкшего времени (--fill).
   На залитом окне тон штриха — от фона страницы: светлый штрих на оранжевом не виден. */
.cd-sched .sched-grid .sched-cell.fill { --hatch: repeating-linear-gradient(45deg,
    var(--hatch-ink) 0, var(--hatch-ink) 1.5px, transparent 1.5px, transparent 7px); }
.cd-sched .sched-grid .sched-cell.fill.work,
.cd-sched .sched-grid .sched-cell.fill.stu-win { --hatch-ink: color-mix(in srgb, var(--bg) 45%, transparent); }
/* На пастельных окнах светлой темы светлый штрих не виден — там он тёмный и тонкий. */
[data-theme="light"] .cd-sched .sched-grid .sched-cell.fill.work,
[data-theme="light"] .cd-sched .sched-grid .sched-cell.fill.stu-win { --hatch-ink: color-mix(in srgb, var(--text) 14%, transparent); }
.cd-sched .sched-grid .sched-cell:hover { background-color: var(--cell-hover); }
.cd-sched .sched-grid .sched-cell.work.stu-win:hover { filter: brightness(1.1); }
.cd-sched .sched-grid .sched-cell.no-pick:hover,
.cd-sched .sched-grid .sched-cell.past:hover { background-color: var(--cell); filter: none; }
/* Урок занимает плашку целиком и повторяет её скругление (обрезает сама клетка). */
.cd-sched .sched-cell .lesson-chip { flex: 1 1 0; border-radius: 0; }
.cd-sched .sched-cell .lesson-chip:hover { transform: none; box-shadow: none; }
/* Сдвинутый чип выходит за плашку — обрезки клетки больше нет, скругляется сам. */
.cd-sched .sched-cell .lesson-chip.off { --chip-step: calc(100% + var(--gap)); border-radius: 9px; }
.cd-sched .sched-grid .sched-cell:has(> .lesson-chip.off) { overflow: visible; }
@media (max-width: 760px) {
  .cd-sched .sched-grid { --gap: 3px; }
  .cd-sched .sched-grid .sched-cell { border-radius: 6px; }
  .cd-sched .sched-cell .lesson-chip.off { border-radius: 6px; }
}

/* Пояснение ученику над сеткой класса. Фон нейтральный, цвета окон — легендой внутри. */
.cd-book-note { display: flex; gap: 12px; align-items: flex-start; margin-top: 14px;
  padding: 12px 14px; border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--text) 4%, transparent); }
/* Образцы цветов — легендой ВНУТРИ пояснения (.win-legend), те же токены, что у клеток. */
.cd-book-note .win-legend { margin-top: 9px; font-size: .92em; }
.cd-book-text { flex: 1; min-width: 0; font-size: .84em; line-height: 1.5; color: var(--text); }
.cd-book-sub { margin-top: 4px; font-size: .92em; color: var(--muted); }
/* Попап «Поставить урок»: строки — зеброй попапа урока, но без «кармана» под кнопку
   «Открыть класс» (её здесь нет); выбор «Разовый / Постоянный» — на всю ширину. */
.cd-book .lesson-pop .lp-row:last-child { padding-bottom: 9px; }
.cd-book-kind { display: flex; flex-wrap: nowrap; width: 100%; margin: 16px 0 10px; }
.cd-book-kind .seg-btn { flex: 1; text-align: center; }
.cd-book > .cd-book-sub { margin-top: 10px; font-size: .8em; }

/* Попап клетки на главной ученика (макет владельца продукта): две карточки — «Свободное
   окно» с тумблером и раскрывающееся «Поставить урок» со строками классов. Строки темнее
   карточки, как в макете: это список ВНУТРИ шага, а не третья карточка рядом. */
.ss-pop { display: flex; flex-direction: column; gap: 12px; }
.ss-card { background: var(--surface-2); border-radius: var(--radius); padding: 14px 16px; }
.ss-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; cursor: pointer; }
.ss-head { width: 100%; padding: 0; border: 0; background: none; color: inherit;
  font: inherit; text-align: left; }
.ss-title { font-size: 1.06em; font-weight: 600; color: var(--text); }
/* ⚠️ Кегль подобран ЗАМЕРОМ под модалку 460px: «Учитель сможет ставить и переносить занятия
   на это время» встаёт в одну строку (~357 из 370px; при .78em уже переносилось) — просьба
   владельца продукта. На телефоне переносится, и это нормально. */
.ss-sub { margin-top: 6px; font-size: .75em; line-height: 1.45; color: var(--text-dim); }
.ss-note { margin-top: 4px; font-size: .72em; line-height: 1.45; color: var(--muted); }
.ss-chev { flex: none; width: 0; height: 0; border-left: 7px solid transparent;
  border-right: 7px solid transparent; border-top: 8px solid var(--text-dim); }
.ss-card.open .ss-chev { transform: rotate(180deg); }
.ss-card:not(.open) .ss-list, .ss-card.open > .ss-sub { display: none; }
.ss-list { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.ss-item { border-radius: var(--radius-sm); background: var(--surface); overflow: hidden; }
.ss-cls { width: 100%; display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; padding: 12px 14px; border: 0; background: none; color: inherit; font: inherit;
  text-align: left; cursor: pointer; transition: background .12s; }
.ss-cls:hover { background: color-mix(in srgb, var(--text) 4%, transparent); }
.ss-cls-main { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.ss-cls-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ss-cls-lang { font-size: .8em; color: var(--muted); }
.ss-cls-side { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
.ss-cls-who { display: inline-flex; align-items: center; gap: 8px; font-size: .88em; }
.avatar.ss-av { width: 26px; height: 26px; font-size: .74em; }
/* «доступно / закрыто» — сигнальные цвета оценки (вне палитры школы), как в макете. */
.ss-st { font-size: .8em; font-weight: 600; white-space: nowrap; }
.ss-st.ok { color: var(--grade-good); }
.ss-st.no { color: var(--grade-low); }
.ss-more { padding: 10px 14px 14px; border-top: 1px solid var(--border); }
.ss-more-t { font-size: .84em; line-height: 1.45; color: var(--text-dim); }
.ss-acts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
/* Пара «разово / еженедельно» помещается в ряд в модалке 460px (как в макете); на узком
   экране переносится в столбик сама, без медиазапроса. */
.ss-pick { flex: 1 1 auto; border-radius: 99px; padding: 9px 12px; font-size: .86em; }
.ss-more-row { display: flex; align-items: center; gap: 12px; }
.ss-more-row .ss-more-t { flex: 1 1 auto; min-width: 0; }
.ss-more-row .ss-pick { flex: none; }
.ss-wait { display: flex; justify-content: center; padding: 8px 0; }
.ss-empty-t { font-size: .84em; line-height: 1.45; color: var(--text-dim); }

/* Модалка «Главная страница» (личные настройки учителя) */
.hs-cap { font-size: .74em; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); margin: 14px 0 2px; }
.hs-cap:first-child { margin-top: 0; }
.hs-row { padding: 9px 0; border-bottom: 1px solid var(--border); }
/* Последняя строка листа — без разделителя: он висел бы чертой над пустым дном окна */
.hs-row:last-child, .hs-nav:last-child { border-bottom: 0; }
.hs-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.hs-hint { font-size: .78em; color: var(--muted); }
/* Перетаскиваемая строка виджета: грип слева, подпись тянется, тумблер справа */
/* ⚠️ Строки списка не выделяются текстом: на телефоне долгое касание иначе выделяло подпись
   вместо перетаскивания (жалоба владельца 26.10.2026). */
.hs-drag { gap: 10px; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
.hs-drag .hs-main { flex: 1; }
/* Ручка: `touch-action: none` — иначе палец на ней прокручивает окно вместо перетаскивания
   (`enableLiftReorder` берёт строку за ручку указательными событиями). Зона нажатия шире
   самого значка (≈42px по высоте): за 18-пиксельную точку пальцем не взяться; отрицательные
   поля возвращают раскладку строки на место. */
.hs-drag .dnd-grip { font-size: 1.15em; padding: 12px 10px; margin: -12px -6px -12px -12px;
  touch-action: none; -webkit-user-select: none; user-select: none; }
.hs-widgets .dnd-dragging { opacity: .5; }
/* Поднятая строка: у самой строки заливки нет, и без неё она просвечивала бы сквозь соседей.
   Заливка и «поля» — тенью-обводкой, а не padding'ом: ширина строки в руке не меняется. */
.hs-widgets .lift-card { background: var(--surface-2); border-radius: var(--radius-sm);
  box-shadow: 0 0 0 8px var(--surface-2), var(--shadow); }

@media (max-width: 1000px) {
  /* Одна колонка: колонка виджетов РАСКРЫТА (`display: contents`), и они вместе с сеткой идут в
     потоке по числам `--ord` на каждом узле (ставит `composeSide`: блок показателей — 0, виджеты —
     2, 4, 6…, сетка — нечётное число между соседями). Так строка «Расписание» в «Виджетах» ставит
     сетку на ЛЮБОЕ место среди виджетов (по умолчанию — показатели над ней, лента под ней).
     Пустых строк грида, а с ними и лишних зазоров, нет: поток из того, что есть.
     ⚠️ Числа читает только телефон: на ПК колонка справа — обычный flex-контейнер, где порядок
     задаёт сама разметка.
     ⚠️ `align-items: stretch`: у `.tdash-main` стоит `start` (для грида на ПК), и во flex-колонке
     он сжимал бы сетку и ряд «урок + домашка» по содержимому, а не на всю ширину. */
  .tdash-main { display: flex; flex-direction: column; align-items: stretch; gap: 16px; }
  .tdash-side { display: contents; }
  .tdash-main > .tdash-week, .tdash-side > * { order: var(--ord, 0); }
  .tdash-head { grid-template-columns: minmax(0, 1fr) auto; }
  .tdash-stats { flex-direction: row; }
  /* ⚠️ Селектор именно `.tdash-stats > *`, а не `.tdash-stat`: во-первых, у ученика рядом
     стоит карточка алмазов (она не `.tdash-stat`) и без этого забирала бы ширину по
     содержимому; во-вторых, `.tdash-stat` встречается и в колонке виджетов (`.tdash-side`),
     а там ось вертикальная — `flex: 1 1 0` растянул бы «Следующий урок» по высоте. */
  .tdash-stats > * { flex: 1 1 0; min-width: 0; }
  /* Показатели встали в ряд — карточка сузилась вчетверо, и десяток аватарок в ней уже
     не помещается рядом с числом. Показываем пять: ряд остаётся подсказкой «кто это»,
     а сколько их всего, говорит число. */
  .tdash-stats > .tdash-card .tdash-avrow > .avatar:nth-child(n+6) { display: none; }
}
@media (max-width: 760px) {
  .tdash { gap: 12px; }
  /* Приветствие мельче на 15%: на телефоне оно съедало верх экрана, а следующий урок
     (главное на этой странице) уходил под сгиб. */
  .tdash-hello { font-size: 1.15rem; gap: 7px; }
  .tdash-date { margin-top: 4px; font-size: .78em; }
  /* Шапка на телефоне — ВСЕГО ДВЕ строки: приветствие + значок «Виджеты» в углу, под ними
     ОДНА кнопка «Набор и рабочие часы» во всю ширину. ⚠️ Тумблер набора и «Рабочие часы»
     занимали здесь ещё две строки и отжимали карточку «Следующий урок» под сгиб — оба
     переехали ВНУТРЬ листа этой кнопки (schedule.js openRecruitSettings).
     ⚠️ У «Виджетов» подписи нет вовсе (её убрали и на ПК — см. `.tdash-cfg-ico`), а у
     широкой кнопки она есть: значок наверху остаётся ОДИН, и спутать его не с чем.
     Правило «подписи не прячем» появилось, когда кнопок со значками было две и обе с
     шестерёнкой — какая что настраивает, угадать было нельзя; теперь у широкой кнопки
     значка нет вовсе.
     Обёртки распущены через `display: contents` — так все элементы шапки раскладываются
     по строкам ОДНОЙ сеткой, а раскладка на ПК (кнопка над тем, что она настраивает)
     остаётся нетронутой. */
  .tdash-head, .tdash-bare .tdash-head { grid-template-columns: minmax(0, 1fr) auto;
    gap: 10px; align-items: start; }
  .tdash-head-l, .tdash-head-acts, .tdash-head-r { display: contents; }
  .tdash-greet { grid-column: 1; grid-row: 1; }
  .tdash-desk, .wh-nudge-wrap.tdash-desk { display: none; }   /* тумблер и «Рабочие часы» — ПК */
  .tdash-mob { display: inline-flex; }
  .tdash-cfg { flex: 1 1 auto; justify-content: center; padding: 9px 12px; font-size: .78em; }
  /* «Виджеты» — шестерёнка в углу приветствия (имя кнопки — в title/aria-label) */
  .tdash-head-r > .tdash-cfg { grid-column: 2; grid-row: 1; flex: 0 0 auto;
    padding: 10px; border-radius: 12px; }
  .tdash-head-r > .tdash-cfg svg { width: 18px; height: 18px; }
  /* «Набор и рабочие часы» (учителю) — своей строкой во всю ширину (обёртка выноски — тоже).
     У ученика в шапке действий нет: «Мои окна для учителей» стоят в панели недели
     (`.sched-hours`, см. ниже). */
  .tdash-head-acts > .tdash-mob,
  .wh-nudge-wrap.tdash-mob { grid-column: 1 / -1; grid-row: 2; }
  .tdash-recruit { width: 100%; border-radius: 999px; padding: 13px 16px; font-size: .82em; }
  /* Выноска «Отметьте часы» на телефоне стоит В ПОТОКЕ над кнопкой (см. .wh-nudge ниже),
     поэтому обёртка — колонка. */
  .wh-nudge-wrap.tdash-mob { display: flex; flex-direction: column; gap: 8px; }
  .wh-bar { padding: 12px 14px; gap: 9px; }
  .wh-bar-top { gap: 10px; }
  .wh-bar-acts { flex: 1 1 100%; justify-content: flex-end; flex-wrap: wrap; }
  .wh-count { margin-right: auto; }
  /* Показатели — сетка 2×2 из ОДИНАКОВЫХ плиток, в том порядке, который учитель задал
     в «Виджетах». Горизонтальной ленты нет намеренно: обрезанная карточка у края экрана
     читается как сломанная вёрстка, а не как «пролистай меня».
     ⚠️ «Следующий урок» больше НЕ полоса во всю ширину сверху (было `grid-column: 1/-1;
     order: -1`): полоса ломала ряд и отжимала остальные три плитки вниз. Теперь он такая же
     плитка, только сложенная в колонку — подпись, значение, уточнение, а «В класс ›» прижат
     к нижнему краю (на месте подвала с числами у соседей).
     Значков у плиток нет вовсе — см. пометку у statCard()/chartCard() в schedule.js. */
  .tdash-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .tdash-stat { min-width: 0; padding: 12px; }
  /* Одинокая последняя карточка ряда — во всю ширину: половинка с пустотой рядом читается как
     «второй виджет не загрузился» (у ученика так бывает, когда школа выключила алмазы; у учителя —
     три карточки с графиками: две в ряд и третья на всю ширину). `:only-child` — частный случай. */
  .tdash-stats > :last-child:nth-child(odd) { grid-column: 1 / -1; }
  /* ⚠️ `min-width: 0` обязателен ВСЕМ плиткам ряда, а не только `.tdash-stat`: авто-минимум
     элемента сетки равен его min-content, и карточка алмазов (кольцо 74px + подпись) раздувала
     свою колонку до 201px против 125px у соседа — ряд «2×2» переставал быть ровным. */
  .tdash-stats > * { min-width: 0; }
  /* ⚠️ Карточка алмазов в ПОЛОВИНЕ ряда складывается в колонку: кольцо и подпись в строку
     требуют минимум 183px, а плитке достаётся 163 — и лишние двадцать пикселей уводили
     ВСЮ страницу в горизонтальную прокрутку (замерено на 375px).
     Порядок — как в карточке баланса рядом (макет владельца продукта): пояснение сверху,
     кольцо в правом нижнем углу. ⚠️ Именно `column-reverse`, а не перестановка в JS: в
     разметке кольцо идёт первым, и на ПК оно так и стоит СЛЕВА от текста. */
  .tdash-stats .dmw-main { flex-direction: column-reverse; align-items: stretch; gap: 8px; }
  .tdash-stats .dmw-side { min-width: 0; }
  .tdash-stats .dmw-dial { width: 64px; height: 64px; align-self: flex-end; margin-top: auto; }
  /* «Урок стоит N алмазов» — то же самое, что «до урока ещё 9 из 10» строкой выше;
     в половине ряда это лишняя строка, из-за которой плитка растёт. */
  .tdash-stats .dmw-sub { display: none; }
  /* ⚠️ Через `.tdash-stats >`: ту же карточку показывает главная УЧЕНИКА, где она стоит
     вне блока баланса и алмазов (`.tdash-side`) во всю ширину и колонкой ей быть незачем. */
  .tdash-stats > .tdash-stat-next { flex-direction: column; align-items: stretch; gap: 6px; }
  /* ⚠️ Без пилюли-подложки: «В класс ›» — та же ссылка «слово + стрелка», что «История ›»
     и «Классы ›» в соседних плитках (решение владельца продукта). */
  .tdash-stats > .tdash-stat-next .tdash-stat-go { align-self: flex-end; margin-top: auto; }
  /* Плитки в ряду тянутся до одной высоты — значит и подвалы обязаны стоять на одной
     линии, иначе у соседа без крупного числа («Ученики» рядом со «Ставкой») под текстом
     зияет пустая треть плитки. Свободную высоту отдаём графику: он и есть главная площадь
     карточки. */
  .tdash-stats > .tdash-card { align-items: stretch; }
  .tdash-stats > .tdash-card .spark-wrap { flex: 1 1 auto; display: flex; min-height: 30px; }
  .tdash-stats > .tdash-card .spark { flex: 1 1 auto; height: auto; }
  /* В половинной плитке показатели подвала могут не влезть в строку — переносим их,
     сохраняя порядок «месяц · неделя · сегодня». */
  .tdash-card-foot.multi { flex-wrap: wrap; row-gap: 2px; }
  /* Месяц у «Уроков» на мобильном не показываем — не влезает; остаются неделя и сегодня. */
  .tdash-stats .tdash-metric-month { display: none; }
  /* В плитке 2×2 (≈143px) не помещаются и три аватарки — ряда нет вовсе, по тому же
     правилу, по которому у мобильных плиток убраны значки: место нужнее подписи и числу. */
  .tdash-stats > .tdash-card .tdash-avrow { display: none; }
  .tdash-card .spark { height: 30px; }
  /* ⚠️ Счётчик «через 1 ч 34 мин» в 143px плитки не помещался и обрезался многоточием —
     кегль на пункт мельче, а редкая длинная дата («Понедельник, 17.06») переносится
     на вторую строку: обрезанная дата не читается вовсе, а лишняя строка тянет вверх
     обе плитки ряда и ничего не ломает. */
  .tdash-stats > .tdash-stat-next .tdash-stat-val { font-size: .95em;
    white-space: normal; line-height: 1.2; }
  .sched-toolbar { padding: 8px 10px; }
  /* Склеенная с сеткой панель (учитель; у ученика своё правило ниже, в `em`): строка по центру
     видимой панели — 12px сверху и 12px до шапки дней, плюс скрытый запас под сетку. */
  .sched-toolbar.sched-join { padding: 12px 10px calc(12px + var(--radius)); }
  .sched-toolbar .msk-sec { display: none; }        /* секунды скрыты — экономим место */
  .sched-toolbar .week-label { min-width: 0; font-size: .8em; }
  /* Навигация: пока влезает — справа от часов, не влезла — своей строкой целиком */
  .sched-toolbar .week-nav { flex: 1 1 auto; justify-content: flex-end; gap: 6px; }
  .sched-toolbar .week-nav .btn-sm { padding: 5px 9px; }
  /* Панель ученика: часы, «Мои окна для учителей» и неделя — в ОДНУ строку (макет владельца
     продукта: «можно уменьшить шрифт»). Кегль панели течёт от ширины экрана, а всё внутри —
     шрифты, отступы, значок — задано в `em`, поэтому строка сжимается ЦЕЛИКОМ и держит форму
     (тот же приём, что у легенды под сеткой). Замерено: 360/375/390/430/480 — одна строка.
     ⚠️ Не возвращать фиксированные `px`: на 375 три группы тогда не помещаются, и неделя
     уезжает на вторую строку. Высота кнопки окон и стрелок одна — те же `.btn-sm`, те же
     отступы и кегль; значок ниже строки, высоту он не меняет. */
  .stu-own .sched-toolbar { gap: .5em; padding: .75em .7em; font-size: clamp(8.5px, 3.2vw, 14px); }
  .stu-own .sched-tb-l { gap: .5em; flex: 0 0 auto; }
  .stu-own .sched-toolbar .msk-clock { gap: .4em; }
  .stu-own .sched-toolbar .msk-pulse { width: .5em; height: .5em; }
  .stu-own .sched-toolbar .week-nav { gap: .35em; }
  .stu-own .sched-toolbar .week-label { font-size: .85em; }
  .stu-own .sched-toolbar .btn-sm { padding: .45em .55em; font-size: .85em; letter-spacing: .02em; }
  .stu-own .sched-toolbar .sched-hours { gap: .4em; padding-inline: .6em; }
  /* Склеенная с сеткой панель: общий `padding` выше сбросил бы нижний запас — возвращаем, и строка
     снова стоит по центру видимой панели (сверху и снизу поровну). */
  .stu-own .sched-toolbar.sched-join { padding-top: calc(.75em + 4px);
    padding-bottom: calc(.75em + 4px + var(--radius)); }
  .stu-own .sched-toolbar .sched-hours svg { width: 1.05em; height: 1.05em; }
}
@media (max-width: 340px) {
  /* Совсем узкие экраны: подпись плитки и число рядом с ней уже режутся — раскладываем
     плитки в столбик (места снова хватает, поэтому график выше и подвал в одну строку).
     ⚠️ Порог опущен 380 → 340: на 360–375px (iPhone SE/mini, добрая половина Android)
     сетка 2×2 помещается целиком — замерено, переполнения нет ни в одной плитке, — а
     столбик из четырёх плиток уводил «Следующий урок» под сгиб. */
  .tdash-stats { grid-template-columns: 1fr; }
  .tdash-card-foot.multi { flex-wrap: nowrap; }
  .tdash-card .spark { height: 34px; }
}

.slot-count { font-size: .89em; line-height: 1; color: color-mix(in srgb, var(--text-dim) 78%, var(--surface)); font-weight: 500; font-variant-numeric: tabular-nums; }

/* Попап урока (клик по чипу расписания). Без заголовка — карточка начинается прямо
   с полей. Строки — «зеброй» как в системных настройках macOS: чередование фона
   ВМЕСТО линий-разделителей. Баланс — отдельной строкой. Статус — ТЕМ ЖЕ цветом, что
   и чип в сетке. «Открыть класс» / «Войти в класс» — крупной кнопкой с тем же живым
   переливом, что и «Войти в класс» на странице класса (.cd-enter); кнопка ЗАХОДИТ НА
   нижние строки списка (отрицательный отступ). Ряд действий — нейтральные кнопки
   одного вида, красным не красим. */
.lesson-pop { border-radius: var(--radius); overflow: hidden; }
.lesson-pop .lp-row { display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 9px 15px; font-size: .82em; }
/* ⚠️ Зебра — ЕЛЕ ЗАМЕТНАЯ, и НЕ через --surface-2: у школы с перекрашенной палитрой он
   бывает заметно светлее фона модалки, и полоса начинает «резать глаз» (жалоба владельца
   продукта). Мягкий тон от --text — одинаково приглушённый в обеих темах. */
.lesson-pop .lp-row:nth-child(odd) { background: color-mix(in srgb, var(--text) 5%, transparent); }
/* Последняя строка длиннее снизу — на этот «карман» и заходит кнопка «Открыть класс»,
   не задевая текста строки (align-items:center дополнительно уводит текст вверх). */
.lesson-pop .lp-row:last-child { padding-bottom: 26px; }
/* ⚠️ Подпись и значение — ОДНОГО цвета и веса (решение владельца продукта): роль читается
   по стороне строки, а не по яркости. Раньше подпись была --muted, значение --text, и
   строка выглядела «двухцветной». */
.lp-k { flex: none; }
.lp-k, .lp-v { color: var(--text); font-weight: 400; }
.lp-v { text-align: right; }
.lp-person { display: inline-flex; align-items: center; gap: 8px; }
/* Кружок присутствия: зелёный — человек на платформе, КРАСНЫЙ — офлайн (просили именно
   красный, а не серый: это состояние, а не «нет данных»). */
.lp-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--danger);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 20%, transparent); }
.lp-dot.on { background: var(--lesson-done);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--lesson-done) 24%, transparent); }
.lp-reg { display: inline-flex; align-items: center; gap: 7px; }
/* Значок регулярности (R / S / D) заметно крупнее подписи — на кегле строки он терялся. */
.lp-reg-mark { font-weight: 600; font-size: 1.45em; line-height: 1; }
.lp-status { font-weight: 500; }
/* Статус — ЯРКИЕ варианты (--lesson-*-fg): базовые токены нарисованы как заливка чипа,
   и текстом на тёмной поверхности читаются тускло. */
.lp-st-plan   { color: var(--lesson-upcoming-fg); }
.lp-st-done   { color: var(--lesson-done-fg); }
.lp-st-noshow { color: var(--noshow); }
.lp-st-nobal  { color: var(--lesson-nobal-fg); }
.lp-st-off    { color: var(--muted); }

/* Кнопка «Открыть класс» ЗАХОДИТ на нижние строки списка (по макету владельца продукта):
   отрицательный верхний отступ, z-index держит её над «зеброй». ⚠️ Боковых полей НЕТ —
   кнопка ровно по ширине «зебры» (просили выровнять: раньше она не дотягивала до края). */
.lesson-pop-open:not(:empty) { position: relative; z-index: 1; margin: -14px 0 0; }
.lesson-pop-go.cd-enter { width: 100%; padding: 15px 22px; font-size: .95em;
  border-radius: var(--radius); }
.lesson-pop-acts { margin-top: 12px; flex-wrap: wrap; gap: 8px; }
.lesson-pop-acts .btn { flex: 1 1 auto; }
/* Строка вместо кнопок там, где действий нет (прошедший пробный урок): пустой ряд
   читался бы как «кнопки не загрузились». */
.lp-hint { flex: 1 1 auto; color: var(--muted); font-size: .84em; line-height: 1.45;
  text-align: center; }
/* «выбирается заново» — не время, а его отсутствие: приглушаем, чтобы не читалось часами. */
.lp-v-dim { color: var(--muted); }

/* Выбор «одно занятие / вся серия» — сегмент в диалоге отмены повторяющегося урока. */
.cw-scopes { display: flex; gap: 8px; margin-bottom: 12px; }
.cw-scope { flex: 1 1 0; cursor: pointer; font-family: var(--font); font-size: .85em;
  padding: 9px 10px; border-radius: 10px; text-align: center;
  background: color-mix(in srgb, var(--text) 6%, transparent); border: 1px solid transparent;
  color: var(--text-dim); }
.cw-scope.active { border-color: var(--accent-border); color: var(--accent-text);
  background: color-mix(in srgb, var(--accent) 14%, transparent); }
/* Окно «Удалить урок» (schedule.js askDelete): текст — как у confirmDialog, под выбором
   серии — пояснение, что останется. ⚠️ display здесь не задавать: подсказку прячет
   атрибут hidden, и любой display его перебил бы. */
.lp-del-text { margin: 0 0 14px; color: var(--text-dim); font-size: .92em; line-height: 1.6; }
.lp-del-hint { margin: -4px 0 12px; color: var(--muted); font-size: .84em; line-height: 1.45; }

.slot-title { display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; border-bottom: 1px solid var(--border); padding-bottom: 14px; margin-bottom: 16px; }
.slot-title-date { font-size: 1.02em; font-weight: 500; color: var(--text); }
.slot-title-count { font-size: .82em; color: var(--muted); white-space: nowrap; }
.slot-list { display: flex; flex-direction: column; gap: 12px; max-height: 62vh; overflow: auto; }
/* Мобиле: список — часть единой прокрутки листа-модалки, без вложенного скролл-бокса
   (иначе на мобиле список «упирается» во внутреннюю рамку раньше, чем доскроллит весь лист). */
@media (max-width: 760px) {
  .slot-list { max-height: none; overflow: visible; }
}

/* Карточка урока (окно слота у администратора), по макету владельца продукта — три
   столбца: таймлайн начало/окончание · учитель и ученик · действия с уроком («Лобби
   класса» с переливом, под ней «Перенести» и «Удалить» значками).
   Разделитель — МЕЖДУ уроками (когда их несколько в одном слоте), см. .slot-card + .slot-card. */
.slot-card { border: 1px solid transparent; border-radius: var(--radius-sm); padding: 12px 4px;
  display: flex; align-items: center; gap: 22px; }
.slot-card + .slot-card { border-top: 1px solid var(--border-2); margin-top: 2px; padding-top: 22px; }
.slot-people { flex: 1; display: flex; flex-direction: column; gap: 12px; min-width: 0; }
/* ⚠️ Столбцы карточек одного слота — ОБЩИЕ (subgrid): таблетки итога разной длины
   («Неявка учителя», «Нет баланса» шире подписи «Окончание»), и во флексе столбец времени
   у каждой карточки свой — люди в соседних уроках стояли бы лесенкой. Ширина берётся по
   самой широкой карточке слота, так что одиночный урок выглядит как на макете. Зазоры между
   столбцами задаёт сама карточка (её gap). Без subgrid остаётся флекс — разъедется только
   лесенка, не раскладка. */
@supports (grid-template-columns: subgrid) {
  .slot-list { display: grid; grid-template-columns: auto minmax(0, 1fr) 190px; }
  .slot-card { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; }
  /* Мобиле: действия уходят второй строкой на всю ширину карточки. */
  @media (max-width: 560px) {
    .slot-list { grid-template-columns: auto minmax(0, 1fr); }
    .slot-acts { grid-column: 1 / -1; }
  }
}

/* Таймлайн (точки+линия начало→окончание) — одна колонка на обоих людей */
.slot-times { display: flex; gap: 12px; flex: 0 0 auto; align-self: stretch; }
.tl-col { display: flex; flex-direction: column; align-items: center; padding: 6px 0 6px 2px; }
.tl-dot { flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%; background: var(--accent); }
.tl-line { flex: 1; width: 2px; background: var(--border-2); margin: 3px 0; min-height: 14px; }
.tl-rows { flex: 1; display: flex; flex-direction: column; justify-content: space-between; gap: 12px; }
.time-text { display: flex; flex-direction: column; line-height: 1.2; }
.st-cap { font-size: .68em; color: var(--muted); }
.st-val { font-size: 1em; color: var(--text); font-variant-numeric: tabular-nums; }
/* Итог закончившегося урока — маленькая таблетка между «Началом» и «Окончанием», ровно
   посередине таймлайна (макет владельца). Заливки — те же, что у чипов сетки (.lesson-chip):
   они подобраны под белый текст с контрастом AA в обеих темах. */
.slot-result { align-self: flex-start; padding: 1px 9px; border-radius: 99px; font-size: .58em;
  font-weight: 500; line-height: 1.45; white-space: nowrap; color: #fff; }
.slot-result.done { background: var(--lesson-done); }
.slot-result.noshow { background: var(--lesson-noshow); }
.slot-result.nobal { background: var(--lesson-nobal); }
/* У неявки — хронология урока по наведению/тапу (lesson-timeline.js). */
.slot-result.has-tip { cursor: help; }
/* Проведённый урок с расшифрованным звонком: таблетка — кнопка ИИ-разбора (insights.openCall). */
.slot-result.has-call { position: relative; display: inline-flex; align-items: center; gap: 4px;
  margin: 0; border: 0; font-family: inherit; cursor: pointer; }
/* Таблетка ~15px в высоту — пальцу мало: невидимый запас зоны нажатия (в зазор таймлайна). */
.slot-result.has-call::after { content: ''; position: absolute; inset: -8px -4px; }
.slot-result.has-call:hover { filter: brightness(1.15); }
.slot-result-ic svg { width: 1.1em; height: 1.1em; display: block; }
/* Звонок записан частями — выбор части перед разбором. */
.slot-calls { display: flex; flex-direction: column; gap: 8px; }
.slot-call { justify-content: space-between; gap: 16px; }
.slot-call-meta { color: var(--muted); font-variant-numeric: tabular-nums; }

/* Хронология урока: position:fixed + в document.body (как .cv-info-pop) — иначе её
   обрезала бы прокрутка окна слота. pointer-events:none — это подсказка, а не окно:
   под ней не должно пропадать наведение на таблетку. */
.ltl-pop {
  position: fixed; z-index: 1200; box-sizing: border-box; max-width: calc(100vw - 16px);
  padding: 12px 14px; border-radius: var(--radius-sm);
  background: var(--bg); border: 1px solid var(--border-2); box-shadow: var(--shadow);
  color: var(--text); font-size: .84rem; line-height: 1.4; pointer-events: none;
  display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 7px 12px;
}
/* Колонка времени — общая на все строки и по самой длинной («17:41–18:16»,
   «12.10 16:41»): subgrid. Без его поддержки — с запасом под диапазон. */
.ltl-row { grid-column: 1 / -1; display: grid; grid-template-columns: 7.6em minmax(0, 1fr); gap: 12px; }
@supports (grid-template-columns: subgrid) { .ltl-row { grid-template-columns: subgrid; } }
.ltl-time { color: var(--text-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
.ltl-text { min-width: 0; overflow-wrap: anywhere; }
/* «Чего не было» — без времени, приглушённо, под чертой: это вывод, а не событие. */
.ltl-note { color: var(--text-dim); }
.ltl-row:not(.ltl-note) + .ltl-note { border-top: 1px solid var(--border-2); padding-top: 7px; }
.ltl-empty { grid-column: 1 / -1; color: var(--text-dim); }

/* Строка человека: аватар с точкой присутствия, роль, имя + ID. Профиль открывается
   кликом по аватару или имени — отдельных кнопок «Написать»/«Профиль» нет. */
.slot-row { display: flex; align-items: center; gap: 12px; min-width: 0; }
.slot-ava { position: relative; flex: none; padding: 0; border: 0; background: none;
  border-radius: 50%; cursor: pointer; font: inherit; }
/* Точка присутствия — цвета .lp-dot: зелёная — на платформе, красная — не в сети.
   Обводка цветом поверхности отрезает её от аватара. */
.slot-ava-dot { position: absolute; right: 0; bottom: 0; width: 11px; height: 11px; border-radius: 50%;
  background: var(--danger); border: 2px solid var(--surface); box-sizing: border-box; }
.slot-ava-dot.on { background: var(--lesson-done); }
.person-main { flex: 1; min-width: 0; line-height: 1.25; }
.person-role { font-size: .66em; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); }
.person-name-line { display: flex; align-items: center; gap: 8px; min-width: 0; }
.person-name { font: inherit; font-size: 1em; color: var(--text); font-weight: 500; min-width: 0;
  padding: 0; border: 0; background: none; cursor: pointer; text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; transition: color .12s; }
.person-name:hover { color: var(--accent-text); }
.slot-card .avatar { width: 44px; height: 44px; font-size: 1em; }
.id-chip { display: inline-flex; align-items: center; gap: 3px; font-size: .68em; font-weight: 700;
  color: var(--badge-accent); background: color-mix(in srgb, var(--badge-accent) 16%, transparent);
  border-radius: 5px; padding: 2px 6px; white-space: nowrap; flex: 0 0 auto; }
.id-num { font-variant-numeric: tabular-nums; }
.copy-btn { background: transparent; border: none; color: inherit; opacity: .75; cursor: pointer; font-size: 1em;
  line-height: 1; padding: 0; border-radius: 4px; transition: opacity .12s; }
.copy-btn:hover { opacity: 1; }

/* Действия с уроком: «Лобби класса» во всю ширину столбца, под ней — квадраты-значки. */
.slot-acts { flex: 0 0 190px; display: flex; flex-direction: column; gap: 10px; }
.slot-lobby.cd-enter { width: 100%; padding: 14px 18px; font-size: .92em; border-radius: var(--radius-sm); }
.slot-ics { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 10px; }
.slot-ic { height: 44px; display: grid; place-items: center; padding: 0; cursor: pointer;
  border-radius: var(--radius-sm); background: var(--bg); color: var(--text);
  border: 1px solid transparent; transition: border-color .12s, color .12s; }
.slot-ic svg { width: 20px; height: 20px; display: block; }
.slot-ic:hover { border-color: var(--border-2); }
.slot-ic-del:hover { color: var(--danger); }
.avatar { width: 40px; height: 40px; flex: 0 0 auto; border-radius: 50%; display: grid; place-items: center;
  background: var(--surface-2); border: 1px solid var(--border-2); color: var(--accent-text); font-weight: 600; font-size: 1em; }

/* Мобиле: таймлайн и люди остаются в ряд (имя ужимается ellipsis'ом), действия
   уходят под них одной строкой — лобби растягивается, значки справа. */
@media (max-width: 560px) {
  .slot-card { flex-wrap: wrap; gap: 12px 14px; }
  .slot-times { gap: 8px; }
  .slot-row { gap: 9px; }
  .slot-acts { flex: 1 1 100%; flex-direction: row; }
  .slot-lobby.cd-enter { flex: 1 1 auto; width: auto; padding: 12px 14px; }
  .slot-ics { flex: 0 0 auto; grid-auto-columns: 48px; }
  .slot-ic { height: 100%; min-height: 44px; }
}

/* ── Бейдж баланса уроков ──────────────────────────────────────── */
.bal-badge { display: inline-flex; align-items: center; gap: 2px; padding: 0 6px; border-radius: 99px;
  background: color-mix(in srgb, var(--badge-accent) 10%, transparent); border: 1px solid var(--accent-border); white-space: nowrap; line-height: 1.45; }
.bal-badge.low { background: color-mix(in srgb, var(--danger) 10%, transparent); border-color: color-mix(in srgb, var(--danger) 40%, transparent); }
.bal-ico { font-size: .6em; line-height: 1; opacity: .8; }
.bal-num { font-size: .72em; font-weight: 700; color: var(--badge-accent); font-variant-numeric: tabular-nums; }
.bal-badge.low .bal-num { color: var(--danger); }
.bal-cap { font-size: .78em; color: var(--text-dim); margin-left: 2px; }
.bal-badge.big { padding: 3px 11px; gap: 5px; }
.bal-badge.big .bal-ico { font-size: .9em; }
.bal-badge.big .bal-num { font-size: .95em; }
.bal-badge.big .bal-cap { font-size: .76em; }
/* Компактный (узкий) вариант для бейджа общего баланса в шапке расписания */
.bal-badge.big.bal-toggle { padding: 6px 13px; gap: 5px; }
.bal-badge.big.bal-toggle .bal-num { font-size: 1em; }
.bal-badge.big.bal-toggle .bal-cap { font-size: .72em; }

/* Баланс уроков ученика: «Баланс уроков: N» + круглая кнопка «+» (пополнение) */
.bal-add { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
  width: 21px; height: 21px; padding: 0; border-radius: 50%; cursor: pointer;
  color: var(--badge-accent); background: color-mix(in srgb, var(--badge-accent) 12%, transparent); border: 1px solid var(--accent-border);
  transition: background .14s, transform .1s; }
.bal-add:hover { background: color-mix(in srgb, var(--badge-accent) 22%, transparent); }
.bal-add:active { transform: scale(.92); }
.bal-add svg { width: 13px; height: 13px; }
/* Кнопка-строка выбора класса для пополнения */
.balp-row-btn { width: 100%; text-align: left; background: transparent; border: 1px solid var(--border);
  cursor: pointer; font-family: inherit; }
.balp-row-btn + .balp-row-btn { margin-top: 6px; }
.balp-row-btn:hover { background: rgba(255,255,255,.03); border-color: var(--accent-border); }

/* Виджет общего баланса ученика — раскрытие по классам */
.bal-widget { position: relative; }
.bal-toggle { cursor: pointer; font-family: inherit; font-size: 1rem; }
.bal-toggle:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.bal-toggle.low:hover { background: color-mix(in srgb, var(--danger) 16%, transparent); }
.bal-caret { font-size: .7em; color: var(--text-dim); margin-left: 4px; transition: transform .15s ease; }
.bal-toggle.open .bal-caret { transform: rotate(180deg); }
.bal-pop { position: absolute; top: calc(100% + 8px); right: 0; z-index: 50;
  min-width: 264px; max-width: calc(100vw - 28px);
  background: var(--surface-2); border: 1px solid var(--border-2);
  border-radius: var(--radius-sm); box-shadow: var(--shadow); padding: 8px;
  animation: balpop .12s ease; }
@keyframes balpop { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
.balp-head { font-size: .68em; text-transform: uppercase; letter-spacing: .09em;
  color: var(--text-dim); padding: 6px 8px 8px; }
.balp-list { display: flex; flex-direction: column; gap: 2px; }
.balp-row { display: flex; align-items: center; gap: 12px; padding: 8px; border-radius: var(--radius-sm); }
.balp-row:hover { background: rgba(255,255,255,.03); }
.balp-info { flex: 1; min-width: 0; }
.balp-name { font-weight: 600; font-size: .92em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.balp-teacher { font-size: .78em; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.balp-bal { font-size: .95em; color: var(--badge-accent); font-variant-numeric: tabular-nums; white-space: nowrap; }
.balp-bal.low { color: var(--danger); }
.balp-empty { padding: 10px 8px; color: var(--text-dim); font-size: .9em; }
/* Строка баланса по классу — кликабельна (переход в класс) */
.balp-row-link { cursor: pointer; }
.balp-go { color: var(--muted); font-size: 1em; flex: 0 0 auto; transition: transform .15s, color .15s; }
.balp-row-link:hover .balp-go { color: var(--accent-text); transform: translateX(2px); }

/* Кастомный выпадающий список учеников (с аватарками) при постановке урока */
.stud-sel { position: relative; }
.stud-sel-display { display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%; text-align: left; cursor: pointer; font-family: inherit; font-size: 1rem; }
.stud-sel-cur { display: flex; align-items: center; gap: 8px; min-width: 0; }
.stud-sel-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stud-sel-ph { color: var(--muted); }
.stud-sel-caret { color: var(--text-dim); font-size: .8em; transition: transform .15s; flex: 0 0 auto; }
.stud-sel.open .stud-sel-caret { transform: rotate(180deg); }
.stud-sel-pop { margin-top: 6px; border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  background: var(--surface-2); max-height: 240px; overflow-y: auto; padding: 4px; }
.stud-sel-opt { display: flex; align-items: center; gap: 10px; padding: 8px; border-radius: var(--radius-sm); cursor: pointer; }
.stud-sel-opt:hover { background: rgba(255,255,255,.04); }
.stud-sel-opt-main { min-width: 0; }
.stud-sel-opt-name { font-size: .92em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stud-sel-opt-sub { font-size: .76em; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }


/* статус + кнопка вкл/выкл рядом */
.user-state { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }

/* Имя + бейдж баланса/группы в одну строку; ID под email */
.row-nameline { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.row-id { font-size: .72em; color: var(--muted); margin-top: 3px; font-variant-numeric: tabular-nums; letter-spacing: .3px; }

/* Единый pill: статус + переключатель */
.toggle-pill { display: inline-flex; align-items: center; gap: 7px; flex: 0 0 auto; padding: 7px 15px;
  border-radius: 99px; border: 1px solid var(--border-2); background: transparent; cursor: pointer;
  font-family: var(--font); font-size: .8em; letter-spacing: .3px; transition: background .14s, border-color .14s, color .14s; }
.toggle-pill .tp-dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
.toggle-pill.on { color: var(--accent-text); border-color: var(--accent-border); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.toggle-pill.on:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.toggle-pill.off { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, transparent); background: color-mix(in srgb, var(--danger) 8%, transparent); }
.toggle-pill.off:hover { background: color-mix(in srgb, var(--danger) 16%, transparent); }

/* ── Бейдж непрочитанных в навигации ───────────────────────────── */
.nav-badge { display: inline-grid; place-items: center; min-width: 17px; height: 17px; padding: 0 5px;
  margin-left: 7px; border-radius: 99px; background: var(--tb-accent); color: var(--tb-on-accent); font-size: .66em;
  font-weight: 700; vertical-align: middle; }

/* ── Выноска «Отметьте часы» над кнопкой «Рабочие часы» ────────── */
/* Висит на главной учителя, пока не отмечен ни один рабочий час (schedule.js
   workHoursButton), и исчезает сразу после сохранения. Заливка — акцент палитры школы
   через var(), а не литеральный цвет: подсказка должна краситься вместе с интерфейсом. */
.wh-nudge-wrap { position: relative; display: inline-flex; }
/* Облик — общий с выноской «Настройте профиль» у имени в шапке (.card-nudge ниже). */
.wh-nudge, .card-nudge { padding: 5px 11px; border: 0; border-radius: 9px;
  background: var(--accent); color: var(--on-accent);
  font-family: inherit; font-size: .8em; font-weight: 600; line-height: 1.3; white-space: nowrap;
  cursor: pointer; animation: wh-nudge-glow 2.4s ease-in-out infinite; }
.wh-nudge { position: absolute; bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%); }
/* Хвостик выноски — он и указывает на кнопку; без него это просто плашка над шапкой. */
.wh-nudge::after, .card-nudge::after { content: ''; position: absolute; border: 6px solid transparent; }
.wh-nudge::after { top: 100%; left: 50%; transform: translateX(-50%); border-top-color: var(--accent); }
.wh-nudge:hover, .card-nudge:hover { filter: brightness(1.08); }
/* «Настройте профиль» (app.js renderCardNudge) висит ПОД именем: над шапкой места нет, так
   что хвостик смотрит вверх. Прижата к правому краю, а не по центру #who: у короткого имени
   (и на телефоне, где имени нет вовсе) центрированная выноска вылезала бы за край экрана. */
.card-nudge { position: absolute; top: calc(100% + 10px); right: 0; z-index: 50; }
.card-nudge::after { bottom: 100%; right: 24px; border-bottom-color: var(--accent); }
/* Открытое меню профиля встаёт на то же место; на экране отбора анкета — сам этап. */
.who-btn.open ~ .card-nudge, body.staff-view .card-nudge { display: none; }
@keyframes wh-nudge-glow {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 50%, transparent); }
  50% { box-shadow: 0 0 13px 3px color-mix(in srgb, var(--accent) 45%, transparent); }
}
/* Пульсация — способ поймать взгляд, но навязывать движение тем, кто просил его меньше,
   нельзя: заливка акцентом и хвостик работают и без анимации. */
@media (prefers-reduced-motion: reduce) { .wh-nudge, .card-nudge { animation: none; } }
@media (max-width: 760px) {
  /* На телефоне кнопка «Рабочие часы» стоит СВОЕЙ строкой под тумблером «Набор учеников»,
     и выноска, висящая над ней внахлёст, накрывала бы этот тумблер (а вниз её разворачивать
     нельзя — там карточка недели). Поэтому здесь она не всплывающая, а обычная строка над
     кнопкой: хвостик по-прежнему указывает вниз, на неё же. */
  /* ⚠️ `position: relative`, а НЕ `static`: хвостик — `::after` с `top: 100%`, и у
     статичной выноски он считался бы от ОБЁРТКИ, то есть рисовался под кнопкой и
     показывал в пустоту (так и было — зелёная стрелка под кнопкой ни на что не указывала). */
  .wh-nudge { position: relative; bottom: auto; left: auto; transform: none;
    align-self: center; font-size: .76em; }
  /* Под аватаркой на телефоне сразу шестерёнка «Виджеты» главной: полноразмерная выноска
     наезжала на неё на 5px. Мельче и ближе к аватарке — и шестерёнка открыта целиком. */
  .card-nudge { top: calc(100% + 7px); padding: 4px 10px; font-size: .76em; }
}

/* ── Мессенджер ────────────────────────────────────────────────── */
/* Компактная высота на весь экран, поле ввода не срезается. */
body.msg-view .app { padding-top: 18px; padding-bottom: 16px; }
/* Запас по высоте, чтобы верх чата не срезался даже на низких окнах */
.msg-wrap { display: flex; flex-direction: column; min-height: 0;
  /* топбар 61 + .app padding 18/16 = 95px служебных → страница не прокручивается */
  height: calc(100vh - 95px); height: calc(100dvh - 95px); }
.msg-head { margin-bottom: 12px; }
.msg-head .page-title { font-size: 1.5em; }
.chat { display: grid; grid-template-columns: 288px 1fr; flex: 1 1 auto; min-height: 0;
  border: 1px solid transparent; border-radius: var(--radius); overflow: hidden; background: var(--surface); }
/* min-width:0 обязателен: .chat-side — grid-элемент, а его строка папок (.chat-folders)
   с непереносимыми вкладками иначе раздувает колонку по своему контенту (min-width:auto)
   и поле поиска вылезает за правый край экрана — заметно у админа (папок больше). */
.chat-side { display: flex; flex-direction: column; min-width: 0; min-height: 0; border-right: 1px solid var(--border); }
.chat-folders { display: flex; gap: 5px; padding: 8px 10px; overflow-x: auto; align-items: center;
  border-bottom: 1px solid var(--border); scrollbar-width: thin; }
.cf-tab { padding: 5px 11px; border-radius: 99px; background: var(--surface-2); border: 1px solid var(--border-2);
  color: var(--text-dim); font-size: .76em; cursor: pointer; white-space: nowrap; font-family: var(--font); }
.cf-tab.active { background: color-mix(in srgb, var(--selected) 12%, transparent); border-color: var(--accent-border); color: var(--selected); }
.cf-add, .cf-del, .cf-manage { background: transparent; border: none; color: var(--muted); cursor: pointer; font-size: .95em;
  padding: 2px 6px; flex: 0 0 auto; }
.cf-add:hover, .cf-del:hover, .cf-manage:hover { color: var(--accent-text); }

/* Управление папками (модалка «шестерёнка»): строка = стрелки порядка · имя · переименовать · удалить */
.folder-manage { display: flex; flex-direction: column; gap: 8px; }
.folder-manage-row { display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border: 1px solid transparent; border-radius: 12px; background: var(--surface-2); }
.fm-order { display: flex; flex-direction: column; gap: 1px; flex: 0 0 auto; }
.fm-arrow { width: 26px; height: 18px; line-height: 1; padding: 0; font-size: .7em;
  background: transparent; border: 1px solid var(--border-2); border-radius: 6px; color: var(--text-dim); cursor: pointer; }
.fm-arrow:hover:not(:disabled) { color: var(--accent-text); border-color: var(--accent-border); }
.fm-arrow:disabled { opacity: .3; cursor: default; }
.fm-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fm-act { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; background: transparent; border: none; color: var(--muted); cursor: pointer; border-radius: 8px; }
.fm-act svg { width: 17px; height: 17px; }
.fm-act:hover { color: var(--accent-text); background: var(--surface); }
.fm-del:hover { color: var(--danger); }
/* Стили карточек контактов (.cc-menu/.cc-act/.cc-name-wrap/.cc-pin/.pinned и т.д.)
   собраны ниже единым блоком «Карточки списка чатов». */
.chat-contacts { overflow-y: auto; flex: 1; min-height: 0; }
/* Кнопка «Назад» под списком чатов — не скроллится вместе с контактами */
.chat-side-foot { flex: 0 0 auto; padding: 10px; border-top: 1px solid var(--border); }
.chat-side-back { display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 16px; border-radius: 99px; background: var(--surface-2);
  border: 1px solid var(--border-2); color: var(--text-dim);
  font-size: .82em; font-family: var(--font); cursor: pointer; }
.chat-side-back:hover { color: var(--text); border-color: var(--accent-border); }
/* Поиск по контактам и по сообщениям чата */
.search-field { position: relative; display: flex; align-items: center; }
.search-field .input { width: 100%; box-sizing: border-box; padding-left: 34px; }
.search-ico { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); color: var(--muted);
  display: inline-flex; pointer-events: none; }
.search-ico svg { width: 15px; height: 15px; display: block; }
.search-field:focus-within .search-ico { color: var(--text-dim); }
.chat-search-wrap { margin: 8px 10px 6px; }
.chat-search-wrap .input { font-size: .8em; padding-top: 6px; padding-bottom: 6px; }
.chat-search-wrap .input::placeholder { color: var(--muted); }
/* Поиск в чате: монохромная иконка-кнопка + раскрывающееся поле */
.chat-icon-btn { background: none; border: none; color: var(--muted); cursor: pointer; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center; padding: 6px; border-radius: var(--radius-sm);
  transition: color .15s, background .15s; }
.chat-icon-btn svg { width: 17px; height: 17px; display: block; }
.chat-icon-btn:hover, .chat-icon-btn.active { color: var(--accent-text); background: var(--surface-2); }
.chat-search-box { flex: 0 1 190px; min-width: 0; }
.chat-search-input { width: 100%; box-sizing: border-box; font-size: .82em; padding: 7px 11px; }
.bubble-hit { outline: 2px solid var(--accent); outline-offset: 1px; }
.bubble-dim { opacity: .38; transition: opacity .15s; }
/* Тулбар действий на пузыре (ответ / переслать / закрепить) — появляется на ховер строки */
.bubble-row { align-items: center; gap: 4px; }
.bubble-actions { display: inline-flex; gap: 2px; flex: 0 0 auto; opacity: 0; order: 2; transition: opacity .12s; }
.bubble-row:hover .bubble-actions { opacity: 1; }
.bubble-act { background: none; border: none; color: var(--muted); cursor: pointer; padding: 4px; border-radius: 6px;
  display: inline-flex; transition: color .12s, background .12s; }
.bubble-act svg { width: 15px; height: 15px; display: block; }
.bubble-act:hover { color: var(--accent-text); background: var(--surface-2); }
.bubble-act.bubble-del:hover { color: var(--danger); }
/* ⚠️ На тач-экране :hover не наступает вовсе, и весь ряд действий (ответить,
   переслать, закрепить, удалить) был бы недоступен физически. Показываем его
   приглушённым — тот же приём, что у «трёх точек» в чате комнаты класса. */
@media (hover: none) { .bubble-actions { opacity: .5; } }
.bubble-row.mine .bubble-actions { order: -1; }
.bubble.pinned { box-shadow: inset 0 0 0 1px var(--accent-dim); }
/* Пересланное / цитата ответа внутри пузыря */
.bubble-fwd { font-size: .72em; color: var(--accent-text); margin-bottom: 3px; }
.bubble-fwd b { font-weight: 600; }
/* Отзыв ученика после низкой оценки урока — лежит в чате администратора с учеником, но
   ученик его не видит (Message.hidden_from_sender). Тон предупреждения, а не обычный
   пузырь собеседника: это не реплика, которую ученик написал админу. */
.bubble-row.theirs .bubble.is-hidden-note { background: color-mix(in srgb, var(--danger) 13%, var(--surface-2)); }
.bubble-hidden-note { display: flex; align-items: center; gap: 5px; margin-bottom: 4px;
  font-size: .72em; font-weight: 600; color: var(--danger); }
.bubble-hidden-note svg { width: 13px; height: 13px; flex: 0 0 auto; }
.bubble-quote { display: flex; flex-direction: column; gap: 1px; padding: 4px 9px; margin-bottom: 5px; max-width: 100%;
  border-left: 2px solid var(--accent); background: rgba(255,255,255,.05); border-radius: 5px; cursor: pointer; }
.bubble-quote-name { font-size: .72em; color: var(--accent-text); font-weight: 600; }
.bubble-quote-text { font-size: .8em; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Панель «ответ на сообщение» над полем ввода */
.reply-bar { display: flex; align-items: center; gap: 10px; padding: 8px 16px; border-top: 1px solid var(--border); background: var(--surface); }
.reply-bar-ico { color: var(--accent-text); display: inline-flex; flex: 0 0 auto; }
.reply-bar-ico svg { width: 16px; height: 16px; }
.reply-bar-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; border-left: 2px solid var(--accent); padding-left: 9px; }
.reply-bar-name { font-size: .74em; color: var(--accent-text); font-weight: 600; }
.reply-bar-text { font-size: .82em; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.reply-bar-x { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 1em; flex: 0 0 auto; padding: 4px 6px; }
.reply-bar-x:hover { color: var(--danger); }
/* Панель закреплённых сообщений наверху чата */
.chat-pinned { flex: 0 0 auto; border-bottom: 1px solid var(--border); background: var(--surface);
  padding: 8px 14px; max-height: 98px; overflow-y: auto; scrollbar-width: none; -ms-overflow-style: none; }
.chat-pinned::-webkit-scrollbar { display: none; }
.pinned-head { display: flex; align-items: center; gap: 6px; font-size: .7em; color: var(--accent-text);
  margin-bottom: 6px; text-transform: uppercase; letter-spacing: .05em; }
.pinned-head svg { width: 13px; height: 13px; }
.pinned-list { display: flex; flex-direction: column; gap: 3px; }
.pinned-item { display: flex; align-items: center; gap: 8px; padding: 5px 9px; border-radius: 7px; cursor: pointer;
  border-left: 2px solid var(--accent-dim); background: var(--surface-2); }
.pinned-item:hover { background: var(--bg); }
.pinned-text { flex: 1; min-width: 0; font-size: .82em; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pinned-unpin { background: none; border: none; color: var(--muted); cursor: pointer; flex: 0 0 auto;
  font-size: .9em; padding: 0 4px; line-height: 1; }
.pinned-unpin:hover { color: var(--danger); }
/* ── Карточки списка чатов ─────────────────────────────────────────
   Единая структура: [аватар+бейдж] · [имя/подзаголовок/мета] · [дата/действие].
   Каждая карточка — ровно 3 текстовые строки → список выглядит ровным. */
.chat-contact { display: flex; gap: 12px; padding: 10px 14px; cursor: pointer; align-items: center;
  position: relative; transition: background .12s;
  user-select: none; -webkit-user-select: none; }   /* долгое нажатие открывает меню — без выделения текста */
.chat-contact + .chat-contact { border-top: 1px solid var(--border); }
.chat-contact:hover { background: var(--surface-2); }
.chat-contact.active { background: var(--surface-2); box-shadow: inset 3px 0 0 var(--accent); }
.chat-contact.pinned { background: color-mix(in srgb, var(--accent) 4%, transparent); }
.chat-contact.locked { cursor: default; }
/* Закрытый чат (учителя убрали из класса): не нажимается, приглушён */
.chat-contact.closed { cursor: default; }
.chat-contact.closed:hover { background: transparent; }
.chat-contact.closed .cc-av, .chat-contact.closed .cc-name { opacity: .55; }

/* Аватар + бейдж непрочитанного (нижний-левый угол, как в мессенджерах) */
.cc-av { position: relative; flex: 0 0 auto; }
.chat-contact .avatar { width: 44px; height: 44px; font-size: 1.02em; }
.cc-unread { position: absolute; left: -3px; bottom: -2px; background: var(--accent); color: var(--on-accent);
  font-size: .62em; font-weight: 700; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 99px;
  display: grid; place-items: center; line-height: 1; box-sizing: border-box; border: 2px solid var(--bg);
  font-variant-numeric: tabular-nums; }

/* Центральная колонка */
.cc-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.cc-name-wrap { display: flex; align-items: center; gap: 5px; min-width: 0; }
.cc-name { font-size: .92em; font-weight: 600; color: var(--text); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cc-pin { font-size: .6em; flex: 0 0 auto; line-height: 1; opacity: .85; }
.cc-name-lock { font-size: .8em; flex: 0 0 auto; line-height: 1; }
.cc-sub { font-size: .8em; color: var(--muted); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cc-meta { display: flex; align-items: center; gap: 8px; min-width: 0; }
.cc-bal { flex: 0 0 auto; font-size: .72em; font-weight: 600; color: var(--badge-accent);
  background: color-mix(in srgb, var(--badge-accent) 10%, transparent); border-radius: 99px; padding: 1px 8px;
  white-space: nowrap; font-variant-numeric: tabular-nums; }
.cc-bal.low { color: var(--danger); background: color-mix(in srgb, var(--danger) 13%, transparent); }
.cc-tenure { flex: 0 0 auto; font-size: .72em; color: var(--text-dim); white-space: nowrap; }
.cc-role { flex: 0 0 auto; font-size: .72em; color: var(--text-dim); white-space: nowrap; }
.cc-id { flex: 0 0 auto; font-size: .68em; color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }

/* Правая колонка: дата/время сверху, действие снизу */
.cc-side { flex: 0 0 auto; align-self: stretch; display: flex; flex-direction: column;
  align-items: flex-end; justify-content: space-between; gap: 6px; }
.cc-when { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; line-height: 1.2; }
.cc-when-date { font-size: .68em; color: var(--text-dim); white-space: nowrap; }
/* Время — тусклее даты (правка владельца продукта: одинаково яркие, они сливались в одно). */
.cc-when-time { font-size: .66em; color: color-mix(in srgb, var(--muted) 65%, transparent); white-space: nowrap; font-variant-numeric: tabular-nums; }

/* Меню «⋯»: резервирует место (нет скачков) — на ховер проявляется, на тач видно всегда */
.cc-menu { flex: 0 0 auto; background: transparent; border: none; color: var(--muted); cursor: pointer;
  font-size: 1.05em; line-height: 1; padding: 4px 9px; border-radius: 6px; opacity: 0;
  transition: opacity .12s, color .12s, background .12s; }
.chat-contact:hover .cc-menu { opacity: .6; }
.cc-menu:hover { opacity: 1 !important; color: var(--text); background: var(--surface); }
.chat-contact.locked .cc-menu { display: none; }
.cc-act { flex: 0 0 auto; background: transparent; border: 1px solid var(--border-2); color: var(--text-dim);
  cursor: pointer; font-size: .72em; border-radius: 99px; padding: 4px 11px; font-family: var(--font); white-space: nowrap; }
.cc-act:hover { border-color: var(--accent-border); color: var(--accent-text); }
/* Тач: меню «⋯» видно всегда (плюс долгое нажатие по карточке открывает то же меню) */
@media (hover: none) { .cc-menu { opacity: .55; font-size: 1.2em; padding: 6px 10px; } }

/* Подсказка «Пополните баланс» для заблокированного чата */
.cc-pay { position: absolute; right: 14px; bottom: 9px; font-size: .72em; color: var(--danger);
  white-space: nowrap; pointer-events: none; }
.chat-pane { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.chat-header { display: flex; gap: 12px; align-items: center; padding: 10px 16px;
  border-bottom: 1px solid var(--border); background: var(--surface-2); }
.chat-back { display: none; background: transparent; border: none; color: var(--text-dim); cursor: pointer;
  font-size: 1.6em; line-height: 1; padding: 0 4px; flex: 0 0 auto; }
.chat-back:hover { color: var(--accent-text); }
.chat-h-main { min-width: 0; }
.chat-h-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* «Фон» + «Профиль» — общей группой у правого края (см. messages.js chat-h-acts:
   margin-left:auto на обёртке, а не на каждой кнопке — «Фон» есть не всегда). */
.chat-h-acts { display: flex; align-items: center; gap: 8px; margin-left: auto; flex: 0 0 auto; }
.chat-profile { flex: 0 0 auto; }
.chat-h-name { font-size: .96em; color: var(--text); font-weight: 500; }
.chat-h-role { font-size: .72em; color: var(--muted); }
.chat-body { flex: 1; min-height: 0; overflow-y: auto; padding: 18px; display: flex; flex-direction: column; gap: 8px;
  scrollbar-width: thin; scrollbar-color: var(--border-2) transparent; }
/* Тонкий ненавязчивый скролл (как в Telegram) — появляется при наведении */
.chat-body::-webkit-scrollbar, .chat-contacts::-webkit-scrollbar { width: 7px; }
.chat-body::-webkit-scrollbar-track, .chat-contacts::-webkit-scrollbar-track { background: transparent; }
.chat-body::-webkit-scrollbar-thumb, .chat-contacts::-webkit-scrollbar-thumb {
  background: transparent; border-radius: 99px; border: 2px solid transparent; background-clip: padding-box; }
.chat-body:hover::-webkit-scrollbar-thumb, .chat-contacts:hover::-webkit-scrollbar-thumb {
  background: var(--border-2); background-clip: padding-box; }
.chat-body::-webkit-scrollbar-thumb:hover, .chat-contacts::-webkit-scrollbar-thumb:hover {
  background: var(--muted); background-clip: padding-box; }
.chat-contacts { scrollbar-width: thin; scrollbar-color: var(--border-2) transparent; }
.bubble-row { display: flex; }
.bubble-row.mine { justify-content: flex-end; }
.bubble { max-width: 74%; padding: 7px 12px; border-radius: 14px; font-size: .86em; line-height: 1.45; }
.bubble-row.theirs .bubble { background: var(--surface-2); border: 1px solid transparent;
  border-bottom-left-radius: 4px; color: var(--text); }
.bubble-row.mine .bubble { background: var(--chat-mine); border-bottom-right-radius: 4px; color: var(--on-chat-mine); }
.bubble-text { white-space: pre-wrap; word-break: break-word; display: block; }
.bubble-time { display: block; font-size: .64em; color: var(--muted); text-align: right; margin-top: 3px; }
.bubble-row.mine .bubble-time { color: color-mix(in srgb, var(--on-chat-mine) 65%, transparent); }
/* max-width 100% — картинка не вылезает за пузырь на узких экранах */
.bubble-img { max-width: min(240px, 100%); max-height: 240px; height: auto; border-radius: 9px; display: block; margin-bottom: 4px; }
.bubble-file { display: inline-flex; align-items: center; gap: 6px; color: inherit; font-size: .86em;
  text-decoration: underline; margin-bottom: 2px; }
/* Кнопки системного сообщения (заявка на демо, ссылка в класс…) — во всю ширину пузыря,
   в столбик: варианты обычно исключают друг друга («Подтвердить»/«Отказаться»), и рядом
   в узкой колонке чата они бы обрезались. */
.bubble-btns { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.bubble-btns .btn { width: 100%; justify-content: center; }
/* Скрепка / поле / отправка — единый вид с чатом комнаты класса (.room-chat-*):
   скрепка — залитый квадрат --surface-2 с рамкой, кнопки одного размера и скругления. */
.chat-attach { flex: 0 0 auto; width: 32px; height: 32px; display: grid; place-items: center;
  border-radius: 10px; border: 1px solid var(--border-2); background: var(--surface-2);
  color: var(--text-dim); cursor: pointer; transition: color .12s, border-color .12s; }
.chat-attach svg { width: 16px; height: 16px; display: block; }
.chat-attach:hover { background: var(--surface-2); color: var(--text); border-color: var(--accent-border); }
.chat-attach:disabled { opacity: .5; cursor: default; }
.chat-inputbar { display: flex; gap: 6px; padding: 8px 10px; border-top: 1px solid var(--border); align-items: center; }
.chat-input { flex: 1 1 auto; resize: none; background: var(--bg); border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  color: var(--text); padding: 7px 12px; font-family: var(--font); font-size: .84em; line-height: 1.4; max-height: 120px;
  overflow-y: hidden; touch-action: manipulation; }
.chat-input:focus { outline: none; border-color: var(--accent-border); }
.chat-input::placeholder { font-size: .92em; color: var(--muted); }
/* Прикреплённый файл (ещё не отправлен) */
.chat-staged { display: flex; align-items: center; gap: 8px; margin: 0 12px; padding: 8px 12px;
  background: var(--surface-2); border: 1px solid var(--border-2); border-radius: var(--radius-sm); }
.staged-ico { flex: 0 0 auto; }
.staged-name { flex: 1; min-width: 0; font-size: .84em; color: var(--text-dim); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.staged-x { flex: 0 0 auto; background: transparent; border: none; color: var(--muted); cursor: pointer; font-size: .9em; }
.staged-x:hover { color: var(--danger); }
/* Состояние загрузки: имя + процент + прогресс-бар */
.chat-staged.uploading { flex-direction: column; align-items: stretch; gap: 7px; }
.staged-top { display: flex; align-items: center; gap: 8px; }
.staged-pct { margin-left: auto; flex: 0 0 auto; font-size: .78em; color: var(--accent-text); font-variant-numeric: tabular-nums; }
/* Меню скрепки мессенджера (админ): «Медиа и файлы» / «Карточка учителя». Один узел в
   body — как меню «три точки» комнаты: чат бывает узким, и абсолют внутрь не кладётся. */
.chat-attach-menu { position: fixed; z-index: 230; display: flex; flex-direction: column;
  min-width: 220px; padding: 6px; gap: 2px; background: var(--surface-2);
  border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  box-shadow: 0 12px 30px rgba(0, 0, 0, .45); }
.chat-attach-menu button { background: transparent; border: none; color: var(--text);
  font-family: var(--font); font-size: .9em; text-align: left; padding: 9px 12px;
  border-radius: var(--radius-sm); cursor: pointer; }
.chat-attach-menu button:hover { background: var(--surface); }
/* Сообщение-карточка: плитка анкеты внутри пузыря. «Просмотрено» — метка витрины,
   в чате она только шумела бы.
   ⚠️ Ширина карточки — БЕЗ процентов в width: пузырь — флекс-элемент, который поджимает
   полоса действий, и width:min(320px,100%) внутри сжимаемого родителя WebKit считал по
   внутренней ширине плитки — карточка вылезала за облако. width:auto + потолок 320px
   даёт ровно «сколько есть, но не больше 320». */
.bubble-tcard { margin: 2px 0 4px; }
.bubble-tcard .tg-card { width: auto; max-width: 320px; margin-bottom: 0; }
.bubble-tcard .tg-seen { display: none; }
/* Пузырю с карточкой разрешено быть шире обычного (74%): карточке нужно место,
   на телефоне иначе она сжимается до нечитаемого. Потолок подобран под плитку:
   320px карточка + 24px полей пузыря. */
.bubble:has(> .bubble-tcard) { max-width: min(92%, 348px); }
/* Язычок «открыть» в чате не торчит вбок за край облака сообщения — лежит ПОДОЛУ лица
   сплошной полосой: лицо снизу без скругления, полоса снизу скруглена, вместе они
   читаются одной карточкой. Наведение-«оттягивание» вбок не имеет смысла — гасим. */
.bubble-tcard .tg-card { --tg-strip: 42px; }
.bubble-tcard .tg-face { margin-right: 0; margin-bottom: var(--tg-strip);
  border-radius: var(--tg-r) var(--tg-r) 0 0; }
.bubble-tcard .tg-tab { top: auto; bottom: 0; left: 0; width: auto; height: var(--tg-strip);
  border-radius: 0 0 var(--tg-r) var(--tg-r); }
.bubble-tcard .tg-tab-label { writing-mode: horizontal-tb; transform: none;
  top: 0; bottom: 0; left: 0; right: 0; width: auto; }
.bubble-tcard .tg-card:hover .tg-tab, .bubble-tcard .tg-card:focus-visible .tg-tab,
.bubble-tcard .tg-card:active .tg-tab, .bubble-tcard .tg-card.pulling .tg-tab {
  transform: none; }
.staged-progress { height: 5px; background: var(--bg); border-radius: 99px; overflow: hidden; }
.staged-fill { height: 100%; width: 0; background: var(--progress); border-radius: 99px; transition: width .15s ease; }
.staged-ready { flex: 0 0 auto; font-size: .74em; color: var(--accent-text); font-weight: 600; }
/* Кнопка отправки — в размер скрепки, единый вид с чатом комнаты класса */
.chat-send { flex: 0 0 auto; width: 32px; height: 32px; border-radius: 10px; border: none; background: var(--btn-primary);
  color: var(--on-btn-primary); font-size: 1em; cursor: pointer; display: grid; place-items: center; transition: background .12s; }
.chat-send svg { display: block; }
.chat-send:hover { background: var(--btn-primary-hover); }
.chat-empty { flex: 1; display: grid; place-items: center; align-content: center; color: var(--muted); text-align: center; gap: 8px; }

/* Панель «Ученики» учителя — справа от переписки, только у администратора (messages.js).
   Шапка чата остаётся на всю ширину, ниже две колонки. Ширина панели анимируется через
   Web Animations (CSS-переходы у владельца гасит «Уменьшение движения»), поэтому
   внутренний блок держит фиксированные 300px — содержимое не перестраивается по ходу. */
.chat-split { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; }
.chat-col { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; min-height: 0; }
.chat-students { flex: 0 0 auto; width: 300px; min-height: 0; overflow: hidden; background: var(--surface); }
.chat-students-inner { display: flex; flex-direction: column; width: 300px; height: 100%; min-height: 0;
  border-left: 1px solid var(--border); }
.chat-students.is-closed { display: none; }
.chat-students-foot { display: flex; justify-content: flex-end; }
.chat-students-arrow { display: inline-flex; }
.chat-students-btn.active { color: var(--accent-text); border-color: var(--accent-border); }
.chat-students-btn .csb-ico { display: none; }
.chat-students-btn .csb-ico svg { display: block; width: 17px; height: 17px; }

@media (max-width: 680px) {
  /* Один экран за раз: список ИЛИ открытый чат (как в Telegram) */
  .chat { grid-template-columns: 1fr; }
  .chat-side { border-right: none; }
  .chat-pane { display: none; }
  .chat.show-chat .chat-side { display: none; }
  .chat.show-chat .chat-pane { display: flex; }
  /* Удобный тап-таргет для «к списку» на мобильных */
  .chat-back { display: grid; place-items: center; min-width: 38px; height: 38px; margin: -4px 0 -4px -4px;
    font-size: 1.5em; border-radius: 10px; }
  .chat-back:active { background: var(--surface); }
  .chat-contacts { max-height: none; }

  /* Всё влезает в экран без прокрутки страницы: компактные отступы + точная высота.
     55px топбар + 10px верх + 10px низ = 75px служебных. */
  body.msg-view .app { padding-top: 10px; padding-bottom: 10px; }
  .msg-wrap { height: calc(100vh - 75px); height: calc(100dvh - 75px); }

  /* Компактная шапка чата (−20%) — освобождаем место под сообщения */
  .chat-header { gap: 8px; padding: 5px 8px; }
  .chat-header .avatar { width: 32px; height: 32px; font-size: .85em; }
  .chat-h-name { font-size: .9em; }
  .chat-h-acts { gap: 6px; }
  .chat-profile, .chat-bg-btn, .chat-students-btn { padding: 5px 10px; min-height: 32px; }
  .chat-students-btn { padding: 5px 8px; }
  .chat-students-btn .csb-text { display: none; }
  .chat-students-btn .csb-ico { display: inline-flex; }
  /* Панель учеников на телефоне — накладка на весь чат (рядом с лентой не помещается) */
  .chat-students { position: absolute; top: 0; right: 0; bottom: 0; width: 100%; z-index: 4; }
  .chat-students-inner { width: 100%; border-left: none; }

  /* Карточки списка чатов — плотнее, аватар меньше */
  .chat-contact { padding: 9px 12px; gap: 10px; }
  .chat-contact .avatar { width: 40px; height: 40px; font-size: .95em; }

  /* Поиск контактов — ниже; подсказка мельче (сам шрифт input 16px — иначе iOS зумит при фокусе) */
  .chat-search-wrap .input { padding-top: 5px; padding-bottom: 5px; }
  .chat-search-wrap .input::placeholder { font-size: 14px; }

  /* Пузыри: шрифт на 1–2 пункта меньше, вертикальные отступы −20%, лента плотнее */
  .chat-body { padding: 12px 10px; gap: 6px; }
  .bubble { font-size: .84em; padding: 6px 11px; max-width: 82%; }

  /* Поле ввода уже, подсказка мельче, кнопки компактнее */
  .chat-inputbar { padding: 4px 8px; gap: 6px; }
  .chat-input { padding: 5px 12px; }
  .chat-input::placeholder { font-size: 14px; }
  .chat-send, .chat-attach { width: 36px; height: 36px; }

  /* Поле поиска по чату: на весь ряд, не сжимается в «слс».
     При активном поиске прячем аватар/имя/группу кнопок — место под ввод (как в Telegram). */
  .chat-search-box { flex: 1 1 auto; }
  .chat-header.searching .avatar,
  .chat-header.searching .chat-h-main,
  .chat-header.searching .chat-h-acts { display: none; }
  .chat-header.searching .chat-search-box { flex: 1 1 auto; min-width: 0; }
}

/* ── CRM (канбан) ──────────────────────────────────────────────── */
/* CRM на всю ширину: на странице CRM снимаем ограничение ширины у .app */
body.crm-full .app { max-width: none; }
.crm-page { width: 100%; }
.crm-head-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.crm-tabs-wrap { overflow-x: auto; margin-bottom: 18px; scrollbar-width: thin; }
.crm-tabs { border-bottom: 1px solid var(--border); flex-wrap: nowrap; width: max-content; min-width: 100%; }
.crm-tab-add { color: var(--accent-text); }
.crm-tabs .tab.tab-dragging { opacity: .45; }
.crm-board { display: flex; gap: 14px; overflow-x: auto; padding-bottom: 16px; align-items: flex-start;
  scrollbar-width: thin; scrollbar-color: var(--border-2) transparent; }
.crm-board::-webkit-scrollbar, .crm-col-body::-webkit-scrollbar { height: 8px; width: 8px; }
.crm-board::-webkit-scrollbar-thumb, .crm-col-body::-webkit-scrollbar-thumb {
  background: var(--border-2); border-radius: 99px; border: 2px solid transparent; background-clip: padding-box; }
.crm-col { flex: 0 0 240px; background: var(--surface); border: 1px solid transparent;
  border-radius: var(--radius); display: flex; flex-direction: column; max-height: calc(100vh - 180px);
  transition: border-color .12s, background .12s; }
.crm-col.drop { border-color: var(--accent); background: var(--surface-2); }
.crm-col-head { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--border); }
.crm-col-dot { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; }
.crm-col-name { font-size: .9em; color: var(--text); flex: 1; font-weight: 500; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.crm-col-count { font-size: .72em; color: var(--text-dim); background: var(--surface-2); border-radius: 99px; padding: 1px 8px; }
.crm-col-menu { background: transparent; border: none; color: var(--muted); cursor: pointer; font-size: 1.1em; padding: 0 2px; line-height: 1; }
.crm-col-menu:hover { color: var(--text); }
.crm-col-body { flex: 1; overflow-y: auto; padding: 10px; display: flex; flex-direction: column; gap: 8px; min-height: 44px; }
.crm-card { background: var(--bg); border: 1px solid transparent; border-radius: 9px; padding: 10px 12px;
  cursor: grab; transition: border-color .12s; }
.crm-card:hover { border-color: var(--accent-border); }
.crm-card.dragging { opacity: .45; }
.crm-card-title { font-size: .88em; color: var(--text); }
.crm-card-contact { font-size: .76em; color: var(--accent-text); margin-top: 4px; overflow-wrap: anywhere; }
.crm-card-note { font-size: .76em; color: var(--muted); margin-top: 4px; line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.crm-add-card { margin: 10px; background: transparent; border: 1px dashed var(--border-2); color: var(--muted);
  border-radius: 8px; padding: 8px; cursor: pointer; font-family: var(--font); font-size: .82em; transition: color .12s, border-color .12s; }
.crm-add-card:hover { color: var(--accent-text); border-color: var(--accent-border); }

/* ── Права / группы доступа ────────────────────────────────────── */
.group-badge { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border-radius: 99px;
  background: rgba(96,165,250,.1); border: 1px solid rgba(96,165,250,.35); color: #93c5fd; font-size: .72em; white-space: nowrap; }
.perm-grid { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }
.perm-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 9px 12px;
  background: var(--bg); border: 1px solid transparent; border-radius: var(--radius-sm); }
.perm-row span { font-size: .86em; color: var(--text-dim); }
/* ⚠️ Ряд-тумблер — это <label>, и внутри `.field` его перебивало `.field label
   { display: block }` (два селектора против одного класса): подпись и тумблер слипались
   в строку вместо того, чтобы разойтись по краям ряда. Касалось и прав менеджера. */
.perm-grid label.perm-row { display: flex; }

.crm-card-badge { display: inline-block; margin-top: 5px; font-size: .68em; color: var(--badge-accent);
  background: color-mix(in srgb, var(--badge-accent) 10%, transparent); border: 1px solid var(--accent-border); border-radius: 99px; padding: 1px 7px; }
.crm-account { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.crm-account-id { font-size: .86em; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.crm-account-open { display: inline-flex; align-items: center; gap: 6px; }
.crm-account-ico { display: inline-flex; }
.crm-account-ico svg { width: 14px; height: 14px; }

/* Перетаскивание этапов (за заголовок) */
.crm-col-head { cursor: grab; }
.crm-col-head:active { cursor: grabbing; }
.crm-col-grip { color: var(--muted); font-size: .85em; flex: 0 0 auto; letter-spacing: -1px; }
.crm-col.col-dragging { opacity: .45; }
.crm-col.drop { outline: 2px dashed var(--accent-dim); outline-offset: -2px; }

/* ── CRM: теги, пользовательские поля, поиск ───────────────────── */
.crm-tag { display: inline-flex; align-items: center; font-size: .68em; line-height: 1.4; padding: 1px 8px;
  border-radius: 99px; white-space: nowrap; color: var(--tag, var(--muted));
  background: color-mix(in srgb, var(--tag, var(--muted)) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--tag, var(--muted)) 45%, transparent); }
.crm-card-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }
.crm-tag-pick { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.crm-tag-btn { cursor: pointer; font-family: var(--font); opacity: .55; transition: opacity .12s; }
.crm-tag-btn:hover { opacity: .85; }
.crm-tag-btn.on { opacity: 1; box-shadow: 0 0 0 2px color-mix(in srgb, var(--tag, var(--accent)) 45%, transparent); }
.crm-add-tag, .crm-add-field { border-style: dashed !important; }
.crm-fields-sec { display: flex; flex-direction: column; gap: 12px; margin-top: 4px;
  padding-top: 12px; border-top: 1px solid var(--border); }
.crm-field-del { padding: 2px 6px !important; min-height: 0 !important; opacity: .6; }
.crm-field-del:hover { opacity: 1; }

/* Форма карточки: секции-заголовки + компактные строки «подпись — поле» */
.crm-form { display: flex; flex-direction: column; gap: 9px; }
.crm-form .form-actions { margin-top: 8px; }
.crm-form-sec { margin: 12px 0 0; font-size: .72em; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted); }
.crm-frow { display: grid; grid-template-columns: 112px minmax(0, 1fr); gap: 10px; align-items: center; }
.crm-frow-top { align-items: start; }
.crm-frow-top .crm-frow-lbl { padding-top: 8px; }
.crm-frow-lbl { display: flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden;
  font-size: .78em; color: var(--text-dim); }
.crm-frow-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Заметка — фикс. высота по умолчанию (растягивается вручную за уголок, но не бесконечно) */
.crm-form .crm-note { height: 84px; min-height: 60px; max-height: 220px; }

/* Вложения заметки: сетка миниатюр */
.crm-atts { display: flex; flex-wrap: wrap; gap: 8px; }
.crm-att { position: relative; width: 64px; height: 64px; }
.crm-att-img { width: 100%; height: 100%; object-fit: cover; border-radius: 8px;
  border: 1px solid var(--border); cursor: zoom-in; display: block; }
.crm-att-del { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; padding: 0;
  border-radius: 50%; border: none; background: var(--surface-2); color: var(--muted);
  cursor: pointer; display: flex; align-items: center; justify-content: center; }
.crm-att-del svg { width: 11px; height: 11px; }
.crm-att-del:hover { color: var(--danger); }
.crm-att-add { width: 64px; height: 64px; border-radius: 8px; border: 1px dashed var(--border-2);
  display: flex; align-items: center; justify-content: center; color: var(--muted);
  font-size: 1.4em; cursor: pointer; transition: border-color .12s, color .12s; }
.crm-att-add:hover { border-color: var(--accent); color: var(--accent-text); }

/* Комментарии карточки: свежие сверху, у каждого дата и время */
.crm-comments { display: flex; flex-direction: column; gap: 8px; max-height: 220px; overflow-y: auto;
  scrollbar-width: thin; }
.crm-comment { background: var(--bg); border: 1px solid var(--border); border-radius: 9px; padding: 8px 10px; }
.crm-comment-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 3px; }
.crm-comment-author { font-size: .76em; font-weight: 600; color: var(--text); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.crm-comment-time { font-size: .7em; color: var(--text-dim); white-space: nowrap; }
.crm-comment-del { background: transparent; border: none; color: var(--muted); cursor: pointer;
  padding: 0 2px; line-height: 1; }
.crm-comment-del svg { width: 11px; height: 11px; }
.crm-comment-del:hover { color: var(--danger); }
.crm-comment-text { font-size: .82em; color: var(--text); white-space: pre-wrap; word-break: break-word; }
.crm-comments-empty { color: var(--muted); font-size: .8em; }
/* Новый комментарий: поле + кнопка в одну строку (на узких экранах кнопка уходит вниз) */
.crm-comment-new { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-end; }
.crm-comment-new .textarea { flex: 1 1 200px; min-height: 38px; height: 38px; }
.crm-comment-new .btn { flex: 0 0 auto; }

.crm-search-res { display: flex; flex-direction: column; gap: 8px; max-height: 60vh; overflow-y: auto; margin-top: 6px; }
.crm-search-empty { color: var(--muted); font-size: .85em; padding: 14px 4px; text-align: center; }
.crm-search-row { display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 10px 12px; background: var(--bg); border: 1px solid transparent; border-radius: 9px;
  cursor: pointer; transition: border-color .12s; }
.crm-search-row:hover { border-color: var(--accent-border); }
.crm-search-main { min-width: 0; }
.crm-search-title { font-size: .9em; color: var(--text); font-weight: 500; }
.crm-search-loc { display: flex; align-items: center; gap: 6px; font-size: .74em; color: var(--text-dim);
  white-space: nowrap; flex: 0 0 auto; }
.crm-search-pipe { color: var(--muted); }

/* ── CRM: переключатель «доска / список» ───────────────────────── */
.crm-viewsw { display: inline-flex; padding: 2px; gap: 2px; background: var(--surface); border-radius: 9px; }
.crm-viewsw-btn { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 28px;
  padding: 0; background: transparent; border: none; border-radius: 7px; color: var(--muted); cursor: pointer;
  transition: color .12s, background .12s; }
.crm-viewsw-btn svg { width: 15px; height: 15px; }
.crm-viewsw-btn:hover { color: var(--text); }
.crm-viewsw-btn.on { background: color-mix(in srgb, var(--selected) 16%, transparent); color: var(--selected); }

/* ── CRM: вид списком — этапы секциями, карточка одной строкой ──── */
.crm-list { display: flex; flex-direction: column; gap: 14px; padding-bottom: 16px; }
.crm-lsec { background: var(--surface); border-radius: var(--radius); overflow: hidden; }
.crm-lsec-head { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-bottom: 1px solid var(--border); }
.crm-lsec-head .crm-col-name { flex: 0 1 auto; }
.crm-lsec-head .crm-col-count { margin-right: auto; }
.crm-lrows { display: flex; flex-direction: column; }
.crm-lrow { display: grid; align-items: center; gap: 4px 16px; padding: 9px 14px; cursor: pointer;
  grid-template-columns: minmax(140px, 1.3fr) minmax(120px, 1.2fr) minmax(0, 1fr) minmax(0, 1.3fr) auto;
  transition: background .12s; }
.crm-lrow + .crm-lrow { border-top: 1px solid var(--border); }
.crm-lrow:hover { background: var(--surface-2); }
.crm-lrow-main { display: flex; align-items: center; gap: 8px; min-width: 0; }
.crm-lrow-main .crm-card-badge { margin-top: 0; flex: 0 0 auto; white-space: nowrap; }
.crm-lrow-title { font-size: .88em; color: var(--text); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-lrow-contact { font-size: .78em; color: var(--accent-text); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-lrow-tags { display: flex; flex-wrap: wrap; gap: 4px; min-width: 0; }
.crm-lrow-note { font-size: .78em; color: var(--muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-lrow-date { font-size: .74em; color: var(--text-dim); white-space: nowrap; font-variant-numeric: tabular-nums; }
.crm-lnone { padding: 10px 14px; font-size: .8em; color: var(--muted); }
@media (max-width: 760px) {
  /* На телефоне строка складывается в две: имя + дата, под ними контакт; заметка и теги — ниже. */
  .crm-lrow { grid-template-columns: minmax(0, 1fr) auto; }
  .crm-lrow-main { grid-column: 1; }
  .crm-lrow-date { grid-column: 2; grid-row: 1; }
  .crm-lrow-contact, .crm-lrow-tags, .crm-lrow-note { grid-column: 1 / -1; }
  .crm-lrow-tags:empty, .crm-lrow-note:empty { display: none; }
}

/* ── CRM: рассылка по этапу (рупор) ────────────────────────────── */
.crm-col-bc:hover { color: var(--accent-text); }
.crm-bc { display: flex; flex-direction: column; gap: 10px; }
.crm-bc .crm-form-sec { margin-top: 6px; }
.crm-bc-stage { display: flex; align-items: center; gap: 8px; font-size: .86em; min-width: 0; }
.crm-bc-stage-name { color: var(--text); font-weight: 500; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crm-bc-stage-n { color: var(--text-dim); font-size: .9em; white-space: nowrap; margin-left: auto; }
.crm-bc-text { height: 110px; min-height: 80px; max-height: 260px; }
.crm-bc-chs { display: flex; flex-direction: column; gap: 4px; }
.crm-bc .crm-bc-ch { display: flex; align-items: center; gap: 10px; padding: 8px 10px; margin: 0;
  background: var(--bg); border-radius: 9px; cursor: pointer; }
.crm-bc-ch input { width: 16px; height: 16px; margin: 0; flex: 0 0 auto; accent-color: var(--accent); cursor: pointer; }
.crm-bc-ch-name { font-size: .88em; color: var(--text); }
.crm-bc-ch-hint { margin-left: auto; font-size: .74em; color: var(--text-dim); white-space: nowrap; }
.crm-bc-ch.off { cursor: default; }
.crm-bc-ch.off .crm-bc-ch-name { color: var(--muted); }
.crm-bc-ch.off input { cursor: default; }
.crm-bc-subject { display: flex; flex-direction: column; gap: 4px; }
.crm-bc-segs { display: flex; gap: 2px; padding: 2px; background: var(--bg); border-radius: 10px; }
.crm-bc-seg { flex: 1 1 0; min-width: 0; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 7px 6px; background: transparent; border: none; border-radius: 8px; color: var(--text-dim);
  font-family: var(--font); font-size: .82em; cursor: pointer; white-space: nowrap; transition: color .12s, background .12s; }
.crm-bc-seg .ico svg { width: 14px; height: 14px; }
.crm-bc-seg:hover { color: var(--text); }
.crm-bc-seg.on { background: color-mix(in srgb, var(--selected) 16%, transparent); color: var(--selected); }
.crm-bc-when-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.crm-bc-when-lbl { font-size: .82em; color: var(--text-dim); flex: 0 0 auto; }
.crm-bc-when-row .input, .crm-bc-when-row .select { flex: 1 1 110px; min-width: 0; width: auto; }
.crm-bc-when-note { font-size: .76em; color: var(--muted); flex: 0 0 auto; }
.crm-bc-note { font-size: .76em; color: var(--muted); line-height: 1.4; }
.crm-bc .form-actions { margin-top: 4px; }
.crm-bc .form-actions .btn-primary { display: inline-flex; align-items: center; gap: 6px; }
.crm-bc-planned { display: flex; flex-direction: column; gap: 6px; }
.crm-bc-planned:empty { display: none; }
.crm-bc-item { display: flex; align-items: flex-start; gap: 8px; padding: 8px 10px; background: var(--bg); border-radius: 9px; }
.crm-bc-item-main { flex: 1; min-width: 0; }
.crm-bc-item-when { display: flex; align-items: center; gap: 6px; font-size: .78em; color: var(--text);
  font-variant-numeric: tabular-nums; }
.crm-bc-item-when .ico svg { width: 13px; height: 13px; }
.crm-bc-item-ch { color: var(--text-dim); margin-left: 4px; }
.crm-bc-item-text, .crm-bc-sent-text { font-size: .8em; color: var(--text-dim); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.crm-bc-item-text { margin-top: 2px; }
.crm-bc-item-del:hover { color: var(--danger); }
.crm-bc-sent { display: flex; align-items: baseline; gap: 10px; font-size: .95em; padding: 2px 2px; }
.crm-bc-sent-when, .crm-bc-sent-stats { font-size: .74em; color: var(--muted); white-space: nowrap; flex: 0 0 auto;
  font-variant-numeric: tabular-nums; }
.crm-bc-sent-text { flex: 1; min-width: 0; }

/* ── InfoBot: уведомления в группы Telegram (CRM → InfoBot, js/modules/infobot) ── */
.ib { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.ib-head { display: flex; align-items: center; gap: 8px; min-width: 0; font-size: .86em; }
.ib-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; background: var(--muted); }
.ib-dot.on { background: var(--grade-good); }
.ib-dot.wait { background: var(--warn); }
.ib-dot.err { background: var(--danger); }
.ib-bot { color: var(--text); font-weight: 500; white-space: nowrap; text-decoration: none; flex: 0 0 auto; }
a.ib-bot:hover { color: var(--accent-text); }
.ib-sub { flex: 1 1 auto; min-width: 0; color: var(--text-dim); font-size: .92em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ib-chip { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto; padding: 5px 10px;
  border: 0; border-radius: 999px; background: var(--bg); color: var(--text-dim); font: inherit; font-size: .92em; cursor: pointer; }
.ib-chip:hover, .ib-chip.on { color: var(--text); }
.ib-chip .ico svg { width: 14px; height: 14px; }
.ib-chip-warn { width: 6px; height: 6px; border-radius: 50%; background: var(--warn); }
.ib-iconbtn { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
  width: 30px; height: 30px; padding: 0; border: 0; border-radius: 8px; background: transparent;
  color: var(--text-dim); cursor: pointer; }
.ib-iconbtn svg { width: 16px; height: 16px; }
.ib-iconbtn:hover, .ib-iconbtn.on { color: var(--text); background: var(--bg); }

/* Боковая карточка «Группы» / «Подключение» — СПРАВА от окна, а не внутри него (раскрытая
   в окне, она сдвигала форму, и окно выглядело как второе такое же). Высоту строки задаёт
   окно: содержимое карточки лежит абсолютно и в высоту строки не вкладывается, а
   прокручивается само. На телефоне (≤760px) карточки нет — панель открывается листом. */
/* ⚠️ min-width: 0 — иначе ячейка подложки (grid) не ýже min-content строки, и на средних
   экранах карточка уезжала за край вместо того, чтобы окно сжалось. */
.ib-pair { display: flex; align-items: stretch; justify-content: center; gap: 16px; width: 100%; max-width: 1020px;
  min-width: 0; }
.ib-pair > .modal { flex: 1 1 620px; min-width: 0; }
.ib-side { position: relative; flex: 0 1 380px; min-width: 300px; background: var(--surface);
  border-radius: var(--radius); box-shadow: var(--shadow); }
.ib-side-in { position: absolute; inset: 0; overflow-y: auto; overscroll-behavior: contain; padding: 22px; }
.ib-panel { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.ib-side-head { display: flex; align-items: center; justify-content: space-between; gap: 8px 10px; flex-wrap: wrap; min-width: 0; }
.ib-side-title { font-size: .98em; font-weight: 500; color: var(--text); white-space: nowrap; }
.ib-side-head .btn { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto; text-decoration: none; }
.ib-side-head .btn .ico svg { width: 14px; height: 14px; }
.ib-refresh { align-self: flex-start; display: inline-flex; align-items: center; gap: 5px; margin-top: -6px;
  padding: 4px 8px; font-size: .76em; color: var(--text-dim); }
.ib-refresh .ico svg { width: 12px; height: 12px; }
.ib-steps { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 4px;
  font-size: .84em; color: var(--text-dim); line-height: 1.45; }
.ib-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ib-row .input { flex: 1 1 220px; min-width: 0; width: auto; }
.ib-groups-title { font-size: .84em; color: var(--text); font-weight: 500; }
.ib-groups { display: flex; flex-direction: column; gap: 6px; }
.ib-group { display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: var(--bg); border-radius: 10px;
  font-size: .86em; min-width: 0; }
.ib-group-name { flex: 1 1 auto; min-width: 0; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ib-group.dim .ib-group-name { color: var(--text-dim); }
.ib-group-st { font-size: .82em; color: var(--muted); white-space: nowrap; }
.ib-group-st.warn { color: var(--warn); }
/* Кто в группе — тихой строкой под названием (по ней видно, какая группа чья) */
.ib-group-people { font-size: .82em; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ib-sub-title { margin: 6px 0 2px; font-size: .74em; color: var(--muted); }
.ib-sub-title:first-child { margin-top: 0; }
.ib-group-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.ib-group-by { font-size: .82em; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ib-pending .ib-group-name { color: var(--warn); }
.ib-pending { flex-wrap: wrap; }
.ib-pending-acts { margin-left: auto; display: inline-flex; gap: 6px; flex: 0 0 auto; }
.ib-leave:hover { color: var(--danger); }
.ib-sec { display: flex; flex-direction: column; gap: 4px; }
.ib-code-row { justify-content: space-between; }
.ib-code-row .btn { display: inline-flex; align-items: center; gap: 6px; }
.ib-code-row .btn .ico svg { width: 14px; height: 14px; }

.ib-compose { display: flex; flex-direction: column; gap: 8px; }
.ib-text { height: 120px; min-height: 90px; max-height: 300px; }
.ib-atts:empty { display: none; }
.ib-thumb { position: relative; width: 104px; height: 76px; border-radius: 8px; overflow: hidden; background: var(--bg);
  display: flex; align-items: center; justify-content: center; }
.ib-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ib-thumb-load { font-size: .78em; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.ib-thumb-del { position: absolute; top: 4px; right: 4px; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  background: color-mix(in srgb, var(--bg) 78%, transparent); color: var(--text); }
.ib-thumb-del svg { width: 13px; height: 13px; }
.ib-link { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ib-link > .ico { color: var(--text-dim); flex: 0 0 auto; }
.ib-link > .ico svg { width: 15px; height: 15px; }
.ib-link .input { flex: 1 1 160px; min-width: 0; width: auto; }
.ib-tools { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ib-tools .btn { display: inline-flex; align-items: center; gap: 6px; }
.ib-tools .btn .ico svg { width: 14px; height: 14px; }
.ib-note { font-size: .76em; color: var(--muted); line-height: 1.4; }
.ib-warn { color: var(--warn); }
.ib-empty { font-size: .86em; color: var(--text-dim); padding: 14px; background: var(--bg); border-radius: 10px; line-height: 1.45; }
.ib-send { display: flex; justify-content: flex-end; }
.ib-send .btn { display: inline-flex; align-items: center; gap: 6px; }

.ib-hist-title { margin-top: 4px; }
.ib-list { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
@media (min-width: 761px) {
  /* История прокручивается своим списком: окно не растёт с каждым уведомлением */
  .ib-list { max-height: min(46vh, 460px); overflow-y: auto; overscroll-behavior: contain; padding-right: 2px; }
}
.ib-hist-empty { padding: 6px 2px; }
.ib-more { align-self: center; }
.ib-item { display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; background: var(--bg); border-radius: 10px;
  cursor: pointer; min-width: 0; outline: none; }
.ib-item:hover, .ib-item:focus-visible { background: color-mix(in srgb, var(--text) 5%, var(--bg)); }
.ib-item-top { display: flex; align-items: center; gap: 8px; min-width: 0; font-size: .76em; color: var(--muted); }
.ib-item-when { flex: 0 0 auto; white-space: nowrap; }
.ib-item-author { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ib-item-author::before { content: '·'; margin-right: 8px; }
.ib-seen { margin-left: auto; flex: 0 0 auto; display: inline-flex; align-items: center; gap: 4px; padding: 2px 7px;
  border-radius: 999px; background: color-mix(in srgb, var(--text) 6%, transparent); color: var(--text-dim);
  font-size: 1.05em; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ib-seen .ico svg { width: 13px; height: 13px; }
.ib-seen.all { color: var(--grade-good); background: color-mix(in srgb, var(--grade-good) 12%, transparent); }
.ib-seen + .ib-seen { margin-left: 0; }   /* второй значок — вплотную к первому, а не делит отступ */
.ib-item-text { font-size: .86em; color: var(--text); line-height: 1.45; white-space: pre-wrap; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
.ib-item.open .ib-item-text { display: block; -webkit-line-clamp: unset; }
.ib-item-media { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; }
.ib-item-img { width: 64px; height: 48px; object-fit: cover; border-radius: 6px; display: block; }
/* Кнопка-ссылка уведомления — пилюлей, как она выглядит под сообщением в Telegram */
.ib-item-link { display: inline-flex; align-items: center; gap: 5px; min-width: 0; max-width: 100%; padding: 4px 11px;
  border-radius: 999px; background: color-mix(in srgb, var(--accent) 12%, transparent);
  font-size: .8em; color: var(--accent-text); text-decoration: none; }
.ib-item-link > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ib-item-link .ico svg { width: 13px; height: 13px; }
.ib-item-link:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); }
.ib-item-state { display: flex; align-items: center; gap: 6px; font-size: .76em; color: var(--text-dim); }
.ib-item-state.bad { color: var(--danger); }
.ib-sending { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex: 0 0 auto; }
.ib-gd { display: flex; flex-direction: column; gap: 2px; padding-top: 4px; }
.ib-gd .spinner { width: 18px; height: 18px; border-width: 2px; margin: 4px auto; }
.ib-gd-row { display: flex; align-items: baseline; gap: 10px; padding: 3px 0; font-size: .78em; min-width: 0; }
.ib-gd-name { flex: 0 1 auto; min-width: 0; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ib-gd-st { margin-left: auto; flex: 0 1 auto; min-width: 0; display: inline-flex; align-items: center; gap: 4px;
  color: var(--muted); text-align: right; }
.ib-gd-st > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ib-gd-st .ico svg { width: 12px; height: 12px; }
.ib-gd-st.seen { color: var(--grade-good); }
.ib-gd-st.bad { color: var(--danger); }
.ib-gd-fail { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; padding: 2px 0 8px; }
.ib-gd-why { font-size: .76em; color: var(--text-dim); overflow-wrap: anywhere; }
.ib-gd-unsure { color: var(--warn); }
.ib-gd-st.unsure { color: var(--warn); }
.ib-retry { display: inline-flex; align-items: center; gap: 6px; }
.ib-retry .ico svg { width: 13px; height: 13px; }

/* Перетаскивание пунктов меню (долгое нажатие → «дрожание») */
@keyframes nav-wiggle { 0% { transform: rotate(-1.6deg); } 50% { transform: rotate(1.6deg); } 100% { transform: rotate(-1.6deg); } }
.nav.reordering a { animation: nav-wiggle .28s ease-in-out infinite; cursor: grab; }
.nav.reordering a:active { cursor: grabbing; }
.nav a.nav-dragging { opacity: .45; }
.nav a { user-select: none; -webkit-user-drag: none; }
.nav.reordering a { -webkit-user-drag: element; }

/* ── Общая адаптивность под мобильные ──────────────────────────── */
@media (max-width: 760px) {
  /* iOS Safari зумит страницу при фокусе на поле с font-size < 16px — поэтому 16px на мобиле.
     !important — это безусловный «пол»: иначе классы вроде .chat-input (.9em) перебивают
     голый селектор textarea по специфичности и поле остаётся < 16px → глюк фокуса/зума,
     из-за которого по тапу не активируется ввод и не выезжает клавиатура в чате. */
  .input, .textarea, .select, input, textarea, select, .rt-edit { font-size: 16px !important; }
  .app { padding: 22px 14px 64px; }
  .topbar-inner { height: 54px; gap: 10px; padding: 0 12px; }
  .brand { display: none; }                 /* на мобиле логотип школы скрыт */
  .nav { gap: 2px; overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none; }
  .nav::-webkit-scrollbar { display: none; }
  .nav a { padding: 7px 9px; font-size: .82em; white-space: nowrap; }
  /* Имя на телефоне прячем, таблетка Premium остаётся рядом с аватаркой. */
  .who-text b { display: none; }
  .who-text:not(:has(.premium-pill)) { display: none; }
  /* Аватар+шестерёнка вплотную к иконке выхода — экономим место */
  .topbar-right { gap: 4px; }
  .who-btn { padding-right: 3px; }
  .page-head { margin-bottom: 20px; }
  .page-title { font-size: 1.4em; }
  .split { grid-template-columns: 1fr; }
  /* Тач-таргеты: кнопки не мельче 42px, в формах — во всю ширину */
  .btn { min-height: 42px; }
  .btn-sm { min-height: 36px; }
  /* .btn-compact — сознательное исключение из правила «не мельче 42px» (решение
     владельца продукта: «+ Создать…» в шапке «Материалов» — не критичное действие,
     а простой .btn-compact на мобиле пересекался бы с общим .btn{min-height:42px}
     и терял бы весь эффект уменьшения). Селектор `.btn.btn-compact` — специфичность
     выше голого `.btn`, применённого в этом же @media чуть выше. */
  .btn.btn-compact { min-height: 34px; padding: 7px 13px; font-size: .74em; }
  .form-actions { flex-wrap: wrap; }
  .form-actions .btn { flex: 1 1 auto; }
  /* Действия в строках-карточках: ровный «подвал» на всю ширину с разделителем —
     вместо кнопок, болтающихся справа при переносе */
  .row .row-actions { flex: 1 1 100%; margin-left: 0; border-top: 1px solid var(--border);
    padding-top: 10px; }
  .row .row-actions .btn { flex: 1 1 auto; }
  /* Вкладки не ломают строку — скроллятся горизонтально без полосы */
  .tabs { overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none; flex-wrap: nowrap; }
  .tabs::-webkit-scrollbar { display: none; }
  .tab { white-space: nowrap; flex: 0 0 auto; }
  /* Тост — над нижним safe-area, длинный текст переносится */
  .toast { bottom: calc(18px + env(safe-area-inset-bottom)); max-width: calc(100vw - 24px);
    white-space: normal; padding: 10px 16px; }
}
@media (max-width: 420px) {
  .nav a { padding: 7px 7px; font-size: .78em; }
  .btn { padding: 10px 14px; }
}

/* ── Настройки ─────────────────────────────────────────────────── */
.settings-layout { display: grid; grid-template-columns: 240px 1fr; gap: 20px; align-items: start; }
.settings-nav { display: flex; flex-direction: column; gap: 4px; position: sticky; top: 78px; }
.settings-nav-item { display: flex; align-items: center; gap: 11px; padding: 11px 14px; border-radius: var(--radius-sm);
  background: transparent; border: 1px solid transparent; color: var(--text-dim); cursor: pointer;
  font-family: var(--font); font-size: .9em; text-align: left; transition: background .12s, color .12s; }
.settings-nav-item:hover { background: var(--surface); color: var(--text); }
.settings-nav-item.active { background: var(--surface); color: var(--selected); border-color: var(--border); }
/* Иконка раздела настроек — линейный SVG в currentColor: гаснет вместе с подписью у
   неактивного пункта и подсвечивается у активного, поэтому цвет ей НЕ задаём отдельно. */
.sn-ico { display: inline-flex; flex: 0 0 auto; opacity: .75; }
.sn-ico svg { width: 17px; height: 17px; display: block; }
.settings-nav-item:hover .sn-ico, .settings-nav-item.active .sn-ico { opacity: 1; }
.settings-panel { min-width: 0; }
.settings-panel .card .field:last-child { margin-bottom: 0; }

/* ── Поиск по настройкам (views/settings-search.js) ─────────────────
   Строка стоит в шапке страницы, а пока в ней есть запрос, список совпадений занимает
   место меню и вкладок: они только прячутся, а не пересобираются, поэтому введённое в
   полях и выбранная вкладка не пропадают. Фон групп, а не рамка (см. docs/ui-kit.md). */
.ss-field { flex: 0 1 400px; min-width: 240px; }
.ss-field .input { padding-right: 36px; }
.ss-clear { position: absolute; right: 5px; top: 50%; transform: translateY(-50%); display: none;
  align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0;
  background: none; border: none; border-radius: 99px; color: var(--muted); cursor: pointer; }
.ss-clear svg { width: 15px; height: 15px; display: block; }
.ss-clear:hover { color: var(--text); background: var(--surface-2); }
.ss-field.has-text .ss-clear { display: inline-flex; }
.settings-layout.ss-off { display: none; }
.ss-results { display: flex; flex-direction: column; gap: 12px; max-width: 860px; }
.ss-results[hidden] { display: none; }
.ss-summary { padding: 0 4px; color: var(--muted); font-size: .85em; }
.ss-group { display: flex; flex-direction: column; gap: 2px; padding: 6px;
  background: var(--surface); border-radius: var(--radius); }
.ss-group-head, .ss-hit, .ss-more { width: 100%; box-sizing: border-box; background: none;
  border: 1px solid transparent; border-radius: var(--radius-sm); color: var(--text);
  font-family: var(--font); text-align: left; cursor: pointer; }
.ss-group-head { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 10px; font-size: .9em; font-weight: 500; }
.ss-hit { display: flex; flex-direction: column; gap: 3px; padding: 9px 10px; }
.ss-more { padding: 8px 10px; color: var(--accent-text); font-size: .82em; }
.ss-group-head:hover, .ss-hit:hover, .ss-more:hover { background: var(--surface-2); }
.ss-group-head:focus-visible, .ss-hit:focus-visible, .ss-more:focus-visible {
  outline: none; background: var(--surface-2); border-color: var(--accent-border); }
.ss-crumb { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; min-width: 0; }
.ss-crumb-cat { color: var(--text-dim); }
.ss-crumb-sep { color: var(--muted); }
.ss-crumb-tab { color: var(--accent-text); }
.ss-count { flex: 0 0 auto; min-width: 22px; padding: 1px 7px; border-radius: 99px;
  background: var(--surface-2); color: var(--text-dim); font-size: .78em; text-align: center;
  font-variant-numeric: tabular-nums; }
.ss-ctx { font-size: .74em; color: var(--muted); }
.ss-main { font-size: .88em; line-height: 1.5; color: var(--text); overflow-wrap: anywhere; }
.ss-aux { font-size: .8em; line-height: 1.45; color: var(--text-dim); overflow-wrap: anywhere; }
.ss-results mark { padding: 0 1px; border-radius: 3px; color: inherit;
  background: color-mix(in srgb, var(--accent) 30%, transparent); }
.ss-empty { padding: 56px 16px; text-align: center; }
.ss-empty-t { color: var(--text); }
.ss-empty-s { margin-top: 6px; color: var(--muted); font-size: .85em; }
/* Найденное место на открытой вкладке: рамка на пару секунд и слова запроса. Без анимации —
   у владельца продукта «уменьшение движения» включено, а рамка и так заметна. */
.ss-flash { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: var(--radius-sm);
  scroll-margin-top: 96px; }
::highlight(ss-hit) { background-color: color-mix(in srgb, var(--accent) 34%, transparent); color: inherit; }
@media (max-width: 760px) {
  .ss-field { flex: 1 1 100%; }
}
.kv { display: flex; justify-content: space-between; gap: 12px; font-size: .85em; color: var(--text-dim);
  padding: 6px 0; border-top: 1px solid var(--border); }
.kv b { color: var(--text-dim); font-weight: 600; }

/* Редактор меню по ролям (Настройки → Меню и навигация) */
.navcfg-head { font-size: .95em; margin-bottom: 12px; }
.navcfg-list { display: flex; flex-direction: column; gap: 8px; }
.navcfg-row { display: flex; align-items: center; gap: 10px; padding: 8px;
  background: var(--surface-2); border: 1px solid transparent; border-radius: var(--radius-sm); }
.navcfg-row.navcfg-off { opacity: .5; }
.navcfg-move { display: flex; flex-direction: column; gap: 3px; flex: 0 0 auto; }
.navcfg-arrow { padding: 1px 8px; line-height: 1.3; min-width: 30px; }
.navcfg-arrow:disabled { opacity: .3; cursor: default; }
.navcfg-name { flex: 1 1 auto; min-width: 0; }
.navcfg-vis { display: inline-flex; align-items: center; gap: 8px; flex: 0 0 auto;
  font-size: .8em; color: var(--text-dim); cursor: pointer; white-space: nowrap; }

/* Память: полоса диска + разбивка по типам/таблицам */
.storage-bar { display: flex; height: 18px; border-radius: 99px; overflow: hidden; background: var(--surface-2);
  border: 1px solid transparent; margin: 4px 0 10px; }
.storage-seg { height: 100%; min-width: 2px; transition: width .3s ease; }
.storage-seg:not(:last-child) { box-shadow: inset -1px 0 0 rgba(0,0,0,.25); }
.storage-legend { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.storage-legend b { font-size: 1.05em; color: var(--accent-text); }
.storage-list { display: flex; flex-direction: column; gap: 2px; margin-top: 8px; }
.storage-row { display: flex; align-items: center; gap: 10px; padding: 8px 4px; border-top: 1px solid var(--border); }
.storage-row:first-child { border-top: none; }
.storage-dot { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }
.storage-row-name { flex: 1 1 auto; min-width: 0; font-size: .88em; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.storage-row-size { flex: 0 0 auto; font-variant-numeric: tabular-nums; color: var(--text); min-width: 64px; text-align: right; }
.storage-row-acts { flex: 0 0 auto; display: flex; gap: 6px; min-width: 76px; justify-content: flex-end; }
/* Журналы сервера: два выпадающих списка и кнопка одной строкой (на телефоне переносятся). */
.logs-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; margin-top: 10px; }
.logs-field { display: flex; flex-direction: column; gap: 4px; font-size: .85em; color: var(--text-dim); }
.logs-field .select { min-width: 150px; }
.storage-act { padding: 4px 9px; line-height: 1; }

/* Память → Материалы школы: сводка, разбивка и таблица всех материалов */
/* Резервная копия (Настройки → Система → Резервная копия) */
.backup-acts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.backup-line { margin-top: 10px; font-size: .88em; overflow-wrap: anywhere; }
.backup-line:empty { display: none; }
.backup-staged { margin-top: 14px; padding: 14px; border: 1px solid var(--border); border-radius: 12px; }
.backup-items { margin: 8px 0 0; padding-left: 18px; font-size: .88em; line-height: 1.55; color: var(--text-dim); }
.backup-items li + li { margin-top: 4px; }
.backup-warn { margin: 8px 0 0; font-size: .88em; line-height: 1.5; color: var(--warn); }
.mat-acts { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 2px; }
.mat-caret { display: inline-block; transition: transform .18s ease; }
.mat-toggle.open .mat-caret { transform: rotate(90deg); }
.mat-modal { display: flex; flex-direction: column; gap: 10px; }
.mat-tools { display: flex; flex-direction: column; gap: 8px; }
.mat-search { width: 100%; }
.mat-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.mat-chip { background: var(--surface-2); border: 1px solid transparent; color: var(--text-dim);
  border-radius: 99px; padding: 5px 11px; font-size: .78em; font-family: var(--font); cursor: pointer; }
.mat-chip:hover { color: var(--text); }
.mat-chip.active { background: var(--selected); color: var(--on-accent); }
/* Таблица шире экрана телефона — прокручивается сама, страница за экран не уезжает. */
.mat-tablebox { max-height: 58vh; overflow: auto; border-radius: var(--radius-sm); }
.mat-table { width: 100%; border-collapse: collapse; font-size: .84em; }
.mat-table th, .mat-table td { padding: 8px 9px; text-align: left; white-space: nowrap; }
.mat-th { position: sticky; top: 0; z-index: 1; background: var(--surface);
  border-bottom: 1px solid var(--border); padding: 0 !important; }
.mat-sort { display: flex; align-items: center; gap: 4px; width: 100%; background: none; border: none;
  color: var(--muted); font-family: var(--font); font-size: 1em; padding: 9px 10px; cursor: pointer; }
.mat-sort:hover { color: var(--text); }
.mat-th.active .mat-sort { color: var(--accent-text); }
.mat-th.mat-num .mat-sort { justify-content: flex-end; }
.mat-arrow { font-size: .9em; }
.mat-row td { border-top: 1px solid var(--border); color: var(--text-dim); }
.mat-row:hover td { background: var(--surface-2); }
.mat-title { display: flex; align-items: center; gap: 8px; max-width: 280px; }
.mat-ico { display: inline-flex; color: var(--muted); flex: 0 0 auto; }
.mat-ico svg { width: 16px; height: 16px; }
.mat-name, .mat-owner { display: block; max-width: 250px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.mat-name { color: var(--text); }
.mat-owner { max-width: 160px; }
.mat-num { text-align: right !important; font-variant-numeric: tabular-nums; }
.mat-size { color: var(--text); }
.mat-status { font-size: .92em; }
.mat-active { color: var(--grade-good); }
.mat-inactive { color: var(--warn); }
.mat-none { color: var(--muted); }
.mat-acts-cell { width: 1%; }
.mat-foot { padding-top: 2px; }

/* Файловый менеджер (Настройки → Память → «Открыть файловый менеджер») */
.fm { display: flex; flex-direction: column; gap: 10px; }
.fm-crumbs { display: flex; align-items: center; gap: 3px; flex-wrap: wrap; font-size: .86em; }
.fm-crumb { background: none; border: none; color: var(--accent-text); cursor: pointer; padding: 3px 6px;
  border-radius: 6px; font-family: var(--font); font-size: 1em; }
.fm-crumb:hover { background: var(--surface-2); }
.fm-sep { color: var(--muted); }
.fm-list { display: flex; flex-direction: column; max-height: 62vh; overflow-y: auto;
  border: 1px solid transparent; border-radius: var(--radius-sm); }
.fm-row { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-top: 1px solid var(--border); }
.fm-row:first-child { border-top: none; }
.fm-folder { cursor: pointer; }
.fm-folder:hover { background: var(--surface-2); }
.fm-ico { font-size: 1.35em; width: 44px; min-width: 44px; text-align: center; flex: 0 0 auto; }
.fm-thumb { width: 44px; height: 44px; flex: 0 0 auto; border-radius: 7px; overflow: hidden;
  display: block; background: var(--surface-2); }
.fm-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fm-main { flex: 1 1 auto; min-width: 0; }
.fm-name { font-size: .9em; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fm-sub { font-size: .74em; color: var(--muted); margin-top: 1px; }
.fm-refs { margin-top: 3px; font-size: .78em; line-height: 1.45; color: var(--text-dim); overflow-wrap: anywhere; }
.fm-refs.none { color: var(--muted); font-style: italic; }
.fm-media { display: block; margin-top: 7px; width: 100%; max-width: 340px; }
.fm-media:not(.fm-video) { height: 36px; }
.fm-video { max-height: 200px; border-radius: 8px; background: #000; }
.fm-chev { color: var(--muted); font-size: 1.2em; flex: 0 0 auto; }
.fm-folder:hover .fm-chev { color: var(--accent-text); }
.fm-dl, .fm-del { flex: 0 0 auto; padding: 5px 10px; line-height: 1; }

@media (max-width: 760px) {
  .settings-layout { grid-template-columns: 1fr; }
  .settings-nav { flex-direction: row; overflow-x: auto; position: static; scrollbar-width: none; }
  .settings-nav::-webkit-scrollbar { display: none; }
  .settings-nav-item { white-space: nowrap; flex: 0 0 auto; }
  .navcfg-vis span:last-child { display: none; }
}

/* ── Статистика пользователя в строке (график + метрики) ───────── */
.user-stats { display: flex; align-items: center; gap: 12px; flex: 0 0 auto; }
.sparkline { display: inline-flex; width: 96px; height: 30px; flex: 0 0 auto;
  background: var(--bg); border: 1px solid transparent; border-radius: 8px; padding: 2px 4px; }
.sparkline svg { width: 100%; height: 100%; display: block; }
.us-metrics { display: flex; flex-wrap: wrap; gap: 2px 12px; max-width: 210px; }
.us-metric { font-size: .74em; color: var(--text-dim); white-space: nowrap; }
.us-metric b { color: var(--accent-text); font-weight: 600; font-variant-numeric: tabular-nums; }
@media (max-width: 760px) { .user-stats { flex: 1 1 100%; order: 5; } .us-metrics { max-width: none; } }

/* ── Плашки пользователей (ученик/учитель): единый виджет ──────── */
.urow { --urow-gap: 14px; flex-wrap: nowrap; gap: var(--urow-gap); align-items: center; position: relative; overflow: hidden; --urow-px: 18px; padding-top: 12px; padding-bottom: 12px; }
.avatar.urow-av { width: 60px; height: 60px; font-size: 1.4em; }
/* Размеры плашки сняты пипеткой с макета владельца (макет в 1.7 раза крупнее экрана); оттенки —
   по роли, как доля --text поверх --surface: --urow-dim — приглушённый текст и линия графика,
   --urow-line — рамки и плитки. */
.urow { --urow-dim: color-mix(in srgb, var(--text) 35%, var(--surface));
  --urow-line: color-mix(in srgb, var(--text) 21%, var(--surface)); }
/* Аватар плашки: баланс уроков — чёрный кружок слева сверху, «в сети» — зелёная точка справа снизу */
.urow-avwrap { position: relative; flex: 0 0 auto; width: 60px; height: 60px; }
.urow-bal { position: absolute; top: 0; left: -4px; min-width: 19px; height: 19px; padding: 0 5px;
  box-sizing: border-box; border-radius: 99px; display: grid; place-items: center;
  background: var(--bg); color: var(--text); font-size: .66em; font-weight: 700;
  font-variant-numeric: tabular-nums; line-height: 1; }
.urow-bal.low { color: var(--danger); }
.urow-online { position: absolute; left: 38px; top: 52px; width: 9px; height: 9px; border-radius: 50%;
  background: var(--online-dot); }
/* Какие уведомления дойдут: чёрная таблетка — подключено, зачёркнутая бледная — нет */
.urow-chips { display: flex; flex-wrap: nowrap; gap: 2px; }
.urow-chip { flex: 0 0 auto; width: 42px; height: 14px; display: grid; place-items: center; box-sizing: border-box;
  font-size: .46em; line-height: 1; border-radius: 99px; white-space: nowrap; letter-spacing: 0;
  background: var(--bg); color: var(--text-dim); text-decoration: line-through; opacity: .5; }
.urow-chip.on { text-decoration: none; opacity: 1; }
/* Ширина блока имени → статистика встаёт ровным столбцом, не «скачет» от длины имени.
   ⚠️ `0 1 200px`, а НЕ `0 0 200px`: выше 1100px строка идёт в ОДНУ линию без переноса, и
   если её содержимому нечем сжиматься, лишние пиксели просто вылезают за карточку —
   кнопки «Написать»/«Профиль» висели снаружи плашки (жалоба владельца продукта). Имя и
   так обрезается многоточием, поэтому сжимать в первую очередь надо именно эту колонку.
   Ниже 1100px строка переносится — см. медиа-блок в конце файла. */
.urow-id { display: flex; flex-direction: column; gap: 5px; flex: 0 1 200px; min-width: 130px; }
.urow-nameline .row-title { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.urow-nameline { flex-wrap: nowrap; line-height: 1.15; }
.urow-nameline { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.urow-idn { font-size: .72em; color: var(--muted); font-variant-numeric: tabular-nums; letter-spacing: .3px; }
.urow .toggle-pill { align-self: flex-start; font-size: .65em; padding: 2px 15px; line-height: 1.2; font-weight: 300; }
/* «Активен» — нейтральная таблетка (макет); красным остаётся только «Отключён» */
.urow .toggle-pill.on, .urow .toggle-pill.on:hover { color: var(--muted); border-color: var(--urow-line); background: transparent; }
.urow .toggle-pill.on:hover { border-color: var(--muted); }
.urow .toggle-pill.on .tp-dot { display: none; }
/* Общая оценка учителя */
.urow-score { display: flex; flex-direction: column; align-items: center; flex: 0 0 auto; gap: 2px; }
.urow-score-cap { font-size: .72em; color: var(--text-dim); white-space: nowrap; }
.urow-score-val { font-size: 2.1em; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; color: var(--text-dim); }
/* Цвет крупной оценки — сигнал по баллу (низкий/средний/высокий), фиксированный, вне палитры школы */
.urow-score-val.good { color: var(--grade-good); }
.urow-score-val.mid { color: var(--grade-mid); }
.urow-score-val.low { color: var(--grade-low); }
/* Нет данных — «N/A» мельче и приглушённо: это не балл, и цифрой оно притворяться не должно */
.urow-score-val.na { font-size: 1.05em; font-weight: 700; letter-spacing: .5px; color: var(--muted); padding: 8px 0; }
/* Виджет: график (с числом уроков) + метрики столбиком */
/* ⚠️ `min-width` НЕ обнуляем (в отличие от .urow-id): у флекс-элемента авто-минимум
   равен min-content его содержимого, и он-то и удерживает виджет от схлопывания —
   иначе метрики и график вылезли бы уже из него самого, наехав на «Классы» и кнопки. */
.urow-stat { display: flex; align-items: center; gap: 7px; flex: 0 1 auto; }
/* График — вторая сжимаемая колонка (после имени): линия читается и в 96px, а вот
   метрики и кнопки сжимать нечем — там текст без переносов. */
.urow-chart { position: relative; flex: 0 1 auto; min-width: 96px; }
/* Единая высота модулей строки (график, A/Y/M, кнопки) — всё ровно по высоте.
   64px вмещает 2 кнопки (~31px) и 3 кнопки A/Y/M (20px) без выхода за края. */
.urow { --urow-h: 60px; }
.urow-chart .sparkline { display: block; width: 132px; max-width: 100%; height: var(--urow-h);
  background: transparent;
  border: 0; border-radius: 9px; padding: 0 0 15px; box-sizing: border-box; }
.urow-chart .sparkline svg { display: block; }
.urow-chart-cap { position: absolute; bottom: 0; right: 0; font-size: .66em; line-height: 1.2; font-weight: 300; color: var(--urow-dim); z-index: 1; pointer-events: none; }
/* Число уроков приглушено наравне с подписью: строка и так перегружена ярким (жалоба владельца) */
.urow-chart-cap b { color: inherit; font-weight: 300; font-variant-numeric: tabular-nums; }
.urow-metrics { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; min-width: 44px; }
.urow-fin { font-size: .8em; font-weight: 400; line-height: 1.2; color: var(--text-dim); white-space: nowrap; font-variant-numeric: tabular-nums; }
.urow-fin-sub { font-size: .65em; font-weight: 300; line-height: 1.2; color: var(--text-dim); white-space: nowrap; font-variant-numeric: tabular-nums; }
.urow-spark-holder { display: block; }
.urow-spacer { flex: 1 1 auto; min-width: 8px; }
/* Левый спейсер — центрирует блок статистики/графика в плашке */
.urow-lead { flex: 1 1 auto; min-width: 8px; }
.urow-btns { display: flex; flex-direction: column; justify-content: space-between;
  height: var(--urow-h); flex: 0 0 92px; width: 92px; }
.urow-btns .btn { width: 100%; }
/* Кнопки панели — тонкие контурные, как в макете: 28px высотой, приглушённый текст */
.urow-btns .btn { padding: 0 14px; height: 28px; box-sizing: border-box; font-size: .72em; font-weight: 400;
  background: transparent; color: var(--urow-dim); border: 1px solid var(--urow-line); border-radius: 6px; }
.urow-btns .btn:hover { background: transparent; color: var(--text-dim); border-color: var(--muted); }
/* Переключатель диапазона графика: A (всё время) / Y (год) / M (месяц) —
   ровно по высоте графика, кнопки равномерно сверху донизу */
.urow-range { display: flex; flex-direction: column; justify-content: space-between;
  height: var(--urow-h); flex: 0 0 auto; }
.urow-range-btn { width: 18px; height: 18px; display: grid; place-items: center; padding: 0;
  font-family: var(--font); font-size: .72em; font-weight: 300; line-height: 1;
  border: 1px solid var(--urow-line); border-radius: 5px;
  background: transparent; color: var(--urow-line); cursor: pointer; transition: background .15s, color .15s, border-color .15s; }
.urow-range-btn:hover { color: var(--muted); border-color: var(--muted); }
.urow-range-btn.active { background: var(--urow-line); color: var(--bg); border-color: var(--urow-line); }

/* Шеврон сворачивания — только на мобильных */
.urow-chevron { display: none; }

/* ── Карточка ученика/учителя на телефоне: свёрнута → разворачивается по тапу ──
   Свёрнуто: аватар + имя/статус + шеврон.
   Развёрнуто (.is-open): общая оценка в правом верхнем углу, ниже A/Y/M + график +
   метрики в линию, ещё ниже — «Классы»/«Написать»/«Профиль». Едино для обеих ролей. */
@media (max-width: 760px) {
  .urow { flex-wrap: wrap; gap: 10px 12px; align-items: center; padding: 14px; }
  .urow .urow-avwrap,
  .urow .urow-nameline { cursor: pointer; }
  .urow .urow-id { flex: 0 1 auto; min-width: 0; }

  /* Шеврон справа */
  .urow .urow-chevron { display: grid; place-items: center; flex: 0 0 auto;
    width: 36px; height: 36px; margin-left: auto; padding: 0; border-radius: 9px;
    border: 1px solid var(--border-2); background: transparent; color: var(--muted); cursor: pointer; }
  .urow .urow-chevron:hover { color: var(--accent-text); border-color: var(--accent-border); }
  .urow .urow-chevron svg { width: 18px; height: 18px; }

  /* Свёрнуто — прячем всё, кроме шапки */
  .urow .urow-stat,
  .urow .urow-chips,
  .urow .urow-spacer,
  .urow .urow-lead,
  .urow .urow-score,
  .urow .classes-chip,
  .urow .urow-btns { display: none; }

  /* Развёрнуто */
  .urow.is-open .urow-chevron { display: none; }
  /* Общая оценка — в правый верхний угол, прижата к правому краю */
  .urow.is-open .urow-score { display: flex; flex: 0 0 auto; margin-left: auto;
    align-items: flex-end; text-align: right; }
  .urow.is-open .urow-stat { display: flex; flex: 1 1 auto; min-width: 0; gap: 10px; }
  .urow.is-open .urow-chart { flex: 1 1 auto; min-width: 60px; }
  .urow.is-open .urow-chart .sparkline { width: 100%; }
  .urow.is-open .urow-metrics { flex: 0 0 auto; }
  /* Спейсер — принудительный перенос строки (нулевой высоты) перед кнопками.
     Оценка остаётся в шапке (margin-left:auto), график/метрики переносятся сами. */
  .urow.is-open .urow-spacer { display: block; flex: 0 0 100%; width: 100%;
    height: 0; min-width: 0; margin: 0; }
  .urow.is-open .classes-chip { display: flex; flex: 1.2 1 0; width: auto;
    min-width: 0; min-height: 0; height: 50px; flex-direction: column; gap: 1px; padding: 0 10px; }
  .urow.is-open .urow-btns { display: flex; flex: 1.6 1 0; flex-direction: row;
    align-items: stretch; height: 50px; gap: 8px; justify-content: stretch; }
  .urow.is-open .urow-btns .btn { flex: 1; height: auto; width: auto; }
}

/* ── Тёмная панель справа (≥1101px): деньги/срок, A/Y/M, график, «Языки/Классы», кнопки ──
   Левый край срезан наклонно, как в макете. Панель вылезает за padding плашки до самого края
   (плашка режет лишнее: overflow hidden). ⚠️ Ниже 1100px строка переносится, панель там
   `display: contents` — дети встают прямо в `.urow` и работают прежние раскладки (перенос,
   аккордеон на телефоне). */
.urow-panel { display: contents; }
@media (min-width: 1101px) {
  .urow-panel { display: flex; align-items: center; gap: 7px; flex: 0 1 auto; align-self: stretch;
    position: relative; margin: -12px calc(-1 * var(--urow-px)) -12px 0; padding: 12px calc(var(--urow-px) + 4px) 12px 83px; }
  .urow-panel::before { content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none;
    background: color-mix(in srgb, var(--bg) 55%, var(--surface));
    clip-path: polygon(0 0, 100% 0, 100% 100%, 78px 100%); }
  .urow-panel > * { position: relative; z-index: 1; }
  .urow-panel .urow-spacer { display: none; }
  /* Общая оценка — бледная крупная цифра фоном слева от панели; цвет по баллу остаётся в подсказке */
  .urow-score-cap { display: none; }
  .urow-score { margin-right: calc(-1 * var(--urow-gap)); }
  .urow-score-val, .urow-score-val.good, .urow-score-val.mid, .urow-score-val.low, .urow-score-val.na {
    font-size: 2.55em; font-weight: 800; color: color-mix(in srgb, var(--text) 11%, var(--surface)); }
  .urow-score-val.na { font-size: 1.6em; padding: 0; letter-spacing: 0; }
  .urow-panel .urow-metrics { margin-right: 35px; }
  .urow-panel .urow-chart { margin-right: 30px; }
  .urow-panel .classes-chip { margin-right: 8px; }
}

/* Список пользователей: вкладки подняты ближе к шапке (без подзаголовка) */
.users-head { margin-bottom: 12px; align-items: center; }
/* Приглушённый шрифт списка (два круга по жалобе владельца: «глаза разбегаются» →
   «ещё на 30%»): имена доведены до --muted, ниже контраст AA уже не позволяет.
   Чтобы имя не сливалось с email/ID (они тоже --muted), имя выделено весом 600. */
.users-screen .row-title { color: var(--muted); font-weight: 600; }

/* Фильтры списка (views/users.js filterBar): на ПК — одна строка полей под вкладками,
   на телефоне — свёрнуты под кнопку «Фильтры» (четыре поля столбиком съели бы экран
   до первого ученика). Поля ужаты против обычного .input: это панель, а не форма. */
.ufl { margin: -10px 0 16px; }
.ufl-fields { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 12px; }
.ufl-field { min-width: 0; }
.ufl-status { flex: 0 1 160px; }
.ufl-teacher { flex: 1 1 260px; max-width: 380px; }
.ufl-balance { flex: 0 1 230px; }
.ufl-tenure { flex: 0 1 190px; }
.ufl-label { font-size: .74em; color: var(--text-dim); margin-bottom: 5px; letter-spacing: .3px; }
.ufl .input, .ufl .select { padding: 8px 12px; font-size: .85em; }
.ufl .select { padding-right: 32px; background-position: right 10px center; }
.ufl-pair { display: flex; gap: 6px; }
.ufl-pair > * { flex: 1 1 0; min-width: 0; }
.ufl-reset { align-self: flex-end; }
.ufl-toggle { display: none; }
.ufl-count { min-width: 18px; padding: 0 5px; border-radius: 99px; font-size: .85em; text-align: center;
  background: var(--accent); color: var(--on-accent); }
/* ⚠️ .btn и flex-контейнеры перебивают атрибут hidden — возвращаем ему силу. */
.ufl [hidden], .ufl-summary[hidden] { display: none; }
.ufl-summary { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 0 0 10px;
  font-size: .8em; color: var(--muted); }
/* Кнопка-ссылка («показать» скрытых): настоящая <button> — фокус с клавиатуры. */
.ufl-link { background: none; border: none; padding: 0; font: inherit; color: var(--accent-text);
  cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
@media (max-width: 760px) {
  .ufl { margin-top: -12px; }
  .ufl-toggle { display: inline-flex; align-items: center; gap: 8px; }
  .ufl-fields { display: none; margin-top: 10px; }
  .ufl.open .ufl-fields { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .ufl-field { max-width: none; }
  .ufl-reset { justify-self: start; }
}
/* Между 1100px и ~1300px строка ещё «десктопная» (в одну линию), но места впритык:
   сначала ужимаем отступы и только потом даём сжиматься содержимому — см. .urow-id. */
@media (max-width: 1300px) {
  .urow { --urow-gap: 10px; padding: 12px 14px; --urow-px: 14px; }
  .urow-chart .sparkline { width: 132px; }
}

/* ⚠️ Порог переноса поднят с 900px до 1100px. Ниже 1100px строка в одну линию НЕ
   ПОМЕЩАЕТСЯ ФИЗИЧЕСКИ: аватар + имя + оценка + график + метрики + «Классы» + две
   кнопки — это ~950px минимума, и в окно 1000px они не влезали никогда. Раньше лишнее
   просто вылезало за карточку (кнопки «Написать»/«Профиль» висели снаружи плашки —
   жалоба владельца продукта), потому что сжиматься содержимому было нечем. */
@media (max-width: 1100px) {
  .urow { flex-wrap: wrap; gap: 14px; }
  .urow-spacer, .urow-lead { display: none; }
  .urow-id { flex: 1 1 auto; }
  .urow-btns { flex-direction: row; height: auto; width: auto; flex: 0 0 auto; justify-content: flex-start; gap: 6px; }
  .urow-btns .btn { width: auto; padding: 0 16px; }
}
/* «Классы» и кнопки уходят на вторую строку ЦЕЛИКОМ и прижимаются к правому краю —
   туда же, где они стоят в однострочной раскладке. ⚠️ Без принудительного переноса
   соседние карточки получаются разной высоты (у ученика с коротким «0 ₽» строка ещё
   помещается, у следующего уже нет), и список выглядит рваным.
   ⚠️ Диапазон с нижней границей: на телефоне (≤760px) у строки своя раскладка-аккордеон,
   и это правило туда лезть не должно. */
@media (min-width: 761px) and (max-width: 1100px) {
  /* Спейсер нулевой высоты = перенос строки (тот же приём, что в мобильной раскладке). */
  .urow-spacer { display: block; flex: 0 0 100%; width: 100%; height: 0; min-width: 0; margin: 0; }
  .urow .classes-chip { margin-left: auto; }
}

/* Поп-ап оценки урока (шкала 1–10) */
.rate-scale { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; max-width: 300px; margin: 0 auto; }
.rate-btn { height: 46px; border-radius: 10px; border: 1px solid var(--border-2);
  background: var(--surface-2); color: var(--text); cursor: pointer; font-size: 1em; font-weight: 700;
  font-family: var(--font); font-variant-numeric: tabular-nums; transition: transform .1s, background .12s, color .12s, border-color .12s; }
.rate-btn:hover { background: var(--btn-primary); color: var(--on-btn-primary); border-color: var(--btn-primary); transform: translateY(-2px); }
.rate-hint { display: flex; justify-content: space-between; max-width: 300px; margin: 12px auto 2px; font-size: .76em; color: var(--muted); }

/* Окно ученика «Оцените урок» (макет владельца продукта, ratings.js → showStudentModal).
   Шкала — 10 квадратов в ряд, «утопленных» в окно (фон --bg темнее поверхности окна в
   обеих темах); выбранный балл заливается сигналом: зелёный — хорошо, красный — ≤ 5.
   ⚠️ --rate-good/--rate-bad ФИКСИРОВАНЫ и одинаковы в обеих темах, как --lesson-*: это
   сигнал «понравилось / нет», а не оформление школы. */
:root { --rate-good: #12c30f; --on-rate-good: #062a05; --rate-bad: #d8130f; --on-rate-bad: #fff; }
.srate-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.srate-title { font-size: 1.35em; font-weight: 500; line-height: 1.25; min-width: 0; }
.srate-who { display: flex; align-items: center; gap: 12px; min-width: 0; }
.srate-who-txt { text-align: right; min-width: 0; }
.srate-name { font-size: 1.08em; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.srate-class { font-size: .76em; color: var(--muted); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.srate-ava.avatar { width: 44px; height: 44px; border: 0; }
.srate-when { margin: 14px 0 24px; font-size: .95em; color: var(--text-dim); }
.srate-scale { display: grid; grid-template-columns: repeat(10, minmax(0, 1fr)); gap: 8px; }
.srate-btn { aspect-ratio: 1; max-height: 52px; border: 0; border-radius: 10px; cursor: pointer;
  background: var(--bg); color: var(--text); font: inherit; font-size: 1.1em; font-weight: 500;
  font-variant-numeric: tabular-nums; transition: background .12s, color .12s, transform .1s; }
.srate-btn:hover { background: color-mix(in srgb, var(--text) 10%, var(--bg)); }
.srate-btn:active { transform: scale(.94); }
.srate-btn.is-good { background: var(--rate-good); color: var(--on-rate-good); }
.srate-btn.is-bad { background: var(--rate-bad); color: var(--on-rate-bad); }
.srate-hint { display: flex; justify-content: space-between; margin-top: 8px; font-size: .74em; color: var(--muted); }
/* Низ окна: слева поле «что было не так» (только после низкого балла), справа колонка
   кнопок — «Отправить» встаёт ПОД «Позже» и поднимает её выше. */
.srate-bottom { display: flex; align-items: flex-end; justify-content: flex-end; gap: 16px; margin-top: 28px; }
.srate-text { flex: 1 1 auto; min-width: 0; min-height: 110px; resize: vertical; border: 0;
  background: var(--bg); border-radius: var(--radius-sm); line-height: 1.4; }
.srate-actions { flex: 0 0 auto; display: flex; flex-direction: column; gap: 10px; }
.srate-act { min-width: 128px; justify-content: center; background: var(--bg); border-color: transparent; }
@media (max-width: 760px) {
  .srate-title { font-size: 1.15em; }
  .srate-ava.avatar { width: 36px; height: 36px; }
  .srate-scale { gap: 4px; }
  .srate-btn { border-radius: 7px; font-size: .95em; }
  /* На телефоне поле — во всю ширину, кнопки строкой под ним. */
  .srate.is-low .srate-bottom { flex-direction: column; align-items: stretch; }
  .srate.is-low .srate-actions { flex-direction: row; }
  .srate.is-low .srate-act { flex: 1 1 0; min-width: 0; }
}

/* ── Карточка ученика (новый дизайн) ─────────────────────────── */
.student-row {
  display: flex; align-items: center;
  gap: 20px; flex-wrap: nowrap;
}

/* Левая колонка: фиксированная ширина */
.srow-left {
  flex: 0 0 190px;
  display: flex; flex-direction: column; gap: 2px;
}
.srow-left .row-nameline { flex-wrap: nowrap; gap: 6px; }
.srow-left .toggle-pill {
  align-self: flex-start; margin-top: 5px;
  font-size: .78em; padding: 4px 10px;
}
.srow-left .row-sub { margin-top: 3px; }

/* Центральный блок: спарклайн + метрики — НЕ растягивается */
.srow-center {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 12px;
}

/* Спарклайн крупнее + тёмный фон-коробка */
.student-row .sparkline {
  width: 120px !important; height: 60px !important;
  background: rgba(0,0,0,.35);
  border-radius: 8px; padding: 6px;
  box-sizing: border-box;
}

/* Метрики в столбик */
.student-row .us-metrics {
  display: flex; flex-direction: column;
  gap: 3px; max-width: none;
}
.student-row .us-metric { font-size: .8em; }

/* Распорка: прижимает классы и кнопки к правому краю */
.srow-spacer { flex: 1 1 auto; }

/* Чип классов */
.srow-classes { flex: 0 0 auto; }
.classes-chip {
  display: inline-flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 0;
  padding: 0 14px; border-radius: 12px; cursor: pointer;
  font-size: .74em; font-weight: 300; line-height: 1.4; white-space: nowrap;
  width: 122px; height: var(--urow-h); box-sizing: border-box;
  border: 1.5px solid var(--urow-line);
  background: transparent; color: var(--urow-dim); font-family: var(--font);
  transition: border-color .18s, color .18s, background .18s;
}
.classes-chip b { font-weight: 700; }
.classes-chip:hover { border-color: var(--muted); color: var(--text-dim); }

/* Правые кнопки стопкой */
.srow-btns {
  flex: 0 0 auto;
  display: flex; flex-direction: column; gap: 6px;
}
.srow-btns .btn {
  min-width: 96px;
  justify-content: center; text-align: center;
}

/* Адаптив ≤760px */
@media (max-width: 760px) {
  .student-row { flex-wrap: wrap; gap: 16px; align-items: flex-start; }

  /* Ряд 1: инфо слева, график прижат к правому краю */
  .srow-left { flex: 0 0 auto; order: 1; }
  .srow-center { flex: 0 0 auto; order: 2; margin-left: auto; }
  .srow-spacer { display: none; }

  /* Ряд 2: «Добавить класс» во всю ширину, в одну строку */
  .srow-classes { order: 3; flex: 1 1 100%; }
  .classes-chip {
    width: 100%; box-sizing: border-box;
    flex-direction: row; gap: 8px;
    min-height: 52px; padding: 12px 16px;
  }

  /* Ряд 3: кнопки во всю ширину, поровну */
  .srow-btns { flex-direction: row; order: 4; flex: 1 1 100%; }
  .srow-btns .btn { flex: 1; }
}

/* ══ Конструктор курсов ════════════════════════════════════════ */

/* — Счётчики в строке класса — */
.class-counts { display: flex; gap: 16px; flex: 0 0 auto; align-items: center; }
.class-sub { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-top: 4px; }
.class-sub-text { color: var(--muted); font-size: .78em; }
.class-sub-name { color: var(--text-dim); font-size: .8em; font-weight: 500; }
.class-sub-id { color: var(--muted); font-size: .74em; letter-spacing: .02em; }
/* Иконка класса в списке — крупный квадрат с сильно скруглёнными углами (squircle). */
.avatar.cls-row-av { width: var(--cls-av, 58px); height: var(--cls-av, 58px); font-size: 1.5em;
  border-radius: 20px; object-fit: cover; }
/* ── Плашка класса: аватар · название · «язычок» входа (макет владельца продукта) ──
   Раньше справа стояла кнопка «Открыть →», а на телефоне она падала отдельной строкой с
   разделителем, и плашка вырастала вдвое. Теперь вход — «язычок» со стрелкой на том же
   жидком переливе, что «Войти в класс» (`mountLiquidStrip` в views/classes.js), и плашка
   на телефоне — одна узкая строка.
   ⚠️ Раскладка — СЕТКА с именованными областями, а не flex-wrap: счётчики админа и баланс
   ученика (`.class-counts`) на телефоне уходят второй строкой под текст, но язычок при этом
   остаётся на всю высоту справа (`grid-area: go` тянется через обе строки). Пустые счётчики
   (у учителя и ученика в едином режиме) скрыты, и строки под ними нет вовсе.
   ⚠️ Язычок — НЕ внутри `.row-actions`: у того на телефоне свой «подвал» с разделителем
   (`.row .row-actions` в мобильном @media), ради которого плашка и росла. */
/* ⚠️ `--cls-av` — ОДНА высота на аватар и язычок (просьба владельца: «кнопка входа по высоте как
   аватарка»): оба стоят по центру строки и одинаково отстоят от верха и низа, а правый отступ
   равен вертикальному, поэтому язычок со всех трёх сторон на одном расстоянии от края. */
.cls-row { --cls-av: 58px; display: grid; align-items: center; gap: 0 16px; padding: 14px;
  padding-left: 18px;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  grid-template-areas: "av main counts go"; }
.cls-row > .cls-row-av { grid-area: av; }
.cls-row > .row-main { grid-area: main; }
.cls-row > .class-counts { grid-area: counts; }
.cls-row > .class-counts:empty { display: none; }
.cls-row > .cls-go { grid-area: go; }
/* Язычок: «D» — слева небольшое скругление, справа полукруг; высота — как у аватарки.
   ⚠️ Радиусы заданы числами, а не `999px`: если сумма радиусов по стороне больше самой
   стороны, браузер ужимает ВСЕ радиусы пропорционально — левое скругление исчезло бы вовсе.
   На низкой плашке телефона такое ужатие нормально: форма сохраняется.
   ⚠️ Фон ниже — фолбэк без WebGL: те же цвета, медленно текущие по высоте (`tg-tab-flow`
   общий с язычками витрины). Шейдер поднялся — холст ложится поверх (`.liquid-canvas`),
   а `has-liquid` гасит эту анимацию. */
.cls-go { --go-w: 46px; position: relative; overflow: hidden; isolation: isolate;
  align-self: center; height: var(--cls-av); width: var(--go-w);
  display: grid; place-items: center; color: #fff;
  border-radius: 14px 36px 36px 14px;
  background: linear-gradient(180deg, #a01a54, #d92d6b 30%, #e8703f 58%, #2bb3a8 82%, #a01a54) 0 0 / 100% 300%;
  animation: tg-tab-flow 14s linear infinite;
  transition: filter .18s ease; }
.cls-go.has-liquid { animation: none; }
.cls-go > :not(.liquid-canvas) { position: relative; z-index: 1; }
/* Стрелка белая с мягкой тенью — как подпись «Войти в класс»: на светлых нитях перелива
   белое без тени теряется. */
.cls-go svg { width: 26px; height: 26px; stroke-width: 2.2;
  filter: drop-shadow(0 1px 4px rgba(0, 0, 0, .45)); }
.cls-row:hover .cls-go { filter: brightness(1.06) saturate(1.06); }
/* Светлая тема — тот же осветляющий фильтр, что у «Войти в класс» (тёмная фуксия на белой
   карточке «слишком бросалась в глаза»); палитру шейдера не трогаем, она общая. */
[data-theme="light"] .cls-go { filter: brightness(1.16) saturate(.82); }
[data-theme="light"] .cls-row:hover .cls-go { filter: brightness(1.22) saturate(.88); }
@media (max-width: 760px) {
  /* Узкая плашка: аватар поменьше, язычок уже, счётчики — второй строкой под текстом. */
  .cls-row { --cls-av: 48px; padding: 10px; padding-left: 12px; gap: 0 12px;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "av main go" "av counts go"; }
  .avatar.cls-row-av { font-size: 1.25em; border-radius: 16px; }
  .cls-go { --go-w: 44px; }
  .cls-go svg { width: 22px; height: 22px; }
  .cls-row > .class-counts { margin-top: 6px; gap: 12px; flex-wrap: wrap; }
}

/* — Детализация списаний класса — */
.chg-hint { color: var(--text-dim); font-size: .85em; line-height: 1.5; margin-bottom: 14px; }
.chg-list { display: flex; flex-direction: column; gap: 8px; }
.chg-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: var(--surface); border: 1px solid transparent; border-radius: var(--radius-sm); padding: 11px 14px; }
.chg-when { display: flex; flex-direction: column; min-width: 120px; }
.chg-date { font-size: .9em; color: var(--text); }
.chg-time { font-size: .76em; color: var(--muted); margin-top: 1px; }
.chg-status { font-size: .76em; padding: 3px 10px; border-radius: 99px; white-space: nowrap;
  border: 1px solid transparent; }
.chg-completed { color: var(--accent-text); background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 35%, transparent); }
.chg-noshow { color: var(--danger); background: color-mix(in srgb, var(--danger) 10%, transparent); border-color: color-mix(in srgb, var(--danger) 40%, transparent); }
.chg-nobalance { color: #c4b5fd; background: rgba(167,139,250,.12); border-color: rgba(167,139,250,.4); }
.chg-cancelled { color: var(--muted); background: var(--surface-2); border-color: var(--border-2); }
.chg-resched { color: var(--warn); background: rgba(251,191,36,.1); border-color: rgba(251,191,36,.4); }
.chg-tail { display: flex; align-items: center; gap: 12px; margin-left: auto; }
.chg-rating { font-size: .8em; color: var(--warn); white-space: nowrap; }
.chg-amount { font-size: .82em; font-variant-numeric: tabular-nums; white-space: nowrap; }
.chg-amount.neg { color: var(--danger); font-weight: 600; }
.chg-amount.zero { color: var(--muted); }

/* ══════════════════════════════════════════════════════════════════
   Страница класса: шапка · человек · показатели · прогресс · панели
   Каркас один на обе роли (учитель и ученик) — различаются только действия.
   ══════════════════════════════════════════════════════════════════ */

/* — Шапка: кто мы · чем платим · что делать — */
.cd-hero {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
  gap: 26px; padding: 20px 24px; margin-bottom: 18px;
  border: 1px solid transparent; border-radius: calc(var(--radius) + 8px);
  /* ⚠️ Ровная поверхность, БЕЗ градиента (пробовали свечение от акцента — снято по
     решению владельца продукта): шапка стоит первой на странице, где ниже идут такие
     же карточки, и цветной прямоугольник наверху читался отдельным баннером. */
  background: var(--surface);
}
.cd-hero-id { display: flex; align-items: center; gap: 16px; min-width: 0; }
.cd-hero-idtext { min-width: 0; }
.cd-hero-ava { display: flex; flex-direction: column; align-items: center; gap: 5px; flex: 0 0 auto; }
.avatar.avatar-hero { width: 96px; height: 96px; font-size: 2.5em; border-radius: 26px;
  border: 1px solid var(--border-2); transition: box-shadow .15s ease; }
/* Аватар кликабелен у управляющего — ведёт в настройки класса (подпись «Сменить» убрана). */
.cd-hero-ava-btn { display: block; padding: 0; border: 0; background: none; cursor: pointer; line-height: 0; }
.cd-hero-ava-btn:hover .avatar-hero { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent); }
.cd-hero-title { display: flex; align-items: center; gap: 8px; min-width: 0; }
.cd-hero-name { font-size: 1.75rem; font-weight: 600; letter-spacing: -.5px; margin: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cd-hero-edit { background: none; border: none; cursor: pointer; padding: 2px; line-height: 0;
  color: var(--muted); flex: 0 0 auto; }
.cd-hero-edit svg { width: 17px; height: 17px; }
.cd-hero-edit:hover { color: var(--accent-text); }
.cd-hero-tags { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.cd-tag { display: inline-flex; align-items: center; gap: 5px; padding: 3px 11px; border-radius: 99px;
  font-size: .78em; color: var(--accent-text); background: color-mix(in srgb, var(--accent) 9%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent); }
.cd-tag-soft { color: var(--text-dim); background: var(--surface-2); border-color: var(--border-2); }
.cd-tag-cap { color: var(--muted); }
/* Язык класса — плоский текст под названием, без пилюли (см. classHero). */
.cd-tag-plain { color: var(--text-dim); background: none; border-color: transparent; padding: 0; }
.cd-tag-lock { color: var(--danger); background: color-mix(in srgb, var(--danger) 10%, transparent);
  border-color: color-mix(in srgb, var(--danger) 35%, transparent); }

/* Баланс уроков и алмазы — одной колонкой: два счёта одного и того же.
   ⚠️ Живёт в панели человека (.cd-person-side), а не в шапке: баланс принадлежит
   ученику и стоять должен рядом с ним. */
.cd-person-bal .cd-side-body { display: flex; flex-direction: column; gap: 2px; align-items: flex-start; }
.cd-bal-val { display: flex; align-items: baseline; gap: 6px; }
.cd-bal-val b { font-size: 1.6rem; font-weight: 650; letter-spacing: -.5px; font-variant-numeric: tabular-nums; }
.cd-bal-val span { font-size: .84em; color: var(--text-dim); }
.cd-bal-val.low b { color: var(--danger); }
.cd-bal-btn { display: flex; align-items: center; gap: 8px; background: none; border: none; cursor: pointer;
  padding: 0; font-family: var(--font); color: inherit; }
.cd-bal-plus { display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%;
  font-size: .8em; color: var(--accent-text); background: color-mix(in srgb, var(--accent) 12%, transparent);
  opacity: 0; transition: opacity .15s ease; }
.cd-bal-btn:hover .cd-bal-plus { opacity: 1; }
.cd-bal-dm { display: inline-flex; align-items: center; gap: 6px; margin-top: 4px;
  font-size: .9em; color: var(--text-dim); }
.cd-bal-dm .dm-ico { width: 16px; height: 16px; }
/* Кошелёк ученика тут стоит СВОЕЙ строкой под балансом, а не соседом в общей плашке
   (как на главной), поэтому вертикальная черта-разделитель ему не нужна: делить не с
   чем, и она читается как обрубок рамки. Отступы тоже снимаем — строка выравнивается
   по левому краю числа уроков. */
.cd-person-bal .dm-box { margin: 0; padding: 0; }
.cd-person-bal .dm-box::before { display: none; }

.cd-hero-acts { display: flex; flex-direction: column; gap: 8px; }
/* Учитель класса в шапке — только у админа (макет владельца продукта): аватар с точкой
   присутствия · «УЧИТЕЛЬ ID N» · имя · пилюля «Заменить учителя». Третья колонка сетки
   шапки, перед кнопкой входа. Размеры сняты с макета в его масштабе ×1.72. */
.cd-hero.has-teacher { grid-template-columns: minmax(0, 1fr) auto auto; }
.cd-hero-teacher { display: flex; align-items: center; gap: 14px; min-width: 0; }
.cd-ht-text { min-width: 0; }
.cd-ht-cap { font-size: .7em; text-transform: uppercase; letter-spacing: .9px; color: var(--muted);
  white-space: nowrap; }
.cd-ht-id { margin-left: 6px; text-transform: none; letter-spacing: 0; opacity: .6; }
.cd-ht-name { font-size: 1.12em; font-weight: 500; margin: 2px 0 4px; max-width: 200px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cd-ht-replace { display: inline-block; padding: 1px 9px; border: 0; border-radius: 99px; cursor: pointer;
  font-family: var(--font); font-size: .7em; font-weight: 500; line-height: 1.35; white-space: nowrap;
  color: #fff; background: var(--teacher-replace); transition: filter .14s ease; }
.cd-ht-replace:hover { filter: brightness(1.12); }
.cd-ht-none { font-size: .9em; color: var(--text-dim); margin: 2px 0 4px; }
/* Учителя нет: пустой кружок с «+» на месте аватара и спокойная (не красная) пилюля. */
.cd-ht-empty { display: grid; place-items: center; width: 58px; height: 58px; flex: 0 0 auto; padding: 0;
  border-radius: 50%; border: 1px dashed var(--border-2); background: none; color: var(--muted);
  cursor: pointer; transition: color .14s ease, border-color .14s ease; }
.cd-ht-empty svg { width: 22px; height: 22px; }
.cd-ht-empty:hover { color: var(--accent-text); border-color: color-mix(in srgb, var(--accent) 50%, transparent); }
.cd-ht-replace.cd-ht-add { color: var(--on-accent); background: var(--accent); }
.avatar.cd-ht-ava { width: 58px; height: 58px; font-size: 1.4em; }
/* Точка присутствия на аватаре (на платформе / нет) — кольцо цвета поверхности отделяет
   её от фото. Пока статус не пришёл, точки нет вовсе: «нет данных» ≠ «офлайн». */
.cd-ava-wrap { position: relative; flex: 0 0 auto; line-height: 0; }
.cd-presence { position: absolute; right: 1px; bottom: 1px; width: 10px; height: 10px; border-radius: 50%;
  border: 2px solid var(--surface); background: var(--presence-off); }
.cd-presence.on { background: var(--presence-on); }
.cd-presence[hidden] { display: none; }

/* Окно «Заменить учителя»: слоты класса сверху, ниже кандидаты с совпадением окон. */
.rt-head { margin-bottom: 12px; }
.rt-cap { font-size: .8em; color: var(--muted); margin-bottom: 6px; }
.rt-slots { display: flex; flex-wrap: wrap; gap: 6px; }
.rt-slot { padding: 2px 10px; border-radius: 99px; font-size: .8em; color: var(--text-dim);
  background: var(--surface-2); border: 1px solid var(--border-2); }
.rt-list { display: flex; flex-direction: column; gap: 4px; margin-top: 10px;
  max-height: min(52vh, 420px); overflow-y: auto; }
.rt-row { display: flex; align-items: center; gap: 12px; width: 100%; padding: 8px 10px; text-align: left;
  font-family: var(--font); color: inherit; background: none; cursor: pointer;
  border: 1px solid transparent; border-radius: var(--radius); transition: background .12s ease; }
.rt-row:hover:not(:disabled) { background: var(--surface-2); }
.rt-row.on { background: color-mix(in srgb, var(--accent) 10%, transparent);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.rt-row:disabled { opacity: .5; cursor: not-allowed; }
.rt-row[hidden] { display: none; }
.rt-list > .hint[hidden] { display: none; }
.rt-more { align-self: flex-start; margin-top: 4px; }
.rt-who { min-width: 0; flex: 1 1 auto; }
.rt-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rt-sub { font-size: .78em; color: var(--muted); }
.rt-badge { flex: 0 0 auto; }
.rt-fit { font-size: .76em; color: var(--muted); white-space: nowrap; }
.rt-fit.some { padding: 2px 10px; border-radius: 99px; color: var(--text-dim);
  background: var(--surface-2); border: 1px solid var(--border-2); }
/* .tg-badge — тот же шильдик «Общие окна», что на витрине: одно совпадение — один вид. */
.rt-badge .tg-badge { display: inline-block; white-space: nowrap; }
.rt-row .cd-presence { border-color: var(--surface); }
/* «Удалить учителя» — отдельно, у правого края: опасное действие не стоит вплотную
   к «Назначить», куда палец тянется по привычке. */
.rt .form-actions .rt-remove { margin-left: auto; white-space: nowrap; }
/* «Войти в класс» — главное действие страницы, поэтому она заметно выше обычной
   кнопки (по просьбе владельца продукта): рядом с ней в шапке больше ничего нет. */
.cd-enter { display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 18px 26px; font-size: .95em; }

/* ── Живой перелив кнопки «Войти в класс» ──────────────────────────
   Четыре слоя поверх заливки: два «жидких» пятна, плавающих навстречу друг другу,
   бегущий блик и статичный глянец сверху. Вместе это читается как перетекание
   света по воде, а не как мигание.
   ⚠️ Анимируются ТОЛЬКО transform и opacity (композитор): фон, фильтр и тень статичны —
   их анимация считается на процессоре и роняет кадры на слабом ноутбуке учителя ровно
   в тот момент, когда он открывает класс.
   ⚠️ Цвета ФИКСИРОВАННЫЕ и одинаковые в обеих темах (решение владельца продукта):
   кнопка не следует за акцентом школы — это самостоятельный «жидкий» градиент
   (малиновый · бирюза · персик), см. BUTTON_PALETTE в js/liquid.js. Раньше палитра
   считалась от акцента, и у зелёной школы кнопка выходила зелёной, у красной —
   красной: перелив есть, а нужного вида нет.
   ⚠️ Пятна вращаются вокруг СМЕЩЁННОГО центра (transform-origin в стороне): вокруг
   собственного центра большое размытое пятно почти не двигается на экране, и вместо
   перелива видно ровную заливку — это была первая, неудачная версия. */
.cd-enter { position: relative; overflow: hidden; isolation: isolate;
  /* Заливка и подпись — свои, не от .btn-primary: цвет кнопки один на обе темы, а
     белая подпись читается на всех цветах перелива (контраст ≥ 3:1 — норма для
     крупного жирного текста; замерено по яркости кадров шейдера). */
  background-color: #c92a63; color: #fff; border: 0;
  /* ⚠️ Тень у подписи обязательна: цвета перелива ЯРКИЕ (по просьбе владельца
     продукта), и на самых светлых нитях белый текст без неё теряется. Тень мягкая —
     на тёмных участках её не видно вовсе. */
  text-shadow: 0 1px 5px rgba(0, 0, 0, .45), 0 0 1px rgba(0, 0, 0, .35);
  /* Глянец сверху — background-IMAGE: заливка остаётся на месте, слои перелива лежат
     поверх и просвечивают. */
  background-image: linear-gradient(180deg, rgba(255,255,255,.26), transparent 48%);
  /* ⚠️ Ни рамки, ни тени (решение владельца продукта): цветное свечение вокруг
     затемняло фон карточки и спорило с самим переливом. */
  box-shadow: none;
  transition: transform .18s ease, filter .18s ease; }
.cd-enter > * { position: relative; z-index: 3; }        /* значок и подпись — над слоями */
.cd-enter .cd-enter-ico svg { filter: drop-shadow(0 1px 4px rgba(0, 0, 0, .45)); }
.cd-enter::before, .cd-enter-fx {
  content: ''; position: absolute; inset: -60%; z-index: 0; pointer-events: none;
  will-change: transform;
}
/* Пятна фолбэка — теми же цветами, что и шейдер (см. BUTTON_PALETTE). */
.cd-enter::before {
  background:
    radial-gradient(closest-side, #2bb3a8, transparent 72%) 22% 30% / 52% 52% no-repeat,
    radial-gradient(closest-side, #d92d6b, transparent 70%) 68% 60% / 50% 50% no-repeat,
    radial-gradient(closest-side, #a01a54, transparent 70%) 46% 86% / 46% 46% no-repeat;
  filter: blur(11px); opacity: .85; transform-origin: 32% 38%;
  animation: enter-swirl-a 9s linear infinite;
}
.cd-enter-fx {
  background:
    radial-gradient(closest-side, #e8703f, transparent 70%) 74% 24% / 44% 44% no-repeat,
    radial-gradient(closest-side, #2bb3a8, transparent 72%) 26% 72% / 44% 44% no-repeat,
    radial-gradient(closest-side, #d92d6b, transparent 70%) 52% 40% / 48% 48% no-repeat;
  filter: blur(13px); opacity: .7; transform-origin: 68% 62%;
  animation: enter-swirl-b 13s linear infinite;
}
/* Холст жидкого градиента (см. js/liquid.js). Лежит ПОД бликом и подписью, во всю
   кнопку; скруглённые углы даёт overflow: hidden самой кнопки. */
.liquid-canvas { position: absolute; inset: 0; z-index: 0; width: 100%; height: 100%;
  display: block; pointer-events: none; }
/* Когда шейдер поднялся, CSS-пятна не нужны — они только мутят картинку.
   ⚠️ Убираем их именно классом от JS, а не удалением правил: без WebGL (старый
   браузер, потерянный контекст) на кнопке должен остаться хоть какой-то перелив. */
.cd-enter.has-liquid::before, .cd-enter.has-liquid .cd-enter-fx { display: none; }
/* Блик — тонкая диагональная полоса, проходящая по кнопке раз в несколько секунд.
   Большую часть цикла стоит за краем: непрерывно бегающий блик читается как загрузка,
   а не как «живая» кнопка. */
.cd-enter::after {
  content: ''; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(105deg, transparent 38%,
    color-mix(in srgb, #fff 62%, transparent) 50%, transparent 62%);
  transform: translateX(-140%); will-change: transform;
  animation: enter-sheen 6.5s ease-in-out infinite;
}
/* Наведение отзывается яркостью, а не тенью — тени у кнопки больше нет. */
.cd-enter:hover { filter: brightness(1.06) saturate(1.06); }
.cd-enter:hover::before { opacity: .85; animation-duration: 6s; }
.cd-enter:hover .cd-enter-fx { opacity: .72; animation-duration: 9s; }
.cd-enter:active { transform: translateY(1px); }
/* ⚠️ В светлой теме перелив СВЕТЛЕЕ и мягче (правка владельца продукта 2026-10-20): на
   белой карточке тёмная фуксия «слишком бросалась в глаза». Палитру шейдера не трогаем
   (она общая с тёмной темой и с язычками витрины) — осветляем фильтром всю кнопку, белая
   подпись при этом остаётся белой, её держит text-shadow. Фильтр статичный — не анимируется. */
[data-theme="light"] .cd-enter { filter: brightness(1.16) saturate(.82); }
[data-theme="light"] .cd-enter:hover { filter: brightness(1.22) saturate(.88); }
/* Пятна ходят по кругу вокруг смещённой точки и дышат масштабом — так их видимые
   границы всё время перекраивают заливку. */
@keyframes enter-swirl-a {
  0%   { transform: rotate(0deg) scale(1); }
  50%  { transform: rotate(180deg) scale(1.25); }
  100% { transform: rotate(360deg) scale(1); }
}
@keyframes enter-swirl-b {
  0%   { transform: rotate(360deg) scale(1.18); }
  50%  { transform: rotate(180deg) scale(.9); }
  100% { transform: rotate(0deg) scale(1.18); }
}
@keyframes enter-sheen {
  0%, 60%   { transform: translateX(-140%); }
  85%, 100% { transform: translateX(160%); }
}
/* Заблокированный класс: перелив выключен — кнопка ведёт не в класс, а к объяснению,
   и «приглашать» нажать её нечестно. */
.cd-enter.btn-locked::before, .cd-enter.btn-locked::after, .cd-enter.btn-locked .cd-enter-fx {
  animation: none; opacity: .2;
}
/* ⚠️ «Уменьшение движения» ЗАМЕДЛЯЕТ перелив, а не выключает его (`animation: none`
   здесь и стояло — на телефоне владельца продукта, где эта настройка iOS включена
   постоянно, кнопка выглядела застывшей картинкой). Перетекание цвета — не тот вид
   движения, от которого настройка защищает: ни параллакса, ни скачков в нём нет.
   То же решение, что у сферы онбординга (замедлить втрое) и у нижнего листа модалок. */
@media (prefers-reduced-motion: reduce) {
  .cd-enter::before { animation-duration: 26s; }
  .cd-enter-fx { animation-duration: 37s; }
  .cd-enter::after { animation-duration: 14s; }
}

/* — Карточка человека (ученик у учителя, учитель у ученика) — */
.cd-members { display: flex; flex-direction: column; gap: 10px; margin-bottom: 16px; }
/* ⚠️ Горизонтальные поля — ТЕ ЖЕ 24px, что у шапки: правый край кнопки «Войти в класс»
   обязан совпасть с правым краем последней кнопки «Мотивировать», а при разных полях
   они расходились бы на 8px при любой ширине окна. */
.cd-person { display: flex; align-items: center; gap: 14px; padding: 13px 24px; margin-bottom: 16px;
  background: var(--surface); border: 1px solid transparent; border-radius: calc(var(--radius) + 4px); }
.cd-members .cd-person { margin-bottom: 0; }
.avatar.cd-person-ava { width: 50px; height: 50px; font-size: 1.25em; }
.cd-person-main { min-width: 0; }
.cd-person-caprow { display: flex; align-items: center; gap: 8px; }
.cd-person-cap { font-size: .7em; text-transform: uppercase; letter-spacing: .9px; color: var(--muted); }
.cd-person-name { font-size: 1.02em; margin-top: 1px; }
.cd-person-sub { font-size: .8em; color: var(--muted); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Чип «Чат» у роли: переписка — свойство самого человека, поэтому она стоит у имени,
   а не в ряду действий справа (там теперь баланс и «Мотивировать»). */
.cd-person-chat { display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
  padding: 3px 10px; border-radius: 99px; font-family: var(--font); font-size: .74em;
  color: var(--text-dim); background: var(--surface-2); border: 1px solid var(--border-2);
  transition: all .14s ease; }
.cd-person-chat svg { width: 13px; height: 13px; }
.cd-person-chat:hover { color: var(--accent-text); border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent); }
/* Правая часть панели: баланс · группа действий («Мотивировать» / «Оплата») · «убрать».
   Группы разделены тонкой чертой — это разные по смыслу вещи, стоящие в одну строку.
   ⚠️ align-items: stretch (а не center) — от него зависит вся раскладка: колонки равной
   высоты дают ОДНУ линию подписей сверху и общий нижний край содержимого. */
.cd-person-side { margin-left: auto; display: flex; align-items: stretch; gap: 18px; flex-wrap: wrap; }
.cd-person-group { display: flex; flex-direction: column; gap: 8px; padding-left: 18px;
  border-left: 1px solid var(--border); }
.cd-person-bal { padding-left: 0; border-left: none; }
/* Подпись группы — ОДНА на все группы панели: «Баланс ученика» и «Мотивировать» стоят
   рядом, и разный кегль/регистр у них читался бы как ошибка вёрстки. Без uppercase —
   капс растягивал «Мотивировать» шире собственных кнопок. */
.cd-side-cap { font-size: .8em; color: var(--muted); white-space: nowrap; line-height: 1.2; }
/* Подпись группы действий центрируется НАД кнопками («Мотивировать», «Оплата»);
   у баланса она остаётся у левого края — там под ней число, а не ряд кнопок. */
.cd-person-group:not(.cd-person-bal) .cd-side-cap { text-align: center; }
/* Содержимое прижато к низу: нижний край «💎 13» совпадает с нижним краем кнопок. */
.cd-side-body { margin-top: auto; }
.cd-person-acts { display: flex; align-items: flex-end; gap: 8px; }
/* Кнопки «Мотивировать» — квадратные: в них только значок, и прямоугольник .btn-sm
   выглядел бы приплюснутым рядом с двумя строками баланса. */
.cd-ico-btn { width: 46px; height: 46px; padding: 0; border-radius: 12px;
  display: inline-grid; place-items: center; }
.cd-ico-btn > svg { width: 19px; height: 19px; }
.cd-person-del:hover { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, transparent); }
.cd-member-role { color: var(--muted); font-weight: 400; }
.cd-nostudent { justify-content: space-between; flex-wrap: wrap; }

/* — Плитки показателей — */
.cd-dash { display: flex; flex-direction: column; gap: 14px; }
.cd-stat-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.cd-stat { display: flex; align-items: center; gap: 14px; min-width: 0; padding: 16px 18px;
  background: var(--surface); border: 1px solid transparent; border-radius: calc(var(--radius) + 4px); }
.cd-stat-ico { flex: 0 0 auto; display: grid; place-items: center; width: 42px; height: 42px;
  border-radius: 13px; color: var(--accent-text); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.cd-stat-ico svg { width: 21px; height: 21px; }
/* Тон плитки — тот же сигнал «плохо/средне/хорошо», что и у оценок в «Пользователях»
   (--grade-*): это индикация, а не палитра школы, и она специально вне темы. */
.cd-stat-ico.good { color: var(--grade-good); background: color-mix(in srgb, var(--grade-good) 13%, transparent); }
.cd-stat-ico.mid  { color: var(--grade-mid);  background: color-mix(in srgb, var(--grade-mid) 15%, transparent); }
.cd-stat-ico.low  { color: var(--grade-low);  background: color-mix(in srgb, var(--grade-low) 13%, transparent); }
.cd-stat-ico.na   { color: var(--muted);      background: var(--surface-2); }
.cd-stat-body { min-width: 0; }
.cd-stat-row { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.cd-stat-val { font-size: 1.9rem; font-weight: 650; line-height: 1.1; letter-spacing: -.5px;
  font-variant-numeric: tabular-nums; color: var(--text); }
.cd-stat-val.good { color: var(--grade-good); }
.cd-stat-val.mid  { color: var(--grade-mid); }
.cd-stat-val.low  { color: var(--grade-low); }
/* «N/A» — не балл, и притворяться цифрой оно не должно (то же правило, что в «Пользователях»). */
.cd-stat-val.na { font-size: 1.15rem; font-weight: 600; letter-spacing: .5px; color: var(--muted); }
.cd-stat-cap { font-size: .86em; color: var(--text-dim); }
.cd-stat-sub { font-size: .75em; color: var(--muted); margin-top: 3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cd-empty { color: var(--muted); font-size: .9em; padding: 16px 0; }

/* — Лента прогресса: когда занятие было и чем кончилось — */
.cd-prog { padding: 18px 20px; background: var(--surface); border: 1px solid transparent;
  border-radius: calc(var(--radius) + 4px); }
.cd-panel-title { font-size: .95em; font-weight: 600; margin: 0 0 16px; }
.cd-tl-scroll { overflow-x: auto; overscroll-behavior-x: contain; padding-bottom: 4px; }
.cd-tl { display: flex; min-width: min-content; }
.cd-tl-item { position: relative; flex: 1 1 0; min-width: 132px; padding: 26px 14px 0 0;
  --tl-color: var(--border-2); }
/* Рельс идёт во всю ширину ячейки, точка сидит на её левом краю: цвет отрезка = исход
   ИМЕННО этого занятия, а не соседнего. */
.cd-tl-item::before { content: ''; position: absolute; left: 0; right: 0; top: 6px; height: 4px;
  background: var(--tl-color); border-radius: 99px; }
.cd-tl-dot { position: absolute; left: 0; top: 0; width: 16px; height: 16px; border-radius: 50%;
  background: var(--surface); border: 4px solid var(--tl-color); box-sizing: border-box; }
.cd-tl-item.ok    { --tl-color: var(--lesson-done); }
.cd-tl-item.bad   { --tl-color: var(--noshow); }
.cd-tl-item.nobal { --tl-color: #a78bfa; }
.cd-tl-item.warn  { --tl-color: var(--warn); }
.cd-tl-item.off,
.cd-tl-item.next  { --tl-color: var(--border-2); }
.cd-tl-date { font-size: .78em; color: var(--muted); }
.cd-tl-title { font-size: .9em; font-weight: 600; margin-top: 3px; }
.cd-tl-note { font-size: .78em; color: var(--muted); margin-top: 2px; }
.cd-tl-item.next .cd-tl-title, .cd-tl-item.next .cd-tl-date { color: var(--muted); font-weight: 500; }

/* — Материалы и доски: две панели рядом — */
.cd-panels { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 18px; }
.cd-panel { min-width: 0; padding: 16px 18px; background: var(--surface);
  border: 1px solid transparent; border-radius: calc(var(--radius) + 4px); }
.cd-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
.cd-panel-head .cd-panel-title { margin: 0; }
.cd-panel-headl { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* «+» у заголовка — добавить из банка. Круглая, ровно под размер строки заголовка:
   рядом стоит ссылка «Все …», и обычная кнопка с подписью перетягивала бы её на себя. */
.cd-panel-add { display: grid; place-items: center; width: 26px; height: 26px; flex: 0 0 auto;
  padding: 0; border-radius: 9px; cursor: pointer; color: var(--text-dim);
  background: var(--surface-2); border: 1px solid var(--border-2); transition: all .14s ease; }
.cd-panel-add svg { width: 15px; height: 15px; }
.cd-panel-add:hover { color: var(--accent-text); border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent); }
/* Ссылка «Все …» — это <button> (открывает окно, а не переход), поэтому кнопочные
   умолчания браузера снимаем явно: без font-family подпись ушла бы дефолтным Arial. */
.cd-panel-link { font-size: .8em; color: var(--accent-text); white-space: nowrap; cursor: pointer;
  background: none; border: 0; padding: 0; font-family: var(--font); }
.cd-panel-link:hover { text-decoration: underline; }
/* Книга, уже добавленная в класс: строка приглушена и не нажимается — повторное
   добавление всё равно упёрлось бы в отказ сервера. */
.cd-mat-row.is-added { opacity: .55; }
.cd-mat-row.is-added:hover { background: none; }
.cd-mat-added { font-size: .75em; color: var(--muted); white-space: nowrap; }
.cd-panel-empty { color: var(--muted); font-size: .86em; padding: 10px 0; }
.cd-mat-list { display: flex; flex-direction: column; gap: 4px; }
.cd-mat-row { display: flex; align-items: center; gap: 12px; padding: 8px; border-radius: 12px;
  transition: background .15s ease; }
.cd-mat-row:hover { background: var(--surface-2); }
.cd-mat-cover { flex: 0 0 auto; width: 42px; height: 42px; border-radius: 12px; display: grid;
  place-items: center; background-size: cover; background-position: center; color: #fff;
  font-weight: 600; font-size: 1.05em; }
.cd-mat-main { min-width: 0; flex: 1 1 auto; }
.cd-mat-title { font-size: .9em; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cd-mat-meta { display: flex; gap: 10px; font-size: .74em; color: var(--muted); margin-top: 2px; }
/* Удаление показывается по наведению — на строке материала оно вторично; на тач-экране
   :hover не наступает вовсе, поэтому там кнопка видна всегда (правило ниже). */
.cd-mat-del { opacity: 0; transition: opacity .15s ease; }
.cd-mat-row:hover .cd-mat-del { opacity: 1; }
.cd-mat-del:hover { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, transparent); }
@media (hover: none) { .cd-mat-del { opacity: 1; } }
/* Ровно три доски в ряд: панель показывает превью (BOARDS_PREVIEW), остальные — за
   ссылкой «Все доски». auto-fill тут не годится — при одной доске она растянулась бы
   во всю панель и выглядела бы иначе, чем при трёх. */
/* ⚠️ Селектор составной (.cd-panel .cd-board-grid), а не голый класс: общий
   .board-lib-grid объявлен НИЖЕ по файлу и при равной специфичности выигрывал —
   панель раскладывалась его auto-fill'ом по 220px, то есть в две колонки. */
.cd-panel .cd-board-grid { grid-template-columns: repeat(3, 1fr); gap: 10px; }

@media (max-width: 1080px) {
  /* Действия уходят под шапку целиком: кнопка «Войти в класс» должна оставаться
     широкой и нажимаемой, а не сжиматься до иконки. */
  .cd-hero { grid-template-columns: minmax(0, 1fr) auto; }
  .cd-hero.has-teacher { grid-template-columns: minmax(0, 1fr) auto; }
  .cd-hero-acts { grid-column: 1 / -1; flex-direction: row; }
  .cd-hero-acts .cd-enter { flex: 1 1 auto; }
  .cd-panels { grid-template-columns: 1fr; }
}
@media (max-width: 760px) {
  .cd-hero { grid-template-columns: 1fr; gap: 16px; padding: 16px; }
  .cd-hero.has-teacher { grid-template-columns: 1fr; }
  .cd-ht-name { max-width: none; }
  .cd-hero-acts { flex-direction: column; min-width: 0; }
  .cd-hero-name { font-size: 1.4rem; white-space: normal; }
  .avatar.avatar-hero { width: 76px; height: 76px; font-size: 1.9em; border-radius: 20px; }
  .cd-stat-grid { grid-template-columns: 1fr; }
  .cd-stat { padding: 13px 14px; }
  .cd-stat-val { font-size: 1.6rem; }
  .cd-person { flex-wrap: wrap; }
  /* Баланс и действия уходят на свою строку под именем: в ширину телефона они рядом
     с аватаром и именем не помещаются. Разделительная черта там становится верхней —
     вертикальная в столбце висела бы сбоку от одной кнопки. */
  /* align-items наследуется (stretch): подписи групп остаются на одной линии и на
     телефоне — с flex-end они разъезжались по вертикали. */
  .cd-person-side { margin-left: 0; width: 100%; gap: 12px; }
  .cd-person-group { padding-left: 0; border-left: none; }
  .cd-person-bal { flex: 1 1 auto; }
  .cd-person-acts .btn { justify-content: center; }
  .cd-tl-item { min-width: 118px; }
  /* Три превью доски в ряд на телефоне превращаются в марки ~100px — там их две,
     третья уходит на вторую строку (сами превью в панели по-прежнему BOARDS_PREVIEW). */
  .cd-panel .cd-board-grid { grid-template-columns: repeat(2, 1fr); }
}

.class-bal {
  display: inline-flex; align-items: center; gap: 2px; font-size: .8em;
  padding: 3px 10px; border-radius: 99px; color: var(--badge-accent);
  background: color-mix(in srgb, var(--badge-accent) 8%, transparent); border: 1px solid color-mix(in srgb, var(--badge-accent) 35%, transparent);
}
.class-bal b { font-weight: 700; }
.class-bal.low { color: var(--danger); background: color-mix(in srgb, var(--danger) 10%, transparent); border-color: color-mix(in srgb, var(--danger) 40%, transparent); }
button.class-bal.editable { cursor: pointer; transition: border-color .15s, background .15s; font-family: inherit; }
button.class-bal.editable:hover { border-color: var(--badge-accent); background: color-mix(in srgb, var(--badge-accent) 16%, transparent); }
.class-bal-edit { margin-left: 5px; font-weight: 700; opacity: .7; }
.class-locked { font-size: 1.1em; flex: 0 0 auto; }
.btn-locked { opacity: .6; }

/* ══ Пополнение баланса: окно — заглушка (иконка `.empty`), тарифной сетки больше нет.
   Экран возврата с платёжной формы остаётся на случай старых ссылок. ══ */
.pay-return-wrap { max-width: 520px; margin: 8vh auto 0; }
.pay-return { text-align: center; }

/* ══ Виртуальная комната класса ════════════════════════════════ */
.room { display: flex; gap: 20px; align-items: flex-start; }
.room-book-pane { flex: 1 1 auto; min-width: 0; }

/* Присутствие (онлайн) */
.room-presence { display: flex; gap: 8px; flex-wrap: wrap; }
.room-online {
  display: inline-flex; align-items: center; gap: 6px; font-size: .78em; color: var(--text-dim);
  padding: 3px 10px; border-radius: 99px; background: var(--surface); border: 1px solid transparent;
}
.room-online.me { color: var(--text); }
/* Точка «онлайн» — всегда зелёная, вне палитры школы (как --grade-*): красный акцент
   школы делал её неотличимой от «офлайн». Токен --grade-good имеет свои значения
   для тёмной и светлой темы. */
.online-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--grade-good); box-shadow: 0 0 6px color-mix(in srgb, var(--grade-good) 70%, transparent); }

/* Шапка комнаты: слева крупное имя класса, справа — присутствие + «Выйти». */
.room-head { align-items: flex-start; margin-bottom: 18px; }
.room-title { font-size: 1.5em; font-weight: 300; }
/* flex-grow 1: когда правая часть переносится под название (телефон, двое в комнате), она
   тянется на всю строку и прижимает «✕ Выйти» к ПРАВОМУ краю — без этого перенесённая
   группа вставала слева, и кнопка оказывалась посередине экрана. */
.room-head-right { flex: 1 1 auto; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; justify-content: flex-end; }
/* Закреплённые присутствие и «✕ Выйти» (класс .pinned ставит room.js вместе с .stuck у панели
   учебника): на ПК встают в полосу над чатом — колонка чата приклеена с top:76px, и выше
   неё при прокрутке было пусто. `right` считает JS по краю колонки чата. На телефоне
   (≤900px) колонка чата уезжает вниз, и закреплять тут нечего — правило только для ПК. */
@media (min-width: 901px) {
  .room-head-right.pinned { position: fixed; top: 14px; z-index: 31; }
}

/* Верхняя панель учебника: карточка «Книга/Урок» + кнопка домашки + панель инструментов.
   На десктопе — одна строка, на узких экранах переносится (см. @media 900px).
   ⚠️ Панель ЗАКРЕПЛЕНА у верха окна при прокрутке (правка владельца продукта): книга,
   урок, домашка и инструменты нужны на любой высоте урока. Подложка (::before) включается
   только в закреплённом виде — класс .stuck ставит room.js (bindStickyHead): иначе урок
   просвечивал бы в зазорах между карточками, а в покое полоса легла бы поверх фона.
   ⚠️ Сами карточки панели — ТЕМНЕЕ карточек урока (--bg вместо --surface), текст на 20%
   прозрачнее: панель служебная и не должна спорить с текстом урока за внимание. */
.room-book-head { margin-bottom: 14px; display: flex; flex-wrap: wrap; align-items: stretch; gap: 10px;
  position: sticky; top: 8px; z-index: 30; }
.room-book-head::before {
  /* ⚠️ Низ подложки — ровно по низу панели (inset снизу 0, правка владельца продукта):
     выступ вниз визуально утолщал закреплённую панель. Вверх и вбок выступ остаётся —
     сверху он закрывает зазор до края окна, сбоку прячет урок в зазорах карточек. */
  content: ''; position: absolute; inset: -8px -10px 0; z-index: -1; pointer-events: none;
  /* Чёрная «шторка» в обеих темах (правка владельца продукта): светлая подложка вокруг
     почерневших карточек читалась белой каймой. Литеральный цвет — намеренно без оверрайда. */
  background: #0b0b0b;
  border-radius: 0 0 var(--radius) var(--radius); opacity: 0; transition: opacity .6s cubic-bezier(.45, 0, .55, 1);
}
.room-book-head.stuck::before { opacity: 1; }
/* ⚠️ На ПК закреплённая панель плавно становится ЧЁРНОЙ, текст и значки — светлыми (правка
   владельца продукта): в светлой теме белые карточки панели сливались с карточками урока
   под ней. Чёрная в ОБЕИХ темах — цвета литеральные и намеренно без оверрайда (как и у
   мобильной закреплённой панели ниже). Кривая и .6s — те же, что у мобильной подложки. */
/* Селекторы через .room-book-head — (0,2,0): собственные transition карточек и кнопок
   объявлены ниже по файлу и при равной специфичности перебили бы этот. */
.room-book-head .room-nav-card, .room-book-head .room-hw-btn, .room-book-head .room-tools,
.room-book-head .room-toolbtn, .room-book-head .room-pick, .room-book-head .room-nav-label {
  transition: background-color .6s cubic-bezier(.45, 0, .55, 1), color .6s cubic-bezier(.45, 0, .55, 1),
    border-color .15s;
}
@media (min-width: 761px) {
  .room-book-head.stuck .room-nav-card, .room-book-head.stuck .room-hw-btn,
  .room-book-head.stuck .room-tools { background: #0b0b0b; }
  .room-book-head.stuck .room-nav-label { color: rgba(236, 236, 236, .45); }
  .room-book-head.stuck .room-pick { color: rgba(236, 236, 236, .85); }
  .room-book-head.stuck .room-pick-lesson { color: rgba(236, 236, 236, .65); }
  .room-book-head.stuck .room-hw-btn { color: rgba(236, 236, 236, .8); }
  .room-book-head.stuck .room-toolbtn { background: #1a1a1a; color: rgba(236, 236, 236, .8); }
  .room-book-head.stuck .room-toolbtn.active { color: var(--selected); }
  .room-book-head.stuck .room-pick:hover, .room-book-head.stuck .room-hw-btn:hover,
  .room-book-head.stuck .room-toolbtn:hover { color: var(--accent-text); }
}

/* Карточка выбора книги и урока — две строки «подпись слева · значение справа».
   ⚠️ У самих значений НЕТ рамки и заливки (правка владельца продукта): карточка уже
   обведена, и вложенные в неё поля читались как ещё один слой панелей. Иерархию
   держит НАЧЕРТАНИЕ — книга жирная и в основном цвете, урок тонкий и приглушённый. */
.room-nav-card {
  flex: 1 1 300px; min-width: 0; display: flex; flex-direction: column; justify-content: center; gap: 0;
  background: var(--bg); border: 1px solid transparent; border-radius: var(--radius); padding: 5px 14px;
}
.room-nav-row { display: flex; align-items: baseline; gap: 12px; min-width: 0; }
.room-nav-label { flex: 0 0 auto; font-size: .8em; color: color-mix(in srgb, var(--muted) 80%, transparent); }
.room-nav-static { margin-left: auto; min-width: 0; font-size: .95em; font-weight: 700; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 5px 0; }

/* Кнопка домашней работы (домик) — заглушка. */
.room-hw-btn {
  flex: 0 0 auto; width: 62px; display: inline-flex; align-items: center; justify-content: center;
  background: var(--bg); border: 1px solid transparent; border-radius: var(--radius);
  color: color-mix(in srgb, var(--text-dim) 80%, transparent);
  cursor: pointer; transition: border-color .15s, color .15s, background .15s;
}
.room-hw-btn:hover { border-color: var(--accent-border); color: var(--accent-text); }
.room-hw-btn .ico svg { display: block; width: 22px; height: 22px; }

/* Панель «Инструменты» — четыре кнопки ОДНИМ рядом (не сеткой 2×2), мелким кеглем.
   Заголовка «Инструменты» нет (правка владельца продукта) — панель в одну строку. */
.room-tools {
  flex: 1 1 360px; min-width: 0; display: flex; flex-direction: column; justify-content: center; gap: 8px;
  background: var(--bg); border: 1px solid transparent; border-radius: var(--radius); padding: 6px 8px;
}
.room-tools-row { display: flex; flex-wrap: nowrap; gap: 6px; }
.room-toolbtn {
  flex: 1 1 0; min-width: 0; display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  padding: 6px 6px; border-radius: var(--radius-sm); cursor: pointer; font-size: .74em; white-space: nowrap;
  background: var(--surface); border: 1px solid transparent; color: color-mix(in srgb, var(--text) 80%, transparent);
  transition: border-color .15s, color .15s, background .15s;
}
.room-toolbtn:hover { border-color: var(--accent-border); color: var(--accent-text); }
.room-toolbtn.active { color: var(--selected); border-color: var(--accent-border);
  background: color-mix(in srgb, var(--accent) 14%, var(--surface-2)); }
.room-toolbtn .ico { flex: 0 0 auto; display: inline-flex; }
.room-toolbtn .ico svg { display: block; width: 14px; height: 14px; }
.room-toolbtn-lbl { overflow: hidden; text-overflow: ellipsis; }
/* ⚠️ «Указка» скрыта на тач-устройствах: она работает по наведению курсора, а на телефоне
   движение пальца по учебнику прокручивает страницу, а не наводит точку — кнопка там бесполезна. */
@media (hover: none) { .room-toolbtn-laser { display: none; } }

/* Лазерная указка комнаты (как на интерактивной доске): красная светящаяся точка,
   плавает над учебником по курсору ведущего и видна остальным в реальном времени. */
.room-laser-dot {
  position: fixed; z-index: 250; width: 20px; height: 20px; margin: -10px 0 0 -10px;
  border-radius: 50%; pointer-events: none; opacity: 0; transition: opacity .16s ease;
  background: radial-gradient(circle, #ffe6e6 0 16%, #ff4646 42%, rgba(255,64,64,.35) 68%, transparent 72%);
  box-shadow: 0 0 15px 4px rgba(255,40,40,.85), 0 0 34px 10px rgba(255,30,30,.4);
}
.room-laser-dot.on { opacity: 1; }
body.room-laser .room-content, body.room-laser .room-content * { cursor: crosshair; }

/* ── Заметки комнаты класса (инструмент «Инструменты → Заметки») ── */
/* Заголовок «Заметки» — по центру, мельче и приглушённее (правка владельца продукта). */
.modal:has(> .rn-modal) > .modal-title { text-align: center; font-size: .92em; font-weight: 500;
  color: var(--muted); margin-bottom: 14px; }

/* ⚠️ align-items: flex-start — крестик стоит НА УРОВНЕ ИМЕНИ ученика, а не по центру
   высокого блока с аватаром (аватар выше, и по центру крестик уезжал вниз). */
.rn-head { display: flex; align-items: flex-start; gap: 14px; margin: 0 0 14px; }
.rn-head-main { flex: 1 1 auto; min-width: 0; padding-top: 2px; }
.rn-head-name { font-size: 1.5em; font-weight: 300; line-height: 1.15;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rn-head-lang { color: var(--text-dim); font-size: .92em; margin-top: 3px; }
.rn-close {
  flex: 0 0 auto; width: 34px; height: 34px; display: inline-flex; align-items: center; justify-content: center;
  background: none; border: none; color: var(--text-dim); cursor: pointer; border-radius: 8px;
}
.rn-close:hover { color: var(--text); background: var(--surface-2); }
.rn-close svg { width: 24px; height: 24px; }

.rn-list { display: flex; flex-direction: column; gap: 14px; max-height: 62vh; overflow-y: auto;
  overscroll-behavior: contain; padding-right: 4px; }
.rn-add {
  display: flex; align-items: center; justify-content: center; padding: 10px; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text-dim);
  transition: border-color .15s, color .15s;
}
.rn-add:hover { border-color: var(--accent-border); color: var(--accent-text); }
.rn-add svg { width: 20px; height: 20px; }
.rn-empty { color: var(--muted); font-size: .9em; text-align: center; padding: 18px 0; }

.rn-note {
  display: block; width: 100%; text-align: left; padding: 14px 16px; cursor: pointer;
  background: var(--surface-2); border: 1px solid transparent; border-radius: var(--radius); color: var(--text);
  transition: border-color .15s, background .15s;
}
.rn-note:hover { border-color: var(--border-2); }
.rn-note-date { color: var(--text-dim); font-size: 1.05em; text-align: right; font-variant-numeric: tabular-nums; margin-bottom: 10px; }
.rn-note-body { white-space: pre-wrap; line-height: 1.6; word-break: break-word; font-size: 1.1em;
  display: -webkit-box; -webkit-line-clamp: 10; -webkit-box-orient: vertical; overflow: hidden; }

.rn-edit { display: flex; flex-direction: column; gap: 12px; }
.rn-edit-top { display: flex; align-items: center; gap: 10px; }
.rn-edit-date { flex: 1 1 auto; color: var(--text-dim); font-size: .88em; font-variant-numeric: tabular-nums; }
.rn-back, .rn-del {
  flex: 0 0 auto; width: 34px; height: 34px; display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; color: var(--text-dim); cursor: pointer;
  transition: border-color .15s, color .15s;
}
.rn-back:hover { border-color: var(--accent-border); color: var(--accent-text); }
.rn-del:hover { border-color: color-mix(in srgb, var(--danger) 55%, transparent); color: var(--danger); }
.rn-back svg, .rn-del svg { width: 17px; height: 17px; }
.rn-edit-area { min-height: 240px; }
.rn-edit-actions { display: flex; align-items: center; justify-content: flex-end; gap: 10px; }
.rn-count { margin-right: auto; color: var(--muted); font-size: .82em; font-variant-numeric: tabular-nums; }
.rn-count.full { color: var(--warn); }

/* Селектор книги/урока: голый текст с кареткой, прижат к ПРАВОМУ краю строки. */
.room-pick {
  display: inline-flex; align-items: center; gap: 8px; min-width: 0; max-width: 100%;
  margin-left: auto; padding: 3px 0; cursor: pointer; font-size: .84em; text-align: right;
  background: none; border: none; color: color-mix(in srgb, var(--text) 80%, transparent); transition: color .15s;
}
.room-pick:hover { color: var(--accent-text); }
.room-pick-mat { font-weight: 700; }
.room-pick-lesson { font-weight: 400; color: color-mix(in srgb, var(--text-dim) 80%, transparent); }
.room-pick-ico { flex: 0 0 auto; }
.room-pick-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.room-pick-caret { flex: 0 0 auto; color: var(--muted); font-size: .8em; }
.room-pick-lesson.done { color: var(--accent-text); }

/* Пункт «＋ Добавить материал» в поповере выбора книги */
.room-pop-add {
  display: flex; align-items: center; gap: 9px; width: 100%; margin-top: 4px; padding: 9px 10px;
  border-radius: 8px; cursor: pointer; font-size: .88em; text-align: left;
  background: transparent; border: 1px solid transparent; border-top: 1px solid var(--border); color: var(--accent-text);
}
.room-pop-add:hover { background: var(--surface-2); }

/* Дропдаун-навигация (поповер) */
.room-pop-layer { position: fixed; inset: 0; z-index: 90; }
.room-pop {
  position: absolute; display: flex; flex-direction: column; overflow: hidden;
  background: var(--surface); border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  box-shadow: var(--shadow); padding: 6px;
}
.room-pop-search { margin-bottom: 6px; flex: 0 0 auto; }
/* min-height:0 обязателен: без него флекс-элемент не сжимается ниже своей высоты по контенту,
   overflow-y не срабатывает → список клиппится, а колесо прокручивает страницу вместо списка.
   overscroll-behavior:contain — чтобы прокрутка не «протекала» на страницу на краях списка. */
.room-pop-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: 2px; }
.room-pop-item {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  padding: 9px 10px; border-radius: 8px; cursor: pointer; font-size: .9em;
  background: transparent; border: 1px solid transparent; color: var(--text-dim);
  transition: background .12s, color .12s;
}
.room-pop-item:hover { background: var(--surface-2); color: var(--text); }
.room-pop-item.active { background: var(--surface-2); color: var(--selected); border-color: var(--accent-border); }
.room-pop-num { flex: 0 0 auto; width: 22px; text-align: center; font-size: .8em; color: var(--muted);
  font-variant-numeric: tabular-nums; }
.room-pop-item.active .room-pop-num { color: var(--selected); }
.room-pop-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.room-pop-ico, .room-pop-check { flex: 0 0 auto; }
.room-pop-check { color: var(--accent-text); }
.room-pop-empty { padding: 16px 10px; text-align: center; color: var(--muted); font-size: .88em; }

.room-sec-score { font-size: .85em; color: var(--accent-text); font-variant-numeric: tabular-nums; }
.room-content { margin-top: 0; }
.room-exercises { display: flex; flex-direction: column; gap: 12px; }
.room-ex { background: var(--surface); border: 1px solid transparent; border-radius: var(--radius); padding: 14px 16px; transition: box-shadow .2s, border-color .2s; }
.room-ex:hover { border-color: var(--border-2); }
/* Переносы строк из конструктора сохраняем везде, где рендерится упражнение (комната/книга),
   а не только в плеере (.slide-view) */
/* Текст теории — на 25% тусклее заголовка (.ex-h = --text), правка владельца продукта:
   от --text-dim не считаем — в палитре школы он бывает равен белому. */
.ex-render .body-text { white-space: pre-wrap; color: color-mix(in srgb, var(--text) 75%, transparent); line-height: 1.8; }
.room-ex-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.room-ex-head .book-ex-type { margin-bottom: 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.room-ex-head:empty { display: none; }
.room-ex-tools { display: flex; gap: 4px; flex: 0 0 auto; margin-left: auto; }
.btn-xs { padding: 3px 9px; font-size: .76em; border-radius: 7px; }
/* ⚠️ Инструменты упражнения (Сбросить · Скрыть · В домашку · Указать) — МЯГКАЯ ЗАЛИВКА
   вместо рамки. Это единственное место, где кнопки идут плотным рядом у КАЖДОГО
   упражнения: четыре контура на карточку × десяток упражнений и давали ощущение
   перегруженного экрана. Заливка держит кликабельность, а `border-color` из :hover
   и .active по-прежнему подсвечивает — рамка обесцвечена, а не удалена. */
.room-tool { gap: 5px; border-color: transparent; background: var(--surface-2); }
.room-tool.active { color: var(--selected); border-color: var(--accent-border); }
/* ⚠️ Только иконки, приглушённые (правка владельца продукта): подписи и яркие значки у
   каждого упражнения отвлекали от самого урока. Смысл кнопки — быстрая подсказка
   `data-tip` по наведению (родной title браузера ждёт ~1 с). */
/* 2026-10-20: на ПК кнопки меньше прежних 30px — сначала −30% (21px), затем +15% (24px):
   21 оказалось мелко. Подложка на 20% прозрачнее, значок тусклее (в обеих темах — от
   --text-dim, поэтому в тёмной глушится белый, в светлой тёмный). Правки владельца продукта.
   На телефоне остаются 30px (см. @media 760px): мельче пальцем не попасть. */
.room-tool { position: relative; width: 24px; height: 24px; min-height: 24px; padding: 0; gap: 0; justify-content: center;
  border-radius: 7px; color: color-mix(in srgb, var(--text-dim) 60%, transparent);
  background: color-mix(in srgb, var(--surface-2) 80%, transparent); }
.room-tool:hover { color: var(--text); }
.room-tool .rt-label { display: none; }
.room-tool .rt-ico svg { width: 14px; height: 14px; }
.room-tool[data-tip]::after {
  content: attr(data-tip); position: absolute; top: calc(100% + 6px); right: 0; z-index: 40;
  padding: 5px 9px; border-radius: 7px; white-space: nowrap; pointer-events: none;
  font-size: 12px; font-weight: 400; line-height: 1.3; letter-spacing: normal; text-transform: none;
  background: var(--surface-2); color: var(--text); border: 1px solid var(--border-2);
  box-shadow: var(--card-shadow); opacity: 0; visibility: hidden; transition: opacity .12s .15s, visibility 0s .27s;
}
.room-tool[data-tip]:hover::after { opacity: 1; visibility: visible; transition: opacity .12s .15s, visibility 0s .15s; }
@media (hover: none) { .room-tool[data-tip]::after { display: none; } }
/* Аудио в комнате — минимальный плеер (макет владельца продукта): без пилюли-подложки и
   рамки, подпись к записи тусклее — блок не должен цеплять взгляд. */
.room-ex .ex-aplayer { background: none; border: none; padding: 0; margin: 2px 0; }
.room-ex .ex-aplayer ~ .ex-cap { color: var(--muted); }
.rt-ico { display: inline-flex; align-items: center; justify-content: center; line-height: 1; }
.rt-ico svg { display: block; }

/* Упражнение, скрытое учителем от ученика */
.room-ex-hint { display: none; align-items: center; gap: 8px; padding: 20px 16px;
  color: var(--muted); font-size: .9em; background: var(--surface-2);
  border: 1px dashed var(--border-2); border-radius: var(--radius-sm); }
.room-ex.ex-hidden { opacity: .55; }                              /* учителю: помечаем приглушением, контент виден */
.room-exercises.is-student .room-ex.ex-hidden { opacity: 1; }
.room-exercises.is-student .room-ex.ex-hidden > .ex-render { display: none; }
.room-exercises.is-student .room-ex.ex-hidden .room-ex-hint { display: flex; }
.room-foot { display: flex; align-items: center; gap: 14px; margin-top: 20px; }
.room-result { font-size: .95em; color: var(--accent-text); }

/* Подсветка-мигание при «указать на упражнение» */
@keyframes ex-point {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent); border-color: var(--border); }
  20%, 60% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 55%, transparent); border-color: var(--accent); }
}
.room-ex.pointed { animation: ex-point 1.8s ease; }

/* Ожидание участника — компактная пилюля рядом с кнопкой «Позвонить» (room-top-row), не на всю ширину.
   Оба — той же высоты (40px), что и навигатор материала/урока слева (room-pick/room-step), и на одном уровне с ним. */
.room-top-row { display: flex; align-items: flex-start; gap: 10px; }
.room-wait {
  /* Текст слева, «Позвать» справа — ОДНОЙ строкой. Раньше это была колонка (кнопка под
     подписью), и плашка занимала две строки там, где хватает одной; на узкой колонке
     чата пара «статус + действие» рядом читается быстрее, чем столбиком. */
  flex: 1 1 auto; min-width: 0; min-height: 40px; display: flex; align-items: center; gap: 8px;
  padding: 4px 6px 4px 12px; border-radius: var(--radius-sm);
  /* Цвет — светофор по состоянию партнёра (см. --wait-c ниже): зелёный «в классе»,
     жёлтый «ждём», красный «офлайн». Берём --grade-* : это сигнал «хорошо/средне/плохо»,
     а НЕ брендовый цвет, поэтому он намеренно вне палитры школы (как оценки, см. CLAUDE.md) —
     иначе у школы с перекрашенным акцентом «в классе» и «офлайн» могли бы слиться.
     Плашка — сплошная заливка (не еле заметный тон): статус партнёра должен считываться
     мгновенно, а не только при взгляде в упор. Затемняем --grade-* до контраста с белым
     текстом через color-mix, а не заводим отдельный литеральный цвет (правило проекта). */
  --wait-c: var(--grade-mid);
  background: color-mix(in srgb, var(--wait-c) 82%, black);
  border: 1px solid transparent;
  color: #fff;
  font-size: .82em; line-height: 1.3;
}
.room-wait.inclass { --wait-c: var(--grade-good); }   /* партнёр в комнате — можно звонить */
.room-wait.offline { --wait-c: var(--grade-low); }    /* платформа у него не открыта */
/* overflow-wrap: колонка чата — 306px, кнопок тут две («Неявка» + «Позвать»), и на них
   уходит больше половины ширины пилюли; без переноса ДЛИННОЕ слово в статусе не сжималось
   бы вместе с колонкой (overflow тут visible по умолчанию), а просто наезжало поверх кнопок. */
.room-wait-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; justify-content: center;
  overflow-wrap: anywhere; }
.room-wait-sub { margin-top: 2px; font-size: .92em; opacity: .85; }
/* «Неявка» и «Позвать ученика/учителя» — внутри плашки ожидания, у её правого края.
   Общий тёмный полупрозрачный низ (не цвет плашки): на сплошной заливке кнопка того же
   тона потерялась бы, а чёрная затемняющая подложка одинаково читается на любом --wait-c. */
.room-wait-call, .room-wait-noshow-btn { flex: 0 0 auto; padding: 3px 10px; white-space: nowrap;
  font-size: .95em; line-height: 1.35; min-height: 0;
  color: #fff; background: color-mix(in srgb, black 28%, transparent);
  border: 1px solid color-mix(in srgb, white 18%, transparent); }
.room-wait-call:hover:not(:disabled), .room-wait-noshow-btn:hover:not(:disabled) {
  background: color-mix(in srgb, black 40%, transparent); }
.room-wait-call:disabled, .room-wait-noshow-btn:disabled { opacity: .6; cursor: default; }
/* Обратный отсчёт на «Неявке». tabular-nums — цифры одной ширины, иначе кнопка дёргается
   каждую секунду и толкает соседнюю «Позвать». Пустой (отсчёт кончился) — не занимает места. */
.room-wait-noshow-time { margin-left: 6px; font-variant-numeric: tabular-nums; }
.room-wait-noshow-time:empty { display: none; }
/* Никто не ждёт (пилюля скрыта) — «Позвонить» одна занимает всю строку, как раньше */
.room-wait.hidden ~ .room-call { flex: 1 1 auto; }
/* Партнёра в комнате нет — звонить некому: кнопку убираем, плашка занимает строку целиком.
   Прячем ТОЛЬКО .idle: входящий звонок (.ringing) и активный (.active) скрывать нельзя —
   position:fixed внутри display:none не рендерится, и на мобиле пропал бы весь виджет звонка. */
.room-top-row.waiting .room-call.idle { display: none; }
/* Входящий звонок: плашка статуса встаёт НАД карточкой «звонит…», а не сбоку.
   Сбоку она сжималась в узкий столбик («Ученик в / классе») и выглядела обрезком
   рядом с окном звонка; сверху она читается как подпись к нему — и это честнее по
   смыслу: сначала «кто на связи», потом «что он делает». */
.room-top-row:has(> .room-call.ringing) { flex-direction: column; align-items: stretch; }
.room-top-row:has(> .room-call.ringing) > .room-wait { flex: 0 0 auto; width: auto; }

/* Чат — высота под окно (учёт шапки сверху), лента скроллится внутри, ввод всегда виден */
/* Правая колонка комнаты: строка ожидание+звонок сверху + чат снизу */
.room-right {
  flex: 0 0 306px; position: sticky; top: 76px;   /* чат чуть уже (−10%) — класс шире */
  /* ⚠️ min-width:0 обязателен: у flex-элемента min-width:auto = min-content, а длинное имя
     файла в полосе загрузки (.staged-name, nowrap) раздувало колонку с 306px до 500+ —
     весь чат «расширялся» на время загрузки и оставался широким, пока файл ждёт отправки. */
  min-width: 0;
  display: flex; flex-direction: column; gap: 12px;
  /* низ панели (чат) должен доходить почти до низа окна — раньше формула оставляла
     ~114px пустого места, из-за чего низ чата не совпадал с низом видимой части урока */
  height: calc(100vh - 96px); min-height: 300px; max-height: calc(100vh - 96px);
}
/* Плавающее видео звонка живёт внутри .room-right: слой колонки должен быть выше липкой панели
   «Книга / Урок» (z-index 30), иначе видео заходит под неё. */
@media (min-width: 901px) { body.call-active .room-right { z-index: 35; } }   /* на телефоне у колонки уже 80 */
.room-chat {
  flex: 1 1 auto; min-height: 120px; position: relative;
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid transparent; border-radius: var(--radius); overflow: hidden;
}
.room-chat-head { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--border); flex: 0 0 auto; }
.room-chat-title { display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0; font-size: .95em; }
.room-chat-title b { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.room-chat-role { font-size: .7em; text-transform: uppercase; letter-spacing: .5px; color: var(--muted);
  border: 1px solid var(--border-2); border-radius: 99px; padding: 1px 7px; flex: 0 0 auto; }
.room-chat-name { font-size: .95em; flex: 1 1 auto; }
.room-chat-sel { flex: 1 1 auto; padding: 5px 8px; font-size: .85em; min-width: 0; }
.room-chat-ctrls { display: flex; gap: 2px; margin-left: auto; flex: 0 0 auto; }
.room-chat-collapse { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 1.05em; padding: 2px 6px; border-radius: 6px; }
.room-chat-collapse:hover { color: var(--text); background: var(--surface-2); }
.room-chat-feed { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  padding: 14px; display: flex; flex-direction: column; gap: 8px;
  scrollbar-width: thin; scrollbar-color: var(--border-2) transparent; }
/* Сообщения прижаты к НИЗУ ленты (к полю ввода), как в любом мессенджере: при паре
   сообщений пустота остаётся СВЕРХУ, а не зазором над строкой ввода.
   ⚠️ Именно margin-top:auto, а НЕ justify-content:flex-end — у flex-контейнера с
   overflow-y:auto флекс-энд в WebKit оставляет верх переполненной ленты недоступным для
   скролла (старые сообщения не проматываются), а авто-маржин при переполнении сам
   становится 0 и скроллу не мешает. */
.room-chat-feed > :first-child { margin-top: auto; }
.room-chat-feed::-webkit-scrollbar { width: 7px; }
.room-chat-feed::-webkit-scrollbar-track { background: transparent; }
.room-chat-feed::-webkit-scrollbar-thumb { background: transparent; border-radius: 99px; transition: background .2s; }
.room-chat-feed:hover::-webkit-scrollbar-thumb { background: var(--border-2); }
.room-chat-feed::-webkit-scrollbar-thumb:hover { background: var(--accent-dim); }
.room-msg { display: flex; }
.room-msg.mine { justify-content: flex-end; }
/* Пузырь: шрифт на пункт мельче и сам пузырь компактнее (−7% по ширине и полям) — в узкой
   колонке чата это разница между «тремя словами в строку» и нормальной строкой. */
.room-msg-body {
  max-width: 74%; padding: 6px 10px; border-radius: 11px; font-size: .76em; line-height: 1.42;
  background: var(--surface-2); border: 1px solid transparent; color: var(--text); white-space: pre-wrap; word-break: break-word;
}
.room-msg-body.hit { outline: 2px solid var(--accent); outline-offset: 1px; }
.room-msg.mine .room-msg-body { background: var(--chat-mine); border-color: transparent; color: var(--on-chat-mine); }
.room-msg-who { font-size: .72em; color: var(--muted); margin-bottom: 3px; }
.room-chat-observe { flex: 0 0 auto; padding: 11px 14px; border-top: 1px solid var(--border);
  color: var(--muted); font-size: .8em; text-align: center; }
/* Нижняя панель компактнее: кнопки и поле занимали заметную долю высоты чата,
   а места в колонке мало — каждый сэкономленный пиксель уходит в ленту сообщений. */
.room-chat-input { flex: 0 0 auto; display: flex; gap: 6px; padding: 8px 10px; align-items: center;
  border-top: 1px solid var(--border); }
.room-chat-input .input { flex: 1 1 auto; touch-action: manipulation; padding: 7px 12px; font-size: .84em; }
/* Прогресс-бар загрузки вложения в чате комнаты (использует общие .staged-*) */
.room-chat-staged { flex: 0 0 auto; display: flex; flex-direction: column; gap: 7px;
  margin: 0 14px 8px; padding: 8px 12px; background: var(--surface-2);
  border: 1px solid var(--border-2); border-radius: var(--radius-sm); }
.room-chat-staged.ready { flex-direction: row; align-items: center; gap: 8px; }
.room-chat-empty { color: var(--muted); font-size: .85em; text-align: center; padding: 20px; margin: auto; }

/* «Язычок» — ручка изменения высоты чата (ПК/планшет). На мобиле чат — фикс. дока внизу, ручка не нужна.
   ⚠️ История формы и цвета: плашка во всю ширину (читалась как ещё одна панель, съедала
   ~20px у ленты) → три серых штриха → одна иконка `refresh` синим → СНОВА три штриха,
   сперва синими, теперь опять СЕРЫМИ — так попросил владелец продукта, увидев их синими
   на экране. То есть прежнее «серый не находят глазами» относилось к плашке-полоске
   в старом виде, а не к треугольнику из трёх штрихов: их видно и серыми.
   ⚠️ Три ОТДЕЛЬНЫХ элемента (.room-chat-resize-bar), а не repeating-linear-gradient:
   градиент даёт полосы ОДИНАКОВОЙ длины, а нужен перевёрнутый треугольник — верхний
   штрих самый длинный, нижний самый короткий. Ширину по отдельности градиентом не задать.
   ⚠️ Полоса ниже на ~15% (было padding 3px сверху и снизу, итого 14.5px → 12.5px):
   владелец продукта назвал нижний край карточки «необоснованно толстоватым».
   ⚠️ Отступ НЕ поровну (0 сверху / 4px снизу), в отличие от прежнего решения: штрихи
   подняты на 2px по просьбе владельца продукта — у самого нижнего края они читались
   как случайный мусор на границе карточки. Общая высота блока при этом не изменилась. */
.room-chat-resize { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 2px; padding: 0 0 4px; cursor: ns-resize; touch-action: none;
  background: transparent; }
/* ⚠️ Серый берём из --muted (вторичный текст), а НЕ из --border-2, как в прежней серой
   версии: #2c2c2c на фоне #111 — почти невидимая волосяная линия, и именно её когда-то
   «не находили глазами». Серый по просьбе владельца продукта, но различимый.
   ⚠️ БЕЗ подсветки акцентом на hover/drag: интерактивность передаёт рост ширины,
   цвет её дублировать не должен. */
.room-chat-resize-bar { height: 1.5px; border-radius: 99px; background: var(--muted);
  transition: width .15s; }
.room-chat-resize-bar:nth-child(1) { width: 42px; }
.room-chat-resize-bar:nth-child(2) { width: 26px; }
.room-chat-resize-bar:nth-child(3) { width: 13px; }
.room-chat-resize:hover .room-chat-resize-bar:nth-child(1), .room-chat-resize.dragging .room-chat-resize-bar:nth-child(1) { width: 50px; }
.room-chat-resize:hover .room-chat-resize-bar:nth-child(2), .room-chat-resize.dragging .room-chat-resize-bar:nth-child(2) { width: 32px; }
.room-chat-resize:hover .room-chat-resize-bar:nth-child(3), .room-chat-resize.dragging .room-chat-resize-bar:nth-child(3) { width: 17px; }
.room-chat.full .room-chat-resize { display: none; }
@media (max-width: 900px) { .room-chat-resize { display: none; } }

/* Свёрнутый чат — плавающая кнопка */
.room-chat.collapsed {
  position: fixed; right: 24px; bottom: 24px; z-index: 60;
  flex: 0 0 auto; width: auto; height: auto; min-height: 0;
  background: none; border: none; overflow: visible;
}
.room-chat-fab {
  width: 48px; height: 48px; border-radius: 50%; cursor: pointer;
  background: var(--btn-primary); color: var(--on-btn-primary); border: none; box-shadow: var(--shadow);
  display: grid; place-items: center;
}
.room-chat-fab svg { width: 21px; height: 21px; display: block; }
/* Кнопка кликабельна даже когда родительская дока переведена в pointer-events:none (во время звонка) */
.room-chat.collapsed .room-chat-fab { pointer-events: auto; }

/* Чат на весь экран (любое устройство) */
.room-chat.full {
  position: fixed; inset: 0; z-index: 200;
  flex: none; max-height: none; min-height: 0;   /* без 100vh/100vw — иначе на мобиле инпут уезжает за экран */
  border-radius: 0; border: none;
}
.room-chat.full .room-chat-feed { flex: 1 1 auto; min-height: 0; max-height: none; padding: 18px max(18px, calc((100% - 760px) / 2)); }
.room-chat.full .room-chat-input { padding: 14px max(18px, calc((100% - 760px) / 2)); }
.room-chat.full .room-msg-body { max-width: 70%; }

/* ── Видеозвонок (WebRTC) ─────────────────────────────────────── */
.room-call { background: var(--surface); border: 1px solid transparent; border-radius: var(--radius); overflow: hidden; flex: 0 0 auto; }
/* idle: высота ровно 40px — как у навигатора материала/урока (room-pick/room-step) слева */
.room-call.idle { padding: 0; height: 40px; }
.call-start { width: 100%; height: 100%; padding: 0 20px; }   /* кнопка «Позвонить» во всю ширину и высоту панели */
.call-start[disabled] { opacity: .5; cursor: not-allowed; }

.call-incoming { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 16px 12px; text-align: center; }
.call-incoming-ico { display: grid; place-items: center; color: var(--accent-text); animation: call-ring 1s ease-in-out infinite; transform-origin: 50% 10%; }
.call-incoming-ico svg { display: block; }
.call-incoming-txt { font-size: .95em; }
.call-incoming-btns { display: flex; gap: 8px; }
@keyframes call-ring { 0%,100% { transform: rotate(0); } 25% { transform: rotate(14deg); } 75% { transform: rotate(-14deg); } }

/* В звонке панель сама по себе не рисуется — видео висит круглым плавающим виджетом */
.room-call.active { display: block; background: none; border: none; padding: 0; overflow: visible; }
.call-widget { position: fixed; z-index: 120; display: flex; flex-direction: column; align-items: center; gap: 8px;
  touch-action: none; user-select: none; -webkit-user-select: none;
  /* Виджет — потомок .room-right, у которого во время звонка pointer-events:none (мобиле).
     Возвращаем интерактивность, иначе кнопки отбой/микрофон/камера не нажимаются. */
  pointer-events: auto; }
/* Строки оверлея: первая — что случилось, вторая (если есть) — что будет дальше. Боковой отступ
   в процентах — на мобиле окно круглое, и текст у краёв круга срезался бы. */
.call-overlay { position: absolute; inset: 0; display: flex; flex-direction: column; gap: 3px;
  align-items: center; justify-content: center;
  color: var(--text); font-size: .78em; background: rgba(0,0,0,.45); text-align: center; padding: 8px 14%; }
.call-overlay b { font-weight: 600; }
.call-overlay span { font-size: .88em; opacity: .75; }
/* На мобиле окно — маленький круг: вторая строка («что будет дальше») туда не влезает и
   налезала на таймер, остаётся только первая. */
.call-square.mobile .call-overlay span { display: none; }
/* «Ученик свернул вкладку» — мелкая подпись по центру сверху, под таймером, как бейдж
   экрана (и под ним, если идёт показ экрана). Без затемнения видео: звонок при этом идёт. */
.call-peer-note { position: absolute; left: 50%; top: 30px; transform: translateX(-50%);
  max-width: 90%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-size: .66em; color: #e5e7eb; background: rgba(20,20,20,.72);
  padding: 2px 9px; border-radius: 99px; backdrop-filter: blur(3px); pointer-events: none; }
.call-peer-note.below-badge { top: 52px; }
/* В круге на мобиле в строку не влезает — переносим на две, а не обрезаем многоточием. */
.call-square.mobile .call-peer-note { white-space: normal; text-align: center; width: max-content;
  max-width: 78%; border-radius: 10px; line-height: 1.25; }
/* Браузер запретил автозапуск звука (или воспроизведение сорвалось системным прерыванием).
   Кнопка лежит ПОВЕРХ видео и перекрывает его целиком — в этом её смысл: пока она видна,
   тап приходится по ней, а не по кнопке play, которую браузер рисует на паузе и которая
   уводит урок в нативный плеер с «прямым эфиром». Звонок остаётся тёмным в обеих темах,
   как и весь блок .call-* — цвета здесь литеральные намеренно. */
/* Панель «Звук» в звонке: уровень микрофона, выбор устройства, подавление эха и шума.
   Живёт в обычной модалке платформы, поэтому цвета — токены палитры, а не тёмные литералы
   блока .call-* (сам виджет звонка намеренно тёмный в обеих темах). */
.snd-body { display: flex; flex-direction: column; gap: 14px; }
.snd-row { display: flex; flex-direction: column; gap: 6px; }
.snd-label { font-size: .8em; color: var(--text-dim); }
.snd-bar { height: 10px; border-radius: 6px; background: var(--surface-2);
  border: 1px solid transparent; overflow: hidden; }
/* ⚠️ Ширину ставит JS каждые 100 мс, поэтому переход короткий: длинный превратил бы
   измеритель в плавную волну, по которой не видно, ПРОПАДАЕТ ли голос. */
.snd-bar > i { display: block; height: 100%; width: 0; background: var(--accent);
  transition: width .08s linear; }
.snd-hint { font-size: .72em; color: var(--muted); }
.snd-cap { font-size: .82em; font-weight: 600; }
.snd-note { font-size: .74em; color: var(--muted); margin-top: 3px; line-height: 1.45; }
.snd-pick { width: 100%; }
/* Окно «Показать экран» с галочкой «Передавать звук» — живёт в обычной модалке платформы, цвета —
   токены палитры. Сама галочка — родной чекбокс (accent-color), как в формах воронок. */
.call-screen-ask { display: flex; flex-direction: column; gap: 10px; }
.call-screen-opt { display: flex; align-items: center; gap: 10px; cursor: pointer;
  font-size: .92em; font-weight: 600; color: var(--text); }
.call-screen-opt input { width: 18px; height: 18px; flex: none; margin: 0; accent-color: var(--accent); cursor: pointer; }
.call-screen-ask .form-actions { justify-content: flex-end; margin-top: 8px; }
.call-screen-warn { font-size: .78em; line-height: 1.45; color: var(--warn); }
/* ⚠️ Сегмента из трёх режимов больше НЕТ (был .snd-modes/.snd-mode): на слух они не
   отличались — резало не то, что написано на кнопке (см. AUDIO_MODES в call.js). Осталось одно
   решение «подавление вкл/выкл», и рисует его общий тумблер платформы (.switch-row из form.js).
   Здесь только подпись: в панели звука это заголовок раздела, а не строка настроек. */
.snd-body .switch-row { padding: 2px 0; font-size: .82em; font-weight: 600; color: var(--text); }
.snd-gain-row { display: flex; align-items: center; gap: 10px; margin-top: 6px; }
.snd-gain { flex: 1 1 auto; min-width: 0; }
.snd-gain-val { flex: 0 0 auto; font-size: .78em; color: var(--text-dim);
  font-variant-numeric: tabular-nums; min-width: 34px; text-align: right; }
.call-unblock { position: absolute; inset: 0; z-index: 4; width: 100%; border: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: 10px; gap: 6px; cursor: pointer; font-family: var(--font);
  font-size: .76em; font-weight: 600; color: #fff; background: rgba(0,0,0,.6); backdrop-filter: blur(2px); }
.call-unblock::before { content: '🔊'; font-size: 1.5em; }
/* Таймер длительности звонка — по центру сверху окна, поверх видео */
.call-timer { position: absolute; top: 6px; left: 50%; transform: translateX(-50%); z-index: 3;
  font-size: .72em; font-variant-numeric: tabular-nums; color: #fff; letter-spacing: .3px;
  background: rgba(0,0,0,.5); padding: 2px 8px; border-radius: 99px; backdrop-filter: blur(3px); pointer-events: none; }
.call-ctrls-mini { display: flex; gap: 8px; background: rgba(0,0,0,.55); padding: 6px; border-radius: 99px; backdrop-filter: blur(4px); }
.call-btn { width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--border-2); cursor: pointer;
  background: var(--surface-2); color: var(--text); font-size: .95em; display: grid; place-items: center; }
.call-btn:hover { border-color: var(--accent-border); }
.call-btn.off { background: color-mix(in srgb, var(--danger) 15%, transparent); border-color: color-mix(in srgb, var(--danger) 45%, transparent); }
.call-btn.hang { background: #ef4444; border-color: #ef4444; color: #fff; }
.call-btn.hang:hover { background: #dc2626; }
.call-btn svg { display: block; }
.call-btn.off { color: #fca5a5; }   /* выключенный микрофон/камера — приглушённо-красный контур иконки */
/* Демонстрация экрана активна — акцентная подсветка кнопки (как «включённое» действие) */
.call-btn.active { background: color-mix(in srgb, var(--accent) 22%, transparent);
  border-color: var(--accent-border); color: var(--accent); }

/* Предупреждение «камера/микрофон заблокированы» на панели звонка (idle/ringing) */
.room-call.idle.has-warn { height: auto; display: flex; flex-direction: column; }
.room-call.idle.has-warn .call-start { height: auto; padding: 10px 20px; }
.call-perm-warn { width: 100%; border: none; border-top: 1px solid var(--border);
  background: color-mix(in srgb, var(--danger) 12%, transparent); color: #fca5a5; font-size: .78em; line-height: 1.35;
  padding: 7px 10px; cursor: pointer; text-align: center; }
.call-perm-warn:hover { background: color-mix(in srgb, var(--danger) 22%, transparent); color: #fecaca; }
.call-incoming .call-perm-warn { border-top: none; border-radius: 8px; margin-top: 2px; }

/* Подсказка под виджетом звонка: свои дорожки не идут (доступ заблокирован).
   Карточка с кнопкой «Найти инструкцию» (макет владельца, вместо пилюли-ссылки).
   Тёмная в обеих темах, как весь виджет звонка, — поэтому цвета литеральные. */
.call-media-warn { max-width: 230px; display: flex; flex-direction: column; gap: 9px;
  border: 1px solid color-mix(in srgb, var(--danger) 40%, transparent); border-radius: 14px;
  /* ⚠️ Фон НЕПРОЗРАЧНЫЙ: красный оттенок кладём градиентом поверх сплошного #1c1213. Раньше стояло
     `color-mix(...), rgba(...)` через запятую — цвет допустим только в последнем слое, браузер
     выбрасывал всю строку, и плашка оставалась без фона (текст терялся на видео и учебнике). */
  background: linear-gradient(color-mix(in srgb, var(--danger) 14%, transparent),
    color-mix(in srgb, var(--danger) 14%, transparent)), #161616;
  color: #fecaca; font-size: .74em; line-height: 1.4; padding: 12px 12px 11px; text-align: center;
  box-shadow: 0 4px 16px rgba(0,0,0,.45); }
.call-media-warn-btn { border: 1px solid color-mix(in srgb, var(--danger) 50%, transparent);
  border-radius: 10px; background: transparent; color: #fca5a5; font: inherit; padding: 6px 10px;
  cursor: pointer; transition: background .15s, border-color .15s, color .15s; }
.call-media-warn-btn:hover { color: #fecaca; border-color: color-mix(in srgb, var(--danger) 80%, transparent);
  background: color-mix(in srgb, var(--danger) 14%, transparent); }
/* Собеседник вошёл в звонок без камеры/микрофона (его media_state): почему его не видно и что
   попросить нажать. Подсказка, а не кнопка. Тёмная в обеих темах, как весь виджет звонка, —
   поэтому цвета литеральные; жёлтый = «внимание», а не «беда у вас», как красный .call-media-warn. */
.call-peer-media { max-width: 240px; display: flex; flex-direction: column; gap: 2px;
  border: 1px solid rgba(251,191,36,.5); border-radius: 12px; background: #161616;
  color: #e5e7eb; font-size: .74em; line-height: 1.35; padding: 7px 12px; text-align: center;
  box-shadow: 0 4px 16px rgba(0,0,0,.45); }
.call-peer-media b { color: #fcd34d; font-weight: 600; }
.call-widget.desktop .call-peer-media { max-width: 320px; }

/* Инструкция «как разрешить доступ к камере/микрофону» (модалка) */
.perm-help p { line-height: 1.55; }
/* Списки «браузер / система» (макет владельца): под эту пару нейросеть ищет шаги */
.perm-help-picks { display: flex; flex-direction: column; gap: 8px; margin: 12px 0 2px; }
.perm-help-sel { width: 100%; }
.perm-help-status { min-height: 1.3em; margin-top: 8px; font-size: .85em; color: var(--muted); text-align: center; }
.perm-help-steps { margin: 8px 0 12px; padding-left: 22px; display: flex; flex-direction: column; gap: 8px; line-height: 1.5;
  max-height: 38vh; overflow: auto; }
.perm-help-steps li { padding-left: 4px; }
.perm-help-note { margin-top: 14px; padding: 10px 12px; border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--text-dim); font-size: .9em; }
/* Ссылка «войти в другом браузере» (встроенный браузер Telegram). 16px — мельче поле зумит
   страницу на iPhone при фокусе. */
.perm-help-link { width: 100%; margin: 2px 0; font-size: 16px; }

/* ПК — большой квадратный виджет: видео учителя и ученика одновременно (главное + миниатюра),
   перетаскивается за фон и меняет размер за уголок — как чат (room-chat-resize). */
.call-square { position: relative; overflow: hidden; touch-action: none;
  background: #0a0d0c; border-radius: var(--radius); border: 1px solid var(--border-2);
  box-shadow: var(--shadow); cursor: grab; }
.call-square:active { cursor: grabbing; }
.call-square video { position: absolute; object-fit: cover; background: #0a0d0c; display: block; }

/* ⚠️ Видео звонка — не медиаплеер: у браузера не должно остаться на нём НИ ОДНОЙ своей кнопки.
   Реальная жалоба с урока: «на моём лице высветилась кнопка воспроизведения; нажала — окно
   увеличилось, картинки нет, а в плеере ПРЯМОЙ ЭФИР, пауза и перемотка ±10 секунд». Эту кнопку
   WebKit рисует поверх ПРИОСТАНОВЛЕННОГО видео своим теневым элементом, и `controls = false`
   её НЕ убирает — она к элементу controls отношения не имеет. Тап по ней и есть вход в
   системный плеер, поэтому её надо гасить здесь, а не только чинить паузу в JS.
   ⚠️ КАЖДЫЙ псевдоэлемент — СВОИМ правилом, объединять их в один список нельзя: браузер
   выбрасывает весь селектор целиком, если не понял хотя бы одну его часть, и правило пропало бы
   ровно там, где нужно (у Safari нет chromium-овских имён, у Chromium — наоборот). */
.call-vid::-webkit-media-controls-start-playback-button { display: none !important; -webkit-appearance: none; }
.call-vid::-webkit-media-controls-overlay-play-button { display: none !important; -webkit-appearance: none; }
.call-vid::-webkit-media-controls-play-button { display: none !important; }
.call-vid::-webkit-media-controls-panel { display: none !important; }
.call-vid::-webkit-media-controls-enclosure { display: none !important; }
.call-vid::-webkit-media-controls { display: none !important; }
.call-vid::-internal-media-controls-overlay-cast-button { display: none !important; }
/* Долгое нажатие на телефоне не должно предлагать «Сохранить видео»/«Транслировать». */
.call-vid { -webkit-touch-callout: none; }
.call-square video.call-main { inset: 0; width: 100%; height: 100%; z-index: 1; border-radius: inherit; }
/* Демонстрация экрана: показываем кадр ЦЕЛИКОМ (contain), а не обрезаем под квадрат окна —
   у экрана другое соотношение сторон, и cover срезал бы половину. Перебивает object-fit: cover
   у .call-square video, поэтому селектор с той же спецификой идёт ниже по файлу. */
.call-square video.call-screen-src { object-fit: contain; background: #000; }
/* Бейдж «идёт демонстрация экрана» — по центру сверху окна, под таймером длительности.
   Не снизу: там миниатюра-PiP, кнопка «поменять местами» и грип размера. */
.call-screen-badge { position: absolute; left: 50%; top: 30px; transform: translateX(-50%);
  max-width: 90%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-size: .66em; color: #fff; background: color-mix(in srgb, var(--accent) 78%, #000 22%);
  padding: 2px 9px; border-radius: 99px; backdrop-filter: blur(3px); pointer-events: none; }
/* Миниатюра-PiP — в ЛЕВОМ нижнем углу (перенесена из правого): грип изменения размера живёт в
   правом нижнем, и на маленьком окне миниатюра его перекрывала — окно было не развернуть. */
.call-square video.call-thumb-vid { left: 10px; bottom: 10px; width: 30%; height: 30%;
  min-width: 76px; min-height: 76px; z-index: 3; border-radius: 10px;
  border: 2px solid var(--surface); box-shadow: 0 2px 10px rgba(0,0,0,.45); cursor: pointer; }
/* Кнопка «поменять видео местами» (кто крупно — собеседник или я) — на верхнем углу миниатюры.
   В покое приглушена (opacity .5 — по просьбе владельца: отвлекала от видео), при наведении — полная
   яркость с более тёмным кружком. Только ПК-виджет. */
.call-flip { position: absolute; left: 6px; bottom: 8px;
  bottom: calc(10px + max(30%, 76px) - 11px);
  z-index: 4; width: 22px; height: 22px; border: 0; border-radius: 50%; padding: 0; cursor: pointer;
  display: grid; place-items: center; color: #fff; background: rgba(0,0,0,.5);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  opacity: .5; transition: background .12s, opacity .12s; }
.call-flip:hover { background: rgba(0,0,0,.72); opacity: 1; }
.call-flip svg { display: block; width: 13px; height: 13px; }
/* Мобиле — компактное квадратное окно с миниатюрой-PiP (как FaceTime/WhatsApp) */
.call-square.mobile { width: 150px; height: 150px; border-radius: 18px;
  border: 1.5px solid var(--accent-dim); box-shadow: 0 10px 28px rgba(0,0,0,.55); }
.call-square.mobile video.call-thumb-vid { left: 7px; bottom: 7px; width: 38%; height: 38%;
  min-width: 0; min-height: 0; border-radius: 10px; border-width: 1.5px; }
/* Грип изменения размера — правый нижний угол. Миниатюра сюда не наезжает (ушла в левый угол).
   ⚠️ Рисунок — три ТОНКИХ штриха со скруглёнными концами и без заливки (раньше был тёмный скос с
   жёстким белым треугольником: острые края торчали из скруглённого угла окна). Длины 24/17/8.5 px
   с шагом 5.7 px, самый длинный — дальше всех от угла; грип занимает весь угол с запасом на
   скругление окна (--radius 14px), концы идут по дуге и ни один штрих не упирается в границу.
   (Первый вариант 15/10/5 владелец продукта назвал узким — не уменьшать.) Сам элемент 32×32 — это зона
   захвата, она прозрачная. Контур — маска, цвет — фон ::before: окно звонка тёмное в обеих
   темах (#0a0d0c), так что светлого оверрайда не нужно; лёгкая тень держит штрихи читаемыми,
   когда на кадре светлое видео. */
.call-resize { position: absolute; right: 0; bottom: 0; width: 32px; height: 32px; z-index: 4;
  cursor: nwse-resize; touch-action: none; }
.call-resize::before { content: ''; position: absolute; right: 0; bottom: 0; width: 32px; height: 32px;
  background: rgba(255,255,255,.27);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M6.5 23.5L23.5 6.5M13 25L25 13M20 26L26 20'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M6.5 23.5L23.5 6.5M13 25L25 13M20 26L26 20'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
  filter: drop-shadow(0 0 1px rgba(0,0,0,.55)); transition: background .12s; }
.call-resize:hover::before { background: rgba(255,255,255,.7); }

/* Обе стороны выключили камеру — прятать пустой чёрный квадрат, оставляя только плавающие
   кнопки управления звонком (микрофон/камера/отбой). */
.call-widget.no-video .call-square { display: none; }

/* Круглая «парящая» кнопка сворачивания чата (вместо шторки-заголовка) — «жидкое стекло»:
   полупрозрачная, с размытием и бликом, поэтому текст под ней читается, а сама она не
   выглядит наклейкой поверх ленты.
   ⚠️ right НЕ 8px: полоса прокрутки ленты — 7px у правого края, и кнопка ложилась прямо
   на неё (видно на скриншоте владельца продукта) — тащить ползунок под кнопкой было нельзя.
   ⚠️ Фон — color-mix от --surface, а не литеральный rgba(20,20,20,…): при светлой теме
   тёмная плашка на светлом чате выглядела дырой (см. правило проекта про палитру). */
.room-chat-min { position: absolute; top: 8px; right: 15px; z-index: 5; width: 32px; height: 32px; border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
  background: color-mix(in srgb, var(--surface) 45%, transparent);
  backdrop-filter: blur(10px) saturate(160%); -webkit-backdrop-filter: blur(10px) saturate(160%);
  box-shadow: 0 2px 10px color-mix(in srgb, #000 18%, transparent),
              inset 0 1px 0 color-mix(in srgb, #fff 20%, transparent);
  color: var(--text-dim); cursor: pointer; font-size: 1.15em; line-height: 1;
  display: grid; place-items: center; transition: color .15s, border-color .15s, background .15s; }
.room-chat-min > span { display: block; margin-top: -1px; }   /* «›» рисуется со смещением вверх от базовой линии */
.room-chat-min:hover { color: var(--text); border-color: var(--accent-border);
  background: color-mix(in srgb, var(--surface) 62%, transparent); }
/* Круглая кнопка слева сверху — чат на весь экран */
.room-chat-max { position: absolute; top: 8px; left: 8px; z-index: 5; width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--border-2); background: rgba(20,20,20,.7); color: var(--text-dim); cursor: pointer;
  font-size: 1em; backdrop-filter: blur(4px); }
/* display:grid;place-items:center + display:block на инлайновом svg (частый приём в этом
   файле, см. .room-chat-fab svg) на мобильном Safari/WebView здесь всё равно давал видимый
   сдвиг иконки вправо-вниз — заменили на абсолютное позиционирование самой иконки: top/left
   50% + translate(-50%,-50%) считается от РЕАЛЬНОГО прямоугольника иконки после рендера,
   а не зависит от того, как движок трактует инлайновый заменяемый элемент внутри грида.
   Кнопка уже position:absolute — она и так задаёт containing block для этого translate. */
.room-chat-max svg { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); display: block; }
.room-chat-max:hover { color: var(--text); border-color: var(--accent-border); }
/* На ПК своя ручка изменения высоты (room-chat-resize) — кнопка «на весь экран» там не нужна, только на мобиле/планшете */
@media (min-width: 901px) { .room-chat-max { display: none; } }
.room-chat.full .room-chat-feed { max-height: none; flex: 1 1 auto; }   /* на весь экран лента заполняет, без лимита 150px */
/* Справа сверху висят «Фон» и «свернуть» — селектор ученика не должен уезжать под них
   (иначе имя читается наполовину). Точное значение — рядом с самими кнопками, в блоке
   «Обои чата» ниже по файлу (там же, где считается их ширина). */
.room-chat-selrow { padding: 10px 12px 0; }
.room-chat-feed { padding-top: 10px; }

/* Прикрепление файла + вложения в сообщениях */
/* Скрепка — минималистичная: контурная иконка (ICONS.paperclip) вместо эмодзи 📎.
   Эмодзи рисуется шрифтом ОС (свой на Windows/macOS/Android), всегда цветное и мимо
   палитры школы — то же правило, что для иконок разделов настроек. */
.room-chat-clip { flex: 0 0 auto; width: 32px; height: 32px; display: grid; place-items: center;
  background: var(--surface-2); border: 1px solid var(--border-2);
  border-radius: 10px; color: var(--text-dim); cursor: pointer; transition: color .12s, border-color .12s; }
.room-chat-clip svg { width: 16px; height: 16px; display: block; }
.room-chat-clip:hover { color: var(--text); border-color: var(--accent-border); }
.room-chat-clip:disabled { opacity: .5; cursor: default; }
/* Кнопка отправки — квадратная со скруглением, в размер скрепки (была .btn btn-sm: крупнее
   и с чужими отступами, из-за чего нижняя панель выглядела громоздкой). */
.room-chat-send { flex: 0 0 auto; width: 32px; height: 32px; display: grid; place-items: center;
  border: none; border-radius: 10px; background: var(--btn-primary); color: var(--on-btn-primary);
  cursor: pointer; transition: background .12s; }
.room-chat-send svg { display: block; }
.room-chat-send:hover { background: var(--btn-primary-hover); }
.room-msg-img { max-width: 180px; max-height: 180px; border-radius: 8px; display: block; }
.room-msg-img-link { display: inline-block; }
.room-msg-text + .room-msg-img-link, .room-msg-text + .room-msg-file { margin-top: 6px; }
.room-msg-file { display: inline-flex; align-items: center; gap: 2px; color: var(--accent-text); word-break: break-all; }

@media (max-width: 900px) {
  /* ⚠️ align-items ОБЯЗАН стать stretch вместе со сменой направления: базовое правило
     .room ставит flex-start (на десктопе это «колонки от верха»), но в column-раскладке
     та же строчка означает «ширина по содержимому» — и колонка раздувалась до min-content
     своей начинки. Длинное название урока в навигаторе (nowrap-текст даёт min-content в
     полную длину, обрезка многоточием на min-content не влияет) растягивало .room-book-pane
     до 431px при экране 375 — за край уезжала ВСЯ страница: и селектор материала, и
     содержимое урока, и нижняя дока. С коротким названием min-content помещался, и баг
     «то есть, то нет» выглядел случайным. */
  .room { flex-direction: column; align-items: stretch; }
  /* Модуль «Позвонить + чат» прижат к САМОМУ низу экрана, высота — по контенту (top:auto!) */
  .room-right {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto; z-index: 80;
    flex-direction: column; gap: 8px; padding: 8px;
    background: var(--bg); border-top: 1px solid var(--border);
    height: auto; min-height: 0; max-height: 85vh;
  }
  .room-chat { flex: 0 0 auto; width: 100%; height: auto; min-height: 0; }
  /* окно сообщений — максимум ~3-4 штуки, дальше внутренний скролл */
  .room-chat-feed { flex: 0 0 auto; max-height: 150px; min-height: 56px; }
  .room-chat.collapsed { position: fixed; right: 16px; bottom: 72px; left: auto;
    height: auto; width: auto; padding: 0; background: none; border: none; }
  /* контент урока получает отступ снизу под закреплённый модуль (без лишней пустоты) */
  body.room-chat-open .room-book-pane { padding-bottom: 300px; }
  body:not(.room-chat-open) .room-book-pane { padding-bottom: 88px; }

  /* Во время звонка кнопки «Позвонить» нет — верхняя строка не занимает место,
     чат/кнопка чата приклеиваются к самому низу экрана (а не висят над пустой докой).
     ВАЖНО: НЕ display:none — иначе пропадёт и сам плавающий виджет звонка (position:fixed
     внутри display:none не рендерится: на мобиле исчезали видео И кнопки, оставался только звук).
     Строка и так нулевой высоты (виджет — fixed, пилюля «Ожидание» скрыта), поэтому просто
     схлопываем без удаления поддерева. */
  body.call-active .room-top-row { height: 0; padding: 0; overflow: visible; }
  body.call-active .room-chat.collapsed { bottom: 14px; }
  body.call-active:not(.room-chat-open) .room-right {
    background: transparent; border-top: none; padding: 0; pointer-events: none; }
}

/* ── Компактнее на мобиле: упражнения, чат-инпут, кнопка звонка ── */
@media (max-width: 760px) {
  /* В комнате контент урока шире — по ширине нижнего модуля (поля 8px, как у доки) */
  body.room-view .app { padding-left: 8px; padding-right: 8px; }
  /* упражнения — меньше текста и «воздуха» */
  .room-ex { padding: 12px; }
  /* плеер: тонкая рамка, подстраивается под содержимое (не тянется пустотой под картинкой) */
  .slide-view { padding: 10px; min-height: 0; }
  .slide-view .ex-render .ex-img { max-height: 70vh; }
  .room-ex .ex-h, .room-ex .ex-render .ex-h { font-size: 1.05em; margin-bottom: 8px; }
  .room-ex .body-text { font-size: .9em; line-height: 1.55; }
  .room-ex .fill-line { font-size: .95em; line-height: 2.1; }
  /* margin — через (0,3,0): общий блок «Тест» ниже по файлу иначе перебил бы его. */
  .room-ex .ex-choice .choice-q { font-size: .95em; margin: 24px 0 10px; }
  /* поля ввода и кнопки в упражнениях — ниже, без лишних отступов */
  .choice-opt { padding: 9px 12px; margin-bottom: 7px; font-size: .9em; }
  .room-ex .input { padding: 7px 12px; }
  .fill-input { padding: 2px 6px; }
  .dnd-word, .wb-chip { padding: 5px 10px; }
  /* строка ввода чата — ниже (цель: закреплённый снизу модуль занимает меньше высоты над
     уроком в классе). Меньше вертикальный padding у строки И у самого поля. */
  .room-chat-input { padding: 4px 8px; gap: 6px; }
  .room-chat-input .input { padding: 5px 12px; }
  /* Скрепка и «отправить» — крупнее, чем на ПК: 32px под палец мало (в проекте минимум
     тач-таргета — 42px у кнопок, здесь идём на 36px как компромисс с экономией высоты). */
  .room-chat-clip, .room-chat-send { width: 36px; height: 36px; }
  /* Плашка «ждём…» — на мобиле не обязана совпадать по высоте с навигатором урока (40px из
     базового правила .room-wait — это для десктопа, там она стоит рядом с ним); здесь ниже.
     По высоте выравнивается на кнопку «Позвонить» — align-items:stretch ниже (не хардкодим
     число: подстроится сама, если у кнопки ещё раз поменяется padding). */
  /* ⚠️ Раскладка «текст слева, кнопка справа» переехала в БАЗОВОЕ правило .room-wait —
     теперь она одинакова на всех экранах (раньше строкой плашка была только на мобиле,
     а на ПК оставалась колонкой). Здесь остаётся только то, что специфично для узкого
     экрана: плашка ниже (совпадать по высоте с навигатором урока тут не с чем). */
  .room-wait { min-height: 0; padding: 5px 8px 5px 12px; }
  .room-top-row { align-items: stretch; }
  /* Кнопка «Позвонить» — во ВСЮ ширину строки, ровно по краям чата под ней. Раньше здесь
     стояло width:90%;margin:0 auto — кнопка была заметно уже соседних блоков и висела
     «не по сетке». Теперь в строке всегда ровно один элемент (плашка ИЛИ кнопка, см.
     .room-top-row.waiting), поэтому сужать нечего — оба тянутся на всю ширину. */
  .room-call.idle { padding: 0; height: auto; }
  .room-call.idle .call-start { height: auto; padding: 6px 16px; }
  /* Панель звонка не шире экрана (длинное предупреждение о камере растягивало её за края) */
  .room-call { max-width: 100%; min-width: 0; }
  /* «Войти в класс» — во всю ширину экрана */
  .cd-enter { width: 100%; justify-content: center; }
  /* Верхняя панель учебника: карточка «Книга/Урок» на всю ширину, а НИЖЕ одной строкой —
     домик слева и инструменты справа. ⚠️ На телефоне у домика и инструментов нет ни рамок,
     ни заливки, ни подписей (правка владельца продукта): в ширину экрана четыре кнопки с
     текстом не помещаются, а панель с заголовком «Инструменты» съедала бы целый экран
     над самим уроком. Смысл несут иконки (тач-таргет 30px — компромисс ради высоты, см. ниже). */
  .room-nav-card { flex: 1 1 100%; padding: 6px 14px; }
  .room-hw-btn {
    flex: 0 0 auto; width: 30px; height: 30px; padding: 0;
    background: none; border: none; color: var(--text-dim);
  }
  /* ⚠️ Ряд домик + инструменты на телефоне ниже на 35% (46/44px → 30px) (правка владельца продукта):
     закреплённая панель занимала слишком много высоты над уроком. */
  .room-hw-btn .ico svg { width: 20px; height: 20px; }
  .room-tools { flex: 0 0 auto; margin-left: auto; padding: 0; gap: 0;
    background: none; border: none; justify-content: center; }
  .room-tools-row { gap: 2px; }
  .room-toolbtn { flex: 0 0 auto; width: 30px; height: 30px; padding: 0; gap: 0;
    background: none; border: none; color: var(--text-dim); }
  .room-toolbtn .ico svg { width: 17px; height: 17px; }
  .room-toolbtn-lbl { display: none; }
  /* ⚠️ Закреплённая панель на телефоне (правка владельца продукта): карточка «Книга/Урок»
     уезжает вверх за край (сдвиг на её высоту — `--nav-shift` ставит room.js), остаётся
     только ряд инструментов на ЧЁРНОЙ подложке — чёрной и в светлой теме, поэтому цвета
     здесь литеральные и намеренно без оверрайда. transform, а не display:none: панель
     sticky, и смена её высоты в потоке дёргала бы урок под пальцем. Переход — transition
     (его «Уменьшение движения» iOS не гасит, гасит только animation). */
  .room-book-head { transition: transform .3s ease; }
  .room-book-head .room-nav-card { transition: opacity .3s ease; }
  .room-book-head.stuck { transform: translateY(calc(-1 * var(--nav-shift, 0px))); }
  .room-book-head.stuck .room-nav-card { opacity: 0; pointer-events: none; }
  /* Переход в чёрный — медленный и мягкий (.6s, правка владельца продукта: за .22s
     панель «щёлкала» в чёрный). Сдвиг карточки остаётся быстрее — он про место, а не про цвет. */

  .room-book-head .room-hw-btn, .room-book-head .room-toolbtn { transition: color .6s cubic-bezier(.45, 0, .55, 1); }
  .room-book-head.stuck .room-hw-btn, .room-book-head.stuck .room-toolbtn { color: rgba(236, 236, 236, .85); }
  /* инструменты упражнения — только иконки (минимализм), квадратные кнопки.
     ⚠️ min-height ставим ЯВНО: .room-tool наследует класс .btn, а у него в этом же
     media-блоке своё правило «тач-таргеты — не мельче 42px» (см. .btn{min-height:42px}
     выше). height:34px его не отменяет — min-height всегда побеждает меньший height,
     поэтому кнопка держалась 34px в ширину и 42px в высоту (прямоугольник, не квадрат). */
  .room-tool .rt-label { display: none; }
  .room-tool { width: 30px; height: 30px; min-height: 30px; padding: 0; gap: 0; justify-content: center; border-radius: 8px; }
  .room-tool .rt-ico svg { width: 15px; height: 15px; }
}

/* — Обзор курса (ProgressMe-стиль) — */
.cv-header { display: flex; gap: 22px; align-items: flex-start; margin-bottom: 24px; flex-wrap: wrap; }
.cv-cover {
  width: 150px; aspect-ratio: 3 / 4; flex: 0 0 auto; border-radius: 12px;
  background-size: cover; background-position: center; box-shadow: var(--shadow);
  display: flex; align-items: center; justify-content: center;
}
.cv-meta { flex: 1 1 240px; min-width: 0; display: flex; flex-direction: column; gap: 12px; }
.cv-title { font-size: 1.8em; font-weight: 300; letter-spacing: .3px; display: flex; align-items: center; gap: 10px; }
.icon-btn.cv-gear { width: 32px; height: 32px; font-size: 18px; }
.cv-desc { font-size: .92em; line-height: 1.6; color: var(--text-dim); white-space: pre-wrap; max-width: 640px; }
.cv-desc b { color: var(--text); font-weight: 500; }

/* align-items: stretch — кнопка «+ Урок» тянется в высоту наравне с полем поиска
   (у обычной .btn и .input вертикальные паддинги совпадают, stretch убирает
   расхождение в 1–2px от line-height). */
.cv-toolbar { display: flex; gap: 12px; align-items: stretch; margin-bottom: 18px; flex-wrap: wrap; }
.cv-toolbar .cv-search { flex: 1 1 240px; }

.cv-modules { display: flex; flex-direction: column; gap: 14px; }
.cv-module { border: 1px solid transparent; border-radius: var(--radius-sm); overflow: hidden; background: var(--surface); }
.cv-mod-head {
  width: 100%; display: flex; align-items: center; gap: 12px; padding: 14px 16px; cursor: pointer;
  background: var(--surface-2); border: none; color: var(--text); text-align: left; font-size: 1.05em;
}
.cv-mod-head:hover { background: var(--surface); }
.cv-mod-num { width: 30px; height: 30px; flex: 0 0 auto; border-radius: 8px; background: var(--bg);
  display: flex; align-items: center; justify-content: center; font-size: .85em; color: var(--accent-text); }
.cv-mod-title { flex: 1 1 auto; }
.cv-mod-toggle { color: var(--muted); }
.cv-mod-thumb { width: 28px; height: 28px; flex: 0 0 auto; border-radius: 6px; background-size: cover; background-position: center; }
.cv-mod-head .mod-grip { padding: 0 2px; }
.cv-module.dnd-dragging { opacity: .5; outline: 1px dashed var(--accent-dim); }

.cv-lessons { display: flex; flex-direction: column; }
.cv-lesson {
  display: flex; align-items: center; gap: 12px; padding: 12px 16px; cursor: pointer;
  border-top: 1px solid var(--border); transition: background .15s;
}
.cv-modules > .cv-lessons > .cv-lesson:first-child { border-top: none; }
.cv-lesson:hover { background: var(--surface-2); }
.cv-code {
  width: 38px; height: 38px; flex: 0 0 auto; border-radius: 9px;
  background: var(--surface-2); border: 1px solid var(--border-2);
  display: flex; align-items: center; justify-content: center; font-size: .82em; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.cv-code-img { background-size: cover; background-position: center; }
.cv-lesson-main { flex: 1 1 auto; min-width: 0; }
.cv-lesson-title { color: var(--text); font-size: .96em; }
.cv-lesson-sub { font-size: .76em; color: var(--muted); margin-top: 2px; }
.cv-open { flex: 0 0 auto; }

/* Теги урока в строке (справа) + инфо-иконка с описанием */
.cv-lesson-tags { flex: 0 1 auto; display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 2px 8px; max-width: 260px; }
.cv-tag { font-size: .74em; color: var(--text-dim); white-space: nowrap; text-transform: uppercase; letter-spacing: .3px; }
.cv-lesson-info { flex: 0 0 auto; position: relative; display: inline-flex; align-items: center; cursor: help; color: var(--muted); }
.cv-lesson-info:hover { color: var(--text); }
.cv-info-ico { display: inline-flex; align-items: center; }
/* position:fixed + добавляется в document.body по hover (см. infoTooltipIcon в course.js) —
   иначе обрезается overflow:hidden у .cv-module (нужен для скругления углов модуля). */
.cv-info-pop {
  position: fixed; z-index: 200; width: 280px; max-width: 60vw;
  padding: 10px 12px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--border-2); box-shadow: var(--shadow);
  color: var(--text-dim); font-size: .82em; line-height: 1.5; white-space: pre-wrap; text-align: left;
  pointer-events: none;
}
@media (max-width: 600px) { .cv-open, .cv-lesson-tags { display: none; } }

/* — Страница урока — */
.lesson-main { min-width: 0; }
.lesson-exercises { display: flex; flex-direction: column; gap: 12px; }
/* Правая колонка (Предпросмотр + Структура урока у учителя, Содержание урока у ученика) —
   ПРИКЛЕЕНА при прокрутке (top как у .settings-nav/.room-right — высота шапки + отступ),
   иначе при длинном списке упражнений она уезжала наверх экрана и оставалась недоступна.
   Своя max-height + overflow-y: длинную «Структуру урока» иначе не долистать — sticky
   сам по себе не даёт внутренней прокрутки, если содержимое выше вьюпорта. */
.lesson-side { min-width: 0; position: sticky; top: 78px; max-height: calc(100vh - 98px); overflow-y: auto; }
.toc-list { display: flex; flex-direction: column; gap: 4px; }
.toc-item {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; cursor: pointer; text-align: left;
  background: none; border: 1px solid transparent; border-radius: 8px; color: var(--text-dim); font-size: .85em; width: 100%;
}
.toc-item:hover { background: var(--surface-2); color: var(--text); border-color: var(--border); }
.toc-num { width: 22px; height: 22px; flex: 0 0 auto; border-radius: 50%; background: var(--surface-2);
  display: flex; align-items: center; justify-content: center; font-size: .78em; color: var(--muted); }
.toc-title { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.toc-flash { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 50%, transparent); transition: box-shadow .2s; border-radius: var(--radius); }
.toc-item.active { background: var(--surface-2); color: var(--selected); border-color: var(--accent-border); }

/* — Конструктор урока: встроенные редакторы упражнений (стопкой, слева) — */
.ex-edit-stack { display: flex; flex-direction: column; gap: 12px; }
/* Кнопка вставки упражнения МЕЖДУ карточками (lesson.js insertExBtn): тонкая пунктирная
   полоса — место вставки видно сразу, но она не спорит с карточками за внимание.
   Наведение — цветом акцента; тап-зона не меньше 30px, чтобы попасть пальцем. */
.ex-add-between {
  width: 100%; padding: 6px 12px;
  border: 1px dashed var(--border); border-radius: 10px;
  background: transparent; color: var(--muted);
  font: inherit; font-size: .85em; line-height: 1.3; min-height: 30px;
  cursor: pointer; text-align: center;
  transition: color .15s, border-color .15s;
}
.ex-add-between:hover { color: var(--accent); border-color: var(--accent); }
.ex-inline-card { background: var(--surface); border: 1px solid transparent; border-radius: var(--radius); padding: 16px 18px; }
.ex-inline-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.ex-inline-title { flex: 1 1 auto; min-width: 0; font-size: 1.02em; color: var(--text); }
/* Ручка «шесть точек» — за неё карточку переставляют прямо в ленте (enableLiftReorder).
   touch-action: none — иначе палец на ручке прокручивает страницу вместо перетаскивания. */
.ex-drag-grip {
  flex: 0 0 auto; width: 28px; height: 28px; margin: -2px -4px -2px -8px;
  display: inline-flex; align-items: center; justify-content: center; border-radius: 8px;
  color: var(--text-dim); cursor: grab; touch-action: none; user-select: none;
  -webkit-user-select: none; -webkit-touch-callout: none; transition: color .15s, background .15s;
}
.ex-drag-grip:hover { color: var(--text); background: var(--surface-2); }
.ex-drag-grip > svg { width: 18px; height: 18px; }
/* На время перетаскивания карточки свёрнуты до шапок, а вместо кнопок — суть упражнения. */
.ex-inline-peek { display: none; }
.ex-edit-stack.lift-sorting .ex-editor,
.ex-edit-stack.lift-sorting .ex-gen-btn,
.ex-edit-stack.lift-sorting .ex-del-btn { display: none; }
/* Кнопки вставки на время перетаскивания тоже скрыты: свёрнутая лента — только шапки
   карточек, полосы между ними создавали бы лишние зазоры и ехали бы вместе с карточками. */
.ex-edit-stack.lift-sorting .ex-add-between { display: none; }
.ex-edit-stack.lift-sorting .ex-inline-head { margin-bottom: 0; }
.ex-edit-stack.lift-sorting .ex-inline-title { flex: 0 1 auto; max-width: 50%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ex-edit-stack.lift-sorting .ex-inline-peek {
  display: block; flex: 1 1 auto; min-width: 0; font-size: .9em; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* — Кнопка «Сгенерировать» в шапке упражнения (нейросеть заполняет одно упражнение) —
   Намеренно спокойная: она стоит в КАЖДОЙ карточке конструктора, и залитая акцентом
   кнопка в десяти карточках подряд перетягивала бы внимание с самих упражнений. */
.ex-gen-btn { flex: 0 0 auto; gap: 6px; color: var(--text-dim); }
.ex-gen-btn:hover:not(:disabled) { color: var(--accent-text); }
.ex-gen-ico { display: inline-flex; flex: 0 0 auto; }
.ex-gen-ico > svg { width: 15px; height: 15px; }

/* На телефоне подписи «Сгенерировать»/«Удалить» рядом с заголовком упражнения не
   помещаются и разрывают его на строки — оставляем только иконки (подпись остаётся
   в title как тултип/для скринридера). */
@media (max-width: 760px) {
  .ex-gen-label, .ex-del-label { display: none; }
  .ex-inline-head .ex-gen-btn, .ex-inline-head .ex-del-btn { padding: 7px 9px; }
}

/* — Поп-ап «Сгенерировать упражнение» — */
.exgen-row { display: flex; gap: 12px; flex-wrap: wrap; }
.exgen-row > .field { flex: 1 1 140px; min-width: 0; }
.exgen-brief { min-height: 76px; }
.exgen-warn { margin: 2px 0 0; font-size: .8em; color: var(--warn); line-height: 1.4; }
.exgen-quota { margin: 10px 0 0; font-size: .8em; color: var(--text-dim); }
.exgen-quota-out { color: var(--danger); }

/* — Предпросмотр урока (правая колонка конструктора): В классе / Марафон — */
.preview-btns { display: flex; gap: 8px; }
.preview-btns .btn { flex: 1 1 0; justify-content: center; }

/* — Структура урока (правая колонка конструктора): выбор/перестановка/удаление — */
.struct-list { display: flex; flex-direction: column; gap: 4px; }
.struct-item {
  display: flex; align-items: center; gap: 8px; padding: 8px 10px; cursor: pointer;
  background: none; border: 1px solid transparent; border-radius: 8px; color: var(--text-dim); font-size: .85em;
}
.struct-item:hover { background: var(--surface-2); color: var(--text); border-color: var(--border); }
.struct-item.active { background: var(--surface-2); color: var(--text); border-color: var(--accent-border); }
.struct-item .toc-title { flex: 1 1 auto; }
.struct-del {
  flex: 0 0 auto; background: none; border: none; color: var(--muted); cursor: pointer; font-size: .9em;
  padding: 2px 4px; border-radius: 6px; opacity: 0; transition: opacity .15s, color .15s;
}
.struct-item:hover .struct-del { opacity: 1; }
.struct-del:hover { color: var(--danger); }

/* — Каталог курсов с обложками — */
.course-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 20px; }
.course-card {
  background: var(--surface); border: 1px solid transparent; border-radius: var(--radius);
  overflow: hidden; transition: border-color .18s, transform .18s, background .18s;
  display: flex; flex-direction: column;
}
.course-card.clickable { cursor: pointer; }
.course-card.clickable:hover { border-color: var(--accent-border); transform: translateY(-3px); background: var(--surface-2); }
.course-card .cc-cover {
  aspect-ratio: 3 / 4; background-size: cover; background-position: center;
  display: flex; align-items: center; justify-content: center; position: relative;
}
.course-card .cc-cover-letter { font-size: 3.4em; font-weight: 300; color: rgba(255,255,255,.85); }
.course-card .cc-body { padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 10px; }
.course-card .cc-title { font-size: 1.02em; color: var(--text); line-height: 1.3; }
.cc-badges { display: flex; flex-wrap: wrap; gap: 6px; }   /* карточки материалов И досок */
.cc-badge {
  font-size: .72em; padding: 3px 8px; border-radius: 99px;
  border: 1px solid var(--border-2); color: var(--text-dim); white-space: nowrap;
}
.cc-badge.pub { border-color: color-mix(in srgb, var(--badge-accent) 40%, transparent); color: var(--badge-accent); background: color-mix(in srgb, var(--badge-accent) 6%, transparent); }
.cc-badge.priv { border-color: var(--border-2); color: var(--muted); }
.course-card .cc-meta {
  display: flex; gap: 14px; font-size: .76em; color: var(--muted);
  border-top: 1px solid var(--border); padding-top: 12px;
}
.course-card .cc-meta b { color: var(--accent-text); font-weight: 600; }

/* — Загрузка обложки (модал) — */
.cover-pick {
  width: 78px; height: 78px; flex: 0 0 auto; border-radius: 10px;
  border: 1px dashed var(--border-2); background: var(--surface-2);
  background-size: cover; background-position: center;
  display: flex; align-items: center; justify-content: center;
}
.cover-pick-hint { font-size: .68em; color: var(--muted); }
/* Виджет картинки урока/модуля: квадрат + стопка кнопок «Загрузить»/«Удалить» */
.img-field { display: flex; gap: 12px; align-items: center; }
.img-field-btns { display: flex; flex-direction: column; gap: 8px; align-items: stretch; }
.img-field-btns .btn { justify-content: center; }

/* — Сегментированный переключатель (видимость) — */
.seg { display: inline-flex; gap: 0; border: 1px solid var(--border-2); border-radius: var(--radius-sm); overflow: hidden; flex-wrap: wrap; }
.seg-btn {
  padding: 8px 14px; font-size: .82em; cursor: pointer;
  background: transparent; color: var(--text-dim); border: none; border-right: 1px solid var(--border);
  transition: background .15s, color .15s;
}
.seg-btn:last-child { border-right: none; }
.seg-btn:hover { color: var(--text); background: var(--surface-2); }
.seg-btn.active { background: color-mix(in srgb, var(--selected) 10%, transparent); color: var(--selected); }

/* — Инлайн-тумблер с подписью — */
.inline-toggle { display: flex; align-items: center; gap: 10px; cursor: pointer; font-size: .9em; color: var(--text-dim); }

/* — Теги — */
.tags-box { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.tag-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 6px 4px 12px; border-radius: 99px; font-size: .8em;
  background: var(--surface-2); border: 1px solid var(--border-2); color: var(--text-dim);
}
/* Без крестика (тег только для чтения — в шапке материала) — паддинг симметричный,
   иначе текст «прижат» влево местом, зарезервированным под кнопку удаления. */
.tag-chip:not(:has(.tag-x)) { padding: 4px 12px; }
.tag-x { border: none; background: none; color: var(--muted); cursor: pointer; font-size: .9em; line-height: 1; }
.tag-x:hover { color: var(--danger); }

/* — Редактор курса: левая колонка (раздел) — */
.sp-head { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin-bottom: 18px; flex-wrap: wrap; }
.sp-title { font-size: 1.2em; color: var(--text); display: inline-flex; align-items: center; gap: 4px; }
.ex-num {
  width: 24px; height: 24px; flex: 0 0 auto; border-radius: 50%;
  background: var(--surface-2); color: var(--muted); font-size: .78em;
  display: flex; align-items: center; justify-content: center; font-variant-numeric: tabular-nums;
}
.ex-ico { margin-right: 7px; }

/* — Редактор курса: правая колонка (разделы) — */
.course-side { min-width: 0; }
.sec-list { display: flex; flex-direction: column; gap: 6px; }
.sec-item {
  display: flex; align-items: center; gap: 6px;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: var(--surface); padding: 4px 6px 4px 4px; transition: border-color .15s, background .15s;
}
.sec-item.active { border-color: var(--accent-border); background: var(--surface-2); }
.sec-name {
  flex: 1 1 auto; min-width: 0; text-align: left; cursor: pointer;
  background: none; border: none; color: var(--text-dim); font-size: .9em;
  padding: 6px 8px; display: inline-flex; align-items: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sec-item.active .sec-name { color: var(--text); }
.sec-name:hover { color: var(--text); }
.sec-grip { color: var(--muted); }
.sec-ctrls { display: flex; gap: 2px; flex: 0 0 auto; }
.icon-btn {
  width: 24px; height: 24px; border-radius: 6px; cursor: pointer;
  background: transparent; border: 1px solid transparent; color: var(--muted);
  font-size: .8em; display: flex; align-items: center; justify-content: center; transition: all .15s;
}
.icon-btn:hover { color: var(--text); border-color: var(--border-2); background: var(--surface-2); }
.icon-btn.danger:hover { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, transparent); }

/* — Пикер «Добавить упражнение» (views/exercise-picker.js) —
   Слева вкладки категорий и «Избранное» внизу, сверху таблетка «Из фото/PDF» и поиск,
   справа плитки в два ряда. Высота окна постоянная: от смены вкладки оно не прыгает,
   прокручиваются только плитки. 690px — чтобы самая длинная вкладка («Упражнения»,
   7 плиток = 4 ряда) помещалась без прокрутки с запасом на лишнюю строку описания.
   Глубина — заливкой без рамок: окно --surface, плитка темнее, поиск темнее всех. */
.modal:has(> .xp) { max-width: 660px; padding: 20px; }
.xp {
  display: grid; grid-template-columns: 150px minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  grid-template-areas: "convert search" "side main";
  gap: 14px 16px; height: min(690px, calc(100vh - 88px));
}
.xp.no-convert { grid-template-areas: "side search" "side main"; }

.xp-convert {
  grid-area: convert; display: flex; align-items: center; justify-content: center; gap: 8px;
  height: 40px; padding: 0 10px; border: none; border-radius: 99px; cursor: pointer;
  background: var(--bg); color: var(--text); font: inherit; font-size: .84em;
  transition: background .15s, color .15s;
}
.xp-convert:hover { background: var(--surface-2); }
.xp-convert-ico { display: flex; color: var(--accent); }
.xp-convert-ico svg { width: 16px; height: 16px; }

.xp-search { grid-area: search; position: relative; display: block; min-width: 0; }
.xp-search-ico {
  position: absolute; left: 15px; top: 50%; transform: translateY(-50%);
  display: flex; color: var(--muted); pointer-events: none;
}
.xp-search-ico svg { width: 16px; height: 16px; }
/* 16px — меньше iOS зумит страницу при фокусе (см. память «Ловушки касания»). */
.xp-search-input {
  width: 100%; height: 40px; padding: 0 16px 0 40px; border: none; border-radius: 99px;
  background: var(--bg); color: var(--text); font: inherit; font-size: 16px; outline: none;
  -webkit-appearance: none; appearance: none;
}
.xp-search-input::placeholder { color: var(--muted); }
.xp-search-input:focus-visible { box-shadow: 0 0 0 1px var(--accent-border); }
.xp-search-input::-webkit-search-cancel-button { cursor: pointer; }

.xp-side { grid-area: side; display: flex; flex-direction: column; gap: 4px; min-height: 0; }
.xp-tab {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 11px 14px; border: none; border-radius: var(--radius-sm); cursor: pointer;
  background: transparent; color: var(--text-dim); font: inherit; font-size: .9em;
  text-align: left; transition: background .15s, color .15s;
}
.xp-tab:hover:not(:disabled) { background: var(--surface-2); color: var(--text); }
.xp-tab-ico { display: flex; flex: 0 0 auto; color: var(--muted); transition: color .15s; }
.xp-tab-ico svg { width: 18px; height: 18px; }
.xp-tab-label { flex: 1 1 auto; min-width: 0; }
.xp-tab.active { background: var(--bg); color: var(--accent); }
.xp-tab.active .xp-tab-ico { color: var(--accent); }
.xp-tab.soon { cursor: default; opacity: .5; }
/* «Избранное» прижато книзу колонки — отдельно от категорий, как на макете владельца. */
.xp-tab-fav { margin-top: auto; }
.xp-soon {
  flex: 0 0 auto; font-size: .68em; letter-spacing: .3px; color: var(--muted);
  padding: 2px 7px; border-radius: 99px; background: var(--surface-2);
}

.xp-main { grid-area: main; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  margin-right: -6px; padding-right: 6px; }
.xp-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px;
  align-content: start; }

.xp-tile { position: relative; border-radius: var(--radius);
  background: color-mix(in srgb, var(--bg) 55%, var(--surface));
  transition: background .15s, transform .15s; }
.xp-tile:hover:not(.disabled) { background: var(--surface-2); transform: translateY(-2px); }
.xp-tile.disabled { opacity: .45; }
.xp-tile-main {
  display: grid; grid-template-areas: "title" "ico" "desc"; justify-items: start; gap: 10px;
  width: 100%; height: 100%; padding: 16px 16px 15px; border: none; border-radius: inherit;
  background: none; color: var(--text); font: inherit; text-align: left; cursor: pointer;
}
.xp-tile-main:disabled { cursor: not-allowed; }
.xp-tile-main:focus-visible { outline: 2px solid var(--accent-border); outline-offset: -2px; }
.xp-tile-title { grid-area: title; font-size: .95em; font-weight: 500; line-height: 1.25;
  padding-right: 26px; }
.xp-tile-ico {
  grid-area: ico; display: flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 10px; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 13%, transparent);
}
.xp-tile-ico svg { width: 20px; height: 20px; }
.xp-tile-desc { grid-area: desc; font-size: .78em; line-height: 1.4; color: var(--muted); }
.xp-tile-main .xp-soon { position: absolute; right: 12px; bottom: 12px; }

/* Звезда: контур, пока не в избранном; залитая — в избранном. */
.xp-star {
  position: absolute; top: 9px; right: 9px; width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  padding: 0; border: none; border-radius: 50%; cursor: pointer;
  background: none; color: var(--muted); opacity: .6; transition: color .15s, opacity .15s;
}
.xp-star svg { width: 18px; height: 18px; }
.xp-tile:hover .xp-star, .xp-star:focus-visible { opacity: 1; }
.xp-star:hover { color: var(--text); }
.xp-star.on { color: var(--text); opacity: 1; }
.xp-star.on svg { fill: currentColor; }

.xp-empty {
  grid-column: 1 / -1; display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 56px 24px; text-align: center; font-size: .86em; line-height: 1.45;
  color: var(--muted);
}
.xp-empty-ico { display: flex; color: var(--muted); opacity: .7; margin-bottom: 4px; }
.xp-empty-ico svg { width: 30px; height: 30px; }
.xp-empty-title { color: var(--text); font-size: 1.05em; }

/* Телефон (лист снизу): поиск и таблетка в ряд, вкладки — лентой с прокруткой вбок,
   плитки в один столбец строкой «иконка — текст»: на 375px два столбца рвали названия. */
@media (max-width: 760px) {
  .modal:has(> .xp) { max-width: none; padding: 12px 14px calc(16px + env(safe-area-inset-bottom)); }
  .xp {
    grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: auto auto minmax(0, 1fr);
    grid-template-areas: "search convert" "side side" "main main";
    gap: 10px; height: calc(100dvh - 120px);
  }
  .xp.no-convert { grid-template-areas: "search search" "side side" "main main"; }
  .xp-side { flex-direction: row; overflow-x: auto; scrollbar-width: none;
    margin: 0 -14px; padding: 0 14px; }
  .xp-side::-webkit-scrollbar { display: none; }
  .xp-tab { width: auto; flex: 0 0 auto; padding: 8px 12px; gap: 7px; font-size: .86em;
    background: var(--surface-2); border-radius: 99px; white-space: nowrap; }
  .xp-tab.active { background: var(--bg); }
  .xp-tab-fav { margin-top: 0; }
  .xp-tab-ico svg { width: 16px; height: 16px; }
  .xp-grid { grid-template-columns: minmax(0, 1fr); gap: 8px; }
  .xp-tile:hover:not(.disabled) { transform: none; }
  .xp-tile-main { grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas: "ico title" "ico desc"; column-gap: 14px; row-gap: 3px;
    align-items: center; padding: 13px 48px 13px 14px; }
  .xp-tile-title { align-self: end; padding-right: 0; }
  .xp-tile-desc { align-self: start; }
  .xp-star { top: 50%; transform: translateY(-50%); right: 8px; opacity: 1; }
}

/* — Поиск GIF (views/gif-picker.js): строка, сетка-«кирпичи», подпись GIPHY.
   Прокручивается только сетка — строка поиска и обязательная подпись «Powered by GIPHY»
   остаются на виду. */
.gif-search { position: relative; margin-bottom: 12px; }
.gif-search .input { padding-left: 36px; }
.gif-search-ico {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  display: flex; color: var(--muted); pointer-events: none;
}
.gif-search-ico svg { width: 16px; height: 16px; }
.gif-scroll { max-height: min(60vh, 540px); overflow-y: auto; overscroll-behavior: contain; }
.gif-grid { columns: 3; column-gap: 8px; }
.gif-cell {
  display: block; width: 100%; margin: 0 0 8px; padding: 0; border: 0; cursor: pointer;
  border-radius: 8px; overflow: hidden; background: var(--surface-2); break-inside: avoid;
}
.gif-cell img { display: block; width: 100%; height: auto; }
.gif-cell:hover, .gif-cell:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.gif-cell:disabled { opacity: .45; cursor: not-allowed; }
.gif-status { padding: 18px 4px; color: var(--muted); font-size: .85em; line-height: 1.45; text-align: center; }
.gif-status:empty, .gif-more-row:empty { display: none; }
.gif-status .spinner { margin: 0 auto; }
.gif-more-row { display: flex; justify-content: center; padding: 4px 0 8px; }
.gif-attrib { margin-top: 10px; font-size: .72em; letter-spacing: .4px; color: var(--muted); text-align: right; }
@media (max-width: 560px) { .gif-grid { columns: 2; } }

/* ══ Конструктор v2: DnD, упражнения, книга ════════════════════ */

/* — Drag-and-drop — */
.dnd-grip {
  flex: 0 0 auto; cursor: grab; color: var(--muted); font-size: 1em;
  padding: 0 4px; user-select: none; line-height: 1;
}
.dnd-grip:active { cursor: grabbing; }
.dnd-dragging { opacity: .5; outline: 1px dashed var(--accent-dim); }
.sec-item .dnd-grip { padding: 0 2px; }
/* Перестановка карточек в ленте (ui.js → enableLiftReorder): соседи уступают место
   переходом, взятая карточка идёт за указателем без задержки и лежит поверх остальных. */
.lift-sorting > * { transition: translate .2s cubic-bezier(.2,.7,.3,1); }
.lift-sorting > .lift-card, .lift-settling > * { transition: none; }
.lift-card { position: relative; z-index: 5; box-shadow: var(--shadow); border-color: var(--accent-border) !important; }
body.lift-dragging, body.lift-dragging * { cursor: grabbing !important; user-select: none; -webkit-user-select: none; }

/* — Подсказка в конструкторе — */
.hint { font-size: .76em; color: var(--muted); margin: 2px 0 8px; line-height: 1.4; }
.mini-label { display: block; font-size: .74em; color: var(--muted); margin-bottom: 8px; }
/* Конструктор теста: «+ Вариант» слева, галочка «Несколько ответов» справа (макет владельца). */
.q-opts-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.q-multi { display: inline-flex; align-items: center; gap: 10px; cursor: pointer;
  font-size: .86em; color: var(--text-dim); }
.q-mark { width: 18px; height: 18px; margin: 0; flex: 0 0 auto; cursor: pointer; accent-color: var(--accent); }

/* — Изображение: превью в конструкторе — */
.img-pick {
  width: 120px; height: 80px; flex: 0 0 auto; border-radius: 8px;
  border: 1px dashed var(--border-2); background: var(--surface-2);
  background-size: cover; background-position: center;
  display: flex; align-items: center; justify-content: center;
}

/* — Редактор теста: вопросы — */
.q-edit { border: 1px solid transparent; border-radius: var(--radius-sm); padding: 12px; margin-bottom: 10px; background: var(--surface-2); }
.q-edit-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.q-num { font-size: .76em; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); }

/* — Рендер упражнения (плеер/книга/превью) — */
/* Заголовок — полужирный и в основном цвете (правка владельца продукта): при 400 он
   сливался с текстом теории под ним, отличаясь только кеглем. */
.ex-render .ex-h { font-size: 1.25em; font-weight: 600; color: var(--text); margin-bottom: 8px; }
.ex-render .ex-img { max-width: 100%; max-height: 60vh; height: auto; border-radius: 10px;
  border: 1px solid transparent; display: block; margin: 0 auto; object-fit: contain; }
.ex-render .ex-cap { color: var(--text-dim); font-size: .85em; margin-top: 8px; text-align: center; }
/* Видео-упражнение (эмбед по ссылке) — адаптивный контейнер 16:9.
   Высота — через padding-top (не aspect-ratio): старые мобильные браузеры/WebView (Android
   System WebView, встроенные браузеры мессенджеров), которых на рынке РФ ещё много, не умеют
   aspect-ratio — контейнер схлопывался в 0 высоты и видео визуально «не работало». */
.ex-render .ex-video { position: relative; width: 100%; max-width: 720px; height: 0;
  padding-top: 56.25%; margin: 0 auto; border-radius: 10px; overflow: hidden;
  border: 1px solid transparent; background: #000; }
.ex-render .ex-video-frame { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
/* Заглушка вместо плеера YouTube: ролик, не скачанный на наш сервер, грузится только по
   нажатию — иначе открытие раздела само шло бы на youtube.com (без VPN в РФ этот запрос
   висит и тянет за собой всю вкладку). */
.ex-render .ex-video-hold { position: absolute; inset: 0; width: 100%; height: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  border: 0; background: var(--surface-2); color: var(--text-dim); font: inherit; font-size: .9em; cursor: pointer; }
.ex-render .ex-video-hold:hover { color: var(--text); }
.ex-render .ex-video-play { display: flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; border-radius: 50%; border: 2px solid currentColor; font-size: 18px; }
/* Превью видео/аудио в конструкторе упражнения. */
.ex-vid-preview { max-width: 480px; }
.ex-aud-preview { display: flex; padding: 0; border: none; background: none; margin-bottom: 12px; }
.ex-aud-preview[hidden] { display: none; }
.ex-aud-preview .ex-aplayer { margin: 0; flex: 1 1 auto; min-width: 0; }
/* Кнопка «Загрузить» и сбоку — что загружено (макет владельца). Длинное имя файла
   режется многоточием: min-width:0 у подписи, иначе flex-строка распёрла бы карточку. */
.ex-aud-up { display: flex; align-items: center; gap: 18px; min-width: 0; }
.ex-aud-up .btn { flex: 0 0 auto; min-width: 150px; }
.ex-aud-status { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: .86em; color: var(--muted); }

/* ── Аудио-плеер упражнения (замена нативного <audio controls> — тот выглядит по-разному
   в каждой ОС и слишком «системно» на фоне остального интерфейса). ── */
.ex-aplayer {
  display: flex; align-items: center; gap: 10px; width: 100%;
  margin: 4px 0; padding: 5px 12px 5px 5px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border-2);
}
.ex-aplayer-el { display: none; }
.ex-aplayer-play {
  flex: 0 0 auto; width: 32px; height: 32px; border-radius: 50%; border: none;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  background: var(--btn-primary); color: var(--on-btn-primary);
  transition: filter .15s, transform .1s;
}
.ex-aplayer-play svg { margin-left: 1px; }   /* play-«треугольник» оптически смещаем к центру */
/* Кольцо загрузки вокруг ▶: видно от нажатия до первого звука (audioPlayerNode → setStarting). */
.ex-aplayer-playwrap { position: relative; flex: 0 0 auto; width: 32px; height: 32px; display: flex; }
.ex-aplayer-ring {
  position: absolute; inset: -4px; pointer-events: none; opacity: 0; transition: opacity .15s;
}
.ex-aplayer-playwrap.loading .ex-aplayer-ring { opacity: 1; }
.ex-aplayer-ring svg { display: block; width: 100%; height: 100%; transform: rotate(-90deg); }
.ex-aplayer-ring circle { fill: none; stroke-width: 2.5; }
.ex-aplayer-ring-bg { stroke: var(--border-2); }
.ex-aplayer-ring-arc { stroke: var(--accent); stroke-linecap: round; transition: stroke-dashoffset .25s ease; }
.ex-aplayer-playwrap.loading .ex-aplayer-play { cursor: progress; }
.ex-aplayer-play:hover { filter: brightness(1.08); }
.ex-aplayer-play:active { transform: scale(.92); }
.ex-aplayer-body { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 10px; }
.ex-aplayer-time {
  flex: 0 0 auto; font-size: .78em; color: var(--muted); font-variant-numeric: tabular-nums;
  min-width: 2.4em; text-align: center;
}
.ex-aplayer-seek { flex: 1 1 auto; min-width: 40px; padding: 8px 0; cursor: pointer; touch-action: none; }
.ex-aplayer-track { position: relative; height: 4px; border-radius: 99px; background: var(--border-2); }
.ex-aplayer-fill { position: absolute; inset: 0 auto 0 0; width: 0%; border-radius: 99px; background: var(--accent); }
.ex-aplayer-thumb {
  position: absolute; top: 50%; left: 0%; width: 11px; height: 11px; border-radius: 50%;
  background: var(--accent); transform: translate(-50%, -50%) scale(0);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
  transition: transform .12s ease; pointer-events: none;
}
.ex-aplayer-seek:hover .ex-aplayer-thumb, .ex-aplayer-seek:active .ex-aplayer-thumb { transform: translate(-50%, -50%) scale(1); }
.ex-aplayer-rate, .ex-aplayer-vol {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 8px; border: none;
  background: none; color: var(--text-dim); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: .76em; font-weight: 600; letter-spacing: .2px;
  transition: background .15s, color .15s;
}
.ex-aplayer-rate:hover, .ex-aplayer-vol:hover { background: var(--surface); color: var(--text); }
@media (max-width: 760px) {
  .ex-aplayer { padding: 6px 12px 6px 6px; gap: 8px; }
  .ex-aplayer-rate { display: none; }   /* на мобиле — компактнее, скорость не первична */
}

/* Заполнить пропуски */
/* ⚠️ pre-wrap — переносы строк учителя из поля «Текст с пропусками» (FILL и DND). Без него
   браузер склеивал весь шаблон в один абзац — и в предпросмотре конструктора, и у ученика
   в классе (жалобы учителей). Правило общее для любого текста, который учитель пишет
   многострочным полем: .body-text, .note-box, .choice-q, .essay-prompt, .imgex-*. */
.fill-line { line-height: 2.1; font-size: 1.05em; white-space: pre-wrap; }
.fill-input {
  display: inline-block; min-width: 3ch;
  padding: 3px 8px; margin: 0 3px; vertical-align: baseline;
  background: var(--surface-2); border: 1px solid var(--border-2);
  border-radius: 7px; color: var(--text); font-family: inherit; font-size: .95em;
}
.fill-input:focus { outline: none; border-color: var(--accent); }
.fill-input.correct { border-color: var(--correct); background: color-mix(in srgb, var(--correct) 12%, transparent); color: var(--correct); }
.fill-input.wrong { border-color: var(--wrong); background: color-mix(in srgb, var(--wrong) 10%, transparent); color: var(--wrong); }
.word-bank { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
.wb-chip {
  padding: 6px 14px; border-radius: 99px; cursor: pointer; font-size: .9em;
  background: var(--surface-2); border: 1px solid var(--border-2); color: var(--text-dim);
  transition: border-color .15s, color .15s;
}
.wb-chip:hover { border-color: var(--accent); color: var(--accent-text); }
.fill-preview {
  border: 1px dashed var(--border-2); border-radius: var(--radius-sm);
  padding: 10px 12px; background: var(--surface);
}
/* ⚠️ Фон предпросмотра — --surface, как у карточки упражнения в классе, а не --surface-2:
   слот пропуска и поле ввода залиты именно --surface-2, на таком же фоне они пропадали
   (в предпросмотре «пустое место» вместо пропуска) — а у ученика они видны. Размер текста
   и промежутки — те же, что в классе: правила .ex-dnd/.fill-line общие. */
.fill-preview .fill-input,
.fill-preview .dnd-word { pointer-events: none; }

/* Перетащить слова (DND) */
.ex-dnd .fill-line { line-height: 2.4; }
.dnd-slot {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 84px; min-height: 34px; padding: 2px 6px; margin: 0 4px;
  vertical-align: middle; border: 1px dashed var(--border-2); border-radius: 8px;
  background: var(--surface-2); transition: border-color .15s, background .15s;
}
.dnd-slot.drop { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); }
.dnd-slot.wrong { border-color: var(--wrong); }
.dnd-source {
  display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px;
  padding: 10px 12px; border: 1px solid transparent; border-radius: var(--radius-sm);
  background: rgba(0,0,0,.2); min-height: 46px; transition: border-color .15s;
}
.dnd-source.drop { border-color: var(--accent); }
.dnd-word {
  padding: 6px 14px; border-radius: 8px; cursor: grab; user-select: none; font-size: .95em;
  background: var(--surface); border: 1px solid var(--border-2); color: var(--text);
  transition: border-color .15s, transform .1s;
}
.dnd-word:hover { border-color: var(--accent-border); }
.dnd-word:active { cursor: grabbing; }
.dnd-word.dragging { opacity: .45; }
/* touch-action: none — иначе палец на слове прокручивает страницу вместо перетаскивания. */
.dnd-word { touch-action: none; -webkit-user-select: none; -webkit-touch-callout: none; }
/* Слово, выбранное нажатием: следующее нажатие на пропуск положит его туда. */
.dnd-word.picked { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--surface-2)); }
/* Летающая копия слова при перетаскивании (лежит в body, поэтому размеры задаёт JS). */
.dnd-word.dnd-ghost {
  position: fixed; z-index: 10000; margin: 0; pointer-events: none; box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center; white-space: nowrap;
  border-color: var(--accent); box-shadow: var(--shadow); cursor: grabbing;
}
.dnd-slot .dnd-word { margin: 0; padding: 2px 10px; line-height: normal; }
.dnd-word.correct { border-color: var(--correct); background: color-mix(in srgb, var(--correct) 14%, transparent); color: var(--correct); }
.dnd-word.wrong { border-color: var(--wrong); background: color-mix(in srgb, var(--wrong) 12%, transparent); color: var(--wrong); }
@media (max-width: 760px) {
  /* На узком экране line-height:2.4 и 34px-слот из базовых правил выше (они выигрывают
     у мобильной .room-ex .fill-line по порядку в файле, см. специфичность) растягивали
     упражнение «перетащить слово» в высоту — правим здесь, позже в каскаде.
     ⚠️ Высота .dnd-slot и line-height ЗАВИСИМЫ, а не два независимых числа: строка растёт
     под самый высокий инлайн-элемент в ней, и если слот выше (или почти равен) той высоте,
     что даёт line-height от размера шрифта, соседние строки со слотами сомкнутся вплотную —
     именно так уже было один раз при line-height:1.75 + min-height:27px (слот оказался
     выше строки). Держим слот заметно НИЖЕ строки — с запасом ~6px, а не впритык. */
  .ex-dnd .fill-line { line-height: 1.9; }
  .dnd-slot { min-height: 23px; padding: 1px 6px; }
  .dnd-slot .dnd-word { padding: 1px 8px; }
}

/* Тест */
/* ⚠️ Вопрос и варианты раньше были одного веса, цвета и почти вплотную друг к другу —
   весь тест сливался в сплошную стену текста (жалобы учеников). Теперь:
   вопрос — как был (обычный вес, полный --text); варианты — легче (300 у variable-Inter)
   и на ~20% тусклее; под вопросом — отступ до вариантов, а между вопросами — заметно
   больше воздуха, чтобы каждый вопрос с вариантами читался отдельным блоком. */
.ex-choice .choice-q { font-size: 1.05em; line-height: 1.45; margin: 30px 0 12px; white-space: pre-wrap; }
.ex-choice .choice-q:first-child { margin-top: 0; }
.ex-choice .choice-opt {
  font-weight: 300; line-height: 1.4;
  color: color-mix(in srgb, var(--text) 80%, transparent);
}
/* Выбранный вариант и наведение — полной яркостью: выбор должен читаться сразу. */
.ex-choice .choice-opt:hover, .ex-choice .choice-opt.selected { color: var(--text); }
/* Специфичность (0,3,0) — выше тусклого цвета варианта выше, иначе проверка теряла бы цвет. */
.ex-choice .choice-opt.correct, .choice-opt.correct { border-color: var(--correct); background: color-mix(in srgb, var(--correct) 12%, transparent); color: var(--correct); }
.ex-choice .choice-opt.wrong, .choice-opt.wrong { border-color: var(--wrong); background: color-mix(in srgb, var(--wrong) 10%, transparent); color: var(--wrong); }
/* Короткие варианты (JS решает по реальной ширине текста, exercise-edit.js layoutChoiceOpts) —
   в два столбца; длинные фразы остаются в один (если хоть один вариант длинный — весь вопрос в один столбец). */
.choice-opts-2col { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 10px; }
.choice-opts-2col .choice-opt { margin-bottom: 0; }

/* — Книга (предпросмотр) — */
.book-wrap { max-width: 720px; margin: 0 auto; }
.book { display: flex; flex-direction: column; gap: 8px; }
.book-title-page { text-align: center; padding: 20px 0 40px; border-bottom: 1px solid var(--border); margin-bottom: 24px; }
.book-cover {
  width: 180px; height: 240px; margin: 0 auto 24px; border-radius: 10px;
  background-size: cover; background-position: center; box-shadow: var(--shadow);
  display: flex; align-items: center; justify-content: center;
}
.book-cover-grad { background: linear-gradient(135deg, hsl(160 40% 28%), hsl(200 45% 20%)); }
.book-title { font-size: 2em; font-weight: 300; letter-spacing: .5px; }
.book-lang { color: var(--text-dim); margin-top: 10px; }
.book-desc { color: var(--muted); margin-top: 14px; max-width: 480px; margin-left: auto; margin-right: auto; }
.book-chapter { margin-bottom: 30px; }
.book-chapter-head {
  display: flex; align-items: center; gap: 12px; margin: 28px 0 18px;
  font-size: 1.4em; font-weight: 300; border-bottom: 1px solid var(--border); padding-bottom: 12px;
}
.book-chapter-num {
  width: 36px; height: 36px; flex: 0 0 auto; border-radius: 9px;
  background: var(--surface-2); border: 1px solid var(--border-2);
  display: flex; align-items: center; justify-content: center; font-size: .7em; color: var(--accent-text);
}
.book-ex { background: var(--surface); border: 1px solid transparent; border-radius: var(--radius); padding: 16px 18px; margin-bottom: 12px; }
.book-ex-type { font-size: .7em; text-transform: uppercase; letter-spacing: 1px; color: var(--muted); margin-bottom: 10px; }
.book-ex-actions { display: flex; align-items: center; gap: 12px; margin-top: 16px; }
.book-ex-result { font-size: .95em; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.book-ex-result.ok { color: var(--correct); }
.book-empty { color: var(--muted); font-size: .85em; padding: 8px 0; }

/* «Сейчас проходим» в дашборде ученика — компактная обложка (не растягиваем в портрет) */

/* — Компактный редактор упражнения (модал) — */
.ex-editor .field { margin-bottom: 12px; }
.ex-editor .field:last-child { margin-bottom: 0; }
.ex-editor .textarea { min-height: 72px; }
.ex-editor .hint { margin: 2px 0 6px; }
.ex-editor .img-pick { width: 110px; height: 74px; }

/* ══ Обрезка изображения (cropper.js) ═════════════════════════════
   Один редактор на всю платформу: картинка целиком + рамка с восемью ручками.
   ⚠️ Цвета рамки/затемнения НАМЕРЕННО не из палитры и не меняются в светлой теме:
   они лежат поверх фотографии и обязаны читаться на любой картинке (как .call-*). */
.crop-wrap { display: flex; flex-direction: column; gap: 14px; }
.crop-formats { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
.crop-fmt {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  min-width: 66px; padding: 8px 10px; cursor: pointer; font-family: var(--font);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--text-dim); transition: border-color .15s, color .15s, background .15s;
}
.crop-fmt:hover { border-color: var(--accent-border); color: var(--text); }
.crop-fmt.active { border-color: var(--selected); color: var(--selected); background: color-mix(in srgb, var(--selected) 8%, transparent); }
.crop-fmt .fmt-ico { width: 26px; height: 26px; }
.crop-fmt .fmt-ico rect { fill: none; stroke: currentColor; stroke-width: 2; }
.crop-fmt .fmt-label { font-size: .72em; }

/* Запас по краям — чтобы угловые ручки, торчащие за картинку, не резались окном */
.crop-stage-wrap { display: flex; justify-content: center; padding: 10px; }
.crop-stage {
  position: relative; flex: 0 0 auto; touch-action: none; cursor: crosshair;
  user-select: none; -webkit-user-select: none;
}
/* Шахматка под картинкой — прозрачный PNG (медали, значки) виден как прозрачный */
.crop-clip {
  position: absolute; inset: 0; overflow: hidden; border-radius: 6px;
  background: repeating-conic-gradient(#3b3b3b 0% 25%, #2d2d2d 0% 50%) 0 0 / 16px 16px;
}
.crop-img { position: absolute; inset: 0; display: block; width: 100%; height: 100%; max-width: none;
  pointer-events: none; -webkit-user-drag: none; }
/* Затемнение вне рамки — тень огромного радиуса от самой рамки, режется .crop-clip */
.crop-shade { position: absolute; box-shadow: 0 0 0 9999px rgba(0, 0, 0, .6); pointer-events: none; }
.crop-shade.round { border-radius: 50%; }
.crop-sel {
  --crop-line: rgba(255, 255, 255, .5);
  position: absolute; box-sizing: border-box; cursor: move; touch-action: none;
  border: 1.5px solid #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, .35);
}
.crop-sel.round { border-radius: 50%; }
/* Сетка третей — только пока рамку тянут */
.crop-sel::before {
  content: ''; position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity .12s;
  background:
    linear-gradient(to right, transparent calc(33.333% - .5px), var(--crop-line) calc(33.333% - .5px), var(--crop-line) calc(33.333% + .5px), transparent calc(33.333% + .5px),
      transparent calc(66.666% - .5px), var(--crop-line) calc(66.666% - .5px), var(--crop-line) calc(66.666% + .5px), transparent calc(66.666% + .5px)),
    linear-gradient(to bottom, transparent calc(33.333% - .5px), var(--crop-line) calc(33.333% - .5px), var(--crop-line) calc(33.333% + .5px), transparent calc(33.333% + .5px),
      transparent calc(66.666% - .5px), var(--crop-line) calc(66.666% - .5px), var(--crop-line) calc(66.666% + .5px), transparent calc(66.666% + .5px));
}
.crop-sel.dragging::before { opacity: 1; }
.crop-sel.round::before { display: none; }

/* Ручки. Видимая часть мелкая, а зона нажатия шире (::after) — пальцем попасть */
.crop-h { position: absolute; touch-action: none; }
.crop-h::after { content: ''; position: absolute; }
.crop-h-nw, .crop-h-ne, .crop-h-sw, .crop-h-se {
  z-index: 2; width: 16px; height: 16px; margin: -8px 0 0 -8px; border-radius: 50%;
  background: #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, .4);
}
.crop-h-nw::after, .crop-h-ne::after, .crop-h-sw::after, .crop-h-se::after { inset: -10px; }
.crop-h-nw { left: 0; top: 0; cursor: nwse-resize; }
.crop-h-ne { left: 100%; top: 0; cursor: nesw-resize; }
.crop-h-sw { left: 0; top: 100%; cursor: nesw-resize; }
.crop-h-se { left: 100%; top: 100%; cursor: nwse-resize; }
/* Стороны: полоса вдоль всей стороны (нажатие) + «таблетка» посередине (видна) */
.crop-h-n, .crop-h-s { z-index: 1; left: 0; right: 0; height: 0; cursor: ns-resize; }
.crop-h-n { top: 0; }
.crop-h-s { top: 100%; }
.crop-h-e, .crop-h-w { z-index: 1; top: 0; bottom: 0; width: 0; cursor: ew-resize; }
.crop-h-e { left: 100%; }
.crop-h-w { left: 0; }
.crop-h-n::after, .crop-h-s::after { left: 0; right: 0; top: -9px; bottom: -9px; }
.crop-h-e::after, .crop-h-w::after { top: 0; bottom: 0; left: -9px; right: -9px; }
.crop-h-n::before, .crop-h-s::before, .crop-h-e::before, .crop-h-w::before {
  content: ''; position: absolute; background: #fff; border-radius: 3px; box-shadow: 0 0 0 1px rgba(0, 0, 0, .4);
}
.crop-h-n::before, .crop-h-s::before { left: 50%; top: -3px; width: 28px; height: 6px; margin-left: -14px; }
.crop-h-e::before, .crop-h-w::before { top: 50%; left: -3px; width: 6px; height: 28px; margin-top: -14px; }
/* Пропорции зафиксированы — стороны тянуть нельзя, остаются углы */
.crop-sel.locked .crop-h-n, .crop-sel.locked .crop-h-s,
.crop-sel.locked .crop-h-e, .crop-sel.locked .crop-h-w { display: none; }
@media (pointer: coarse) {
  .crop-h-nw, .crop-h-ne, .crop-h-sw, .crop-h-se { width: 22px; height: 22px; margin: -11px 0 0 -11px; }
  .crop-h-nw::after, .crop-h-ne::after, .crop-h-sw::after, .crop-h-se::after { inset: -12px; }
  .crop-h-n::after, .crop-h-s::after { top: -14px; bottom: -14px; }
  .crop-h-e::after, .crop-h-w::after { left: -14px; right: -14px; }
  .crop-h-n::before, .crop-h-s::before { width: 36px; height: 8px; margin-left: -18px; top: -4px; }
  .crop-h-e::before, .crop-h-w::before { width: 8px; height: 36px; margin-top: -18px; left: -4px; }
}
.crop-hint { font-size: .76em; color: var(--muted); line-height: 1.4; }
/* Шесть форматов на телефоне — два ряда по три: в один ряд подписи не помещаются */
@media (max-width: 560px) {
  .crop-formats { gap: 6px; }
  .crop-fmt { flex: 1 1 calc(33.333% - 6px); min-width: 0; padding: 8px 4px; }
  .crop-fmt .fmt-label { font-size: .68em; }
}

/* ══ Перевод слов (комната класса) + «Мой словарь» ════════════════ */
/* Интерактивное слово: на ПК при наведении чуть «всплывает» (подсказка, что текст живой) */
.tw { cursor: pointer; border-radius: 4px; }
@media (hover: hover) {
  .tw { display: inline-block; transition: color .14s ease, transform .14s ease; }
  .tw:hover { color: var(--accent-text); transform: translateY(-1.5px) scale(1.07); }
}
.tw.tw-active { color: var(--accent-text); background: color-mix(in srgb, var(--accent) 12%, transparent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 12%, transparent); }

/* Попап перевода: аккуратная карточка рядом со словом, со «стрелочкой» на слово */
.tr-pop {
  position: fixed; z-index: 210; padding: 12px 14px;
  background: var(--surface-2); border: 1px solid var(--border-2);
  border-radius: 12px; box-shadow: var(--shadow); animation: modal-in .16s ease;
}
.tr-pop::before {
  content: ''; position: absolute; top: -5.5px; left: var(--arrow-x, 24px);
  width: 10px; height: 10px; transform: translateX(-50%) rotate(45deg);
  background: var(--surface-2); border-left: 1px solid var(--border-2); border-top: 1px solid var(--border-2);
}
.tr-pop.above::before { top: auto; bottom: -5.5px; transform: translateX(-50%) rotate(225deg); }
.tr-head { display: flex; align-items: center; gap: 10px; }
.tr-word { font-weight: 600; font-size: 1.05em; min-width: 0; overflow-wrap: anywhere; }
.tr-head .tr-ico-btn { margin-left: auto; }
/* «Магнит» — прицепить к переводимому слову следующее слово текста (living → living room).
   ⚠️ БЕЗ круглой рамки .tr-ico-btn, в отличие от динамика: динамик прижат к правому краю
   (margin-left:auto выше) и стоит особняком, а магнит принадлежит самому слову и обязан
   читаться его продолжением, а не второй одинаковой кнопкой в шапке. Отрицательный
   margin съедает половину gap шапки (10px) — иначе значок отрывается от слова. */
.tr-magnet {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0; margin-left: -4px;
  border: 0; border-radius: 7px; background: none; color: var(--text-dim);
  cursor: pointer; transition: color .15s, background .15s;
}
.tr-magnet svg { display: block; }
.tr-magnet:hover:not(:disabled) { color: var(--accent-text); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.tr-magnet:disabled { opacity: .3; cursor: default; }
.tr-ico-btn {
  width: 30px; height: 30px; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  padding: 0; line-height: 1;   /* без браузерного паддинга кнопки — иконка ровно по центру круга */
  border-radius: 50%; border: 1px solid var(--border-2); background: var(--surface);
  color: var(--text-dim); cursor: pointer; transition: color .15s, border-color .15s; position: relative;
}
.tr-ico-btn svg { display: block; }
.tr-ico-btn:hover { color: var(--accent-text); border-color: var(--accent-border); }
/* Индикатор «идёт озвучка» — общий для кнопки-динамика везде (попап перевода, «Мой словарь»,
   список слов в упражнении): .spk-loading вешает speak() из translate.js на переданную
   кнопку, пока /api/tts не начал реально звучать. Озвучка — сетевой запрос (первый раз для
   слова — реальный синтез на сервере, секунда-две), и без индикатора тап по динамику
   выглядит так, будто ничего не произошло — непонятно, ждать или жать ещё раз. Гаснет, как
   только звук ДЕЙСТВИТЕЛЬНО заиграл (событие 'playing' у <audio>), а не по таймеру — иначе
   индикатор либо гас бы раньше звука, либо снова врал на медленной сети.
   Заливка по контуру кнопки ПО ЧАСОВОЙ (не пульсация, не вращение целой рамки):
   --spk-pct — угол через @property (иначе браузер не умеет плавно анимировать переменную
   в градусах, а «скачком» из 0 в 360 выглядело бы миганием, а не заливкой); conic-gradient
   красит сектор от 12 часов ПО ЧАСОВОЙ на этот угол — растущий кусок и есть заливка.
   Кольцевую (а не сплошную) форму вырезает классический трюк с двумя масками: сплошной
   квадрат по content-box (внутренняя, обрезанная по padding) исключается (mask-composite)
   из сплошного квадрата по border-box (полного) — остаётся полоса толщиной = padding.
   border-radius: inherit — НЕ 50%: кнопка-динамик бывает круглой (.tr-ico-btn) И почти
   квадратной (.exw-ico в списке слов), один радиус на оба вида не подошёл бы — оба маскируются
   под СВОЙ фактический border-radius, и полоса всегда повторяет форму именно этой кнопки. */
@property --spk-pct {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}
.spk-loading::after {
  content: ''; position: absolute; inset: -3px; border-radius: inherit; pointer-events: none;
  padding: 2px;
  background:
    conic-gradient(var(--accent) var(--spk-pct), transparent var(--spk-pct)),
    color-mix(in srgb, var(--accent) 22%, transparent);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask-composite: exclude;
  animation: spk-fill .9s linear infinite;
}
@keyframes spk-fill { to { --spk-pct: 360deg; } }
.tr-trans { color: var(--muted); font-size: .84em; margin-top: 4px; font-variant-numeric: tabular-nums; }
.tr-result { color: var(--accent-text); font-size: 1.02em; margin-top: 6px; line-height: 1.45;
  overflow-wrap: anywhere; display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
/* Часть речи выбранного смысла (см. exw-pos в «Списке слов» — тот же приём). */
.tr-pos { font-size: .72em; color: var(--muted); padding: 1px 7px; border-radius: 99px;
  border: 1px solid var(--border-2); white-space: nowrap; }
.tr-loading, .tr-error { font-size: .85em; padding: 8px 0 2px; color: var(--muted); }
.tr-error { color: var(--danger); }
/* «Ещё значения» слева, «В словарь» справа (см. макет владельца продукта) — flex-wrap,
   т.к. вместе с длинным «Ученику в словарь» (учительский попап) пара кнопок может не
   влезть в ширину попапа (300px) на маленьком экране. */
.tr-actions { margin-top: 10px; display: flex; gap: 8px; flex-wrap: wrap; }
.tr-add { display: inline-flex; align-items: center; gap: 6px; }
.tr-add.added { color: var(--accent-text); border-color: var(--accent-border); opacity: .85; }
/* «Ещё значения» — остальные словарные варианты слова (список — .tr-more ниже).
   ⚠️ Без transition на раскрытие: попап пересчитывает СВОЮ позицию по высоте контента
   (positionPop), и анимированный рост читал бы старую высоту в момент клика — та же
   ловушка первого кадра, что и у прелоадера сферы онбординга (см. CLAUDE.md). */
.tr-more-btn { color: var(--text-dim); }
.tr-more { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border-2);
  display: flex; flex-direction: column; gap: 6px; max-height: 220px; overflow-y: auto; }
.tr-more.hidden { display: none; }
.tr-more-item { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: .9em; }
.tr-more-text { color: var(--text); overflow-wrap: anywhere; min-width: 0; }
/* Часть речи + кнопка «в словарь» — одной группой у правого края строки. */
.tr-more-right { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
/* Кнопка «в словарь» у КАЖДОГО варианта — свой круглый значок вместо подписи (текста
   в узкой строке и так три части: слово, часть речи, кнопка). Тот же силуэт/логика
   добавления, что у основной кнопки popup'а (.tr-add), только компактно. */
/* ⚠️ min-height/min-width — на мобиле у .btn-sm свой min-height:36px (тач-таргет),
   а он ПЕРЕБИВАЕТ явный height:24px (это разные свойства, min-height задаёт пол
   независимо от того, что выиграло по specificity) — без сброса кружок раздувался
   бы на узком экране. */
.tr-add-compact { padding: 0; width: 24px; height: 24px; min-width: 0; min-height: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; }
.tr-add-compact svg { display: block; }
.tr-add-compact.added { cursor: default; }

/* «Мой словарь» (модал) */
.dict-sub { color: var(--muted); font-size: .82em; margin: -4px 0 10px; min-height: 1em; }
.dict-search { margin-bottom: 10px; }
.dict-list { display: flex; flex-direction: column; gap: 8px; max-height: 56vh; overflow: auto; padding-right: 2px; }
.dict-row {
  display: flex; align-items: center; gap: 10px; padding: 9px 11px;
  background: var(--surface-2); border: 1px solid transparent; border-radius: var(--radius-sm);
}
.dict-main { flex: 1 1 auto; min-width: 0; }
.dict-word { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.dict-tr { color: var(--muted); font-size: .8em; }
.dict-mean { color: color-mix(in srgb, var(--text-dim) 78%, transparent); font-size: .9em; margin-top: 1px; overflow-wrap: anywhere; }
.dict-del { font-size: .8em; }
.dict-del:hover { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, transparent); }
/* Кнопка «Словарь» в шапке книги — как room-mat-add, иконка + подпись (на мобиле — только иконка) */
.room-dict { order: 1; display: inline-flex; align-items: center; gap: 6px; }
.room-dict .rt-ico { display: inline-flex; }
.room-dict .rt-ico svg { display: block; }

/* ══════════════════════════════════════════════════════════════════
   Светлая тема — точечные правки
   Палитра переопределяется через переменные (см. :root[data-theme="light"] вверху),
   поэтому 90% интерфейса адаптируется само. Ниже — только места с «захардкоженными»
   цветами (белые оверлеи, тёмные фоны, глухие тени), которые иначе ломаются на светлом.
   ══════════════════════════════════════════════════════════════════ */
/* Скроллбар: приглушённый на светлом (тёмный ховер выглядел бы грязно) */
[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background-color: #b7bfbb; }

/* Выделение текста: мятная подложка + тёмный текст того же тона (следуют за акцентом) */
[data-theme="light"] ::selection {
  background: color-mix(in srgb, var(--accent) 22%, #fff);
  color: color-mix(in srgb, var(--accent) 75%, #000);
}

/* Ручка выключенного тумблера — белая: серая #cfcfcf на светлой дорожке почти не видна */
[data-theme="light"] .slider::before { background: #fff; }

/* Всплывающие меню — мягкая тень вместо глухой чёрной */
[data-theme="light"] .who-menu,
[data-theme="light"] .combo-menu { box-shadow: 0 14px 34px rgba(20,50,40,.16); }

/* Кнопка-акцент: фирменное свечение под светлый фон (цвет текста — из --on-btn-primary) */
[data-theme="light"] .btn-primary { box-shadow: 0 4px 16px color-mix(in srgb, var(--btn-primary) 22%, transparent); }
[data-theme="light"] .btn-primary:hover { box-shadow: 0 6px 22px color-mix(in srgb, var(--btn-primary) 30%, transparent); }
/* ⚠️ «Войти в класс» из этого правила ВЫКЛЮЧЕНА, и оверрайд стоит именно ЗДЕСЬ, ниже
   .btn-primary: специфичность у них одинаковая, и выше по файлу он проигрывал — тень
   в светлой теме возвращалась. У кнопки своя заливка, одна на обе темы, и тени нет. */
[data-theme="light"] .cd-enter, [data-theme="light"] .cd-enter:hover { box-shadow: none; }

/* Тост-ошибка: тёмно-красный текст читается на светлом */
[data-theme="light"] .toast { box-shadow: 0 12px 32px rgba(20,50,40,.16), 0 2px 8px rgba(20,50,40,.08); }
[data-theme="light"] .toast.err { color: #b91c1c; }

/* Нативная иконка календаря/времени — без инверсии (она предназначена для тёмного) */
[data-theme="light"] .input::-webkit-calendar-picker-indicator { filter: none; opacity: .55; }

/* Ховеры-строки использовали белый оверлей (невидим на светлом) → тёмный оверлей */
[data-theme="light"] .balp-row-btn:hover,
[data-theme="light"] .balp-row:hover,
[data-theme="light"] .stud-sel-opt:hover { background: rgba(0,0,0,.035); }

/* Пузыри мессенджера: свои — мягкая подложка тона акцента с тёмным текстом того же тона.
   Считаются от --chat-mine (по умолчанию = --accent-dim), поэтому переживают смену палитры
   (сплошная заливка, как в тёмной теме, на светлом фоне выглядела бы кислотной). */
[data-theme="light"] .bubble-row.mine .bubble {
  background: color-mix(in srgb, var(--chat-mine) 28%, #fff);
  color: color-mix(in srgb, var(--chat-mine) 78%, #000);
}
[data-theme="light"] .bubble-row.mine .bubble-time {
  color: color-mix(in srgb, var(--chat-mine) 60%, #000); opacity: .7;
}
[data-theme="light"] .room-msg.mine .room-msg-body {
  background: color-mix(in srgb, var(--chat-mine) 28%, #fff);
  color: color-mix(in srgb, var(--chat-mine) 78%, #000);
}
[data-theme="light"] .bubble-quote { background: rgba(0,0,0,.05); }

/* Бейдж группы доступа — синий читаемый на светлом */
[data-theme="light"] .group-badge { color: #2563eb; background: rgba(37,99,235,.08); border-color: rgba(37,99,235,.28); }

/* ⚠️ Точечных оверрайдов для чипов расписания тут БОЛЬШЕ НЕТ: заливки (--lesson-*) одни на
   обе темы, а текст поверх них всегда белый — подбирать оттенок под тему больше нечему. */
[data-theme="light"] .chg-nobalance { color: #6d28d9; background: rgba(124,58,237,.1); border-color: rgba(124,58,237,.35); }


/* Круглые кнопки чата «на весь экран»/«переместить» — фон был тёмным (казался чёрным
   на светлой). ⚠️ Кнопки «свернуть» (.room-chat-min) тут БОЛЬШЕ НЕТ: её фон считается
   от --surface через color-mix и потому верен в обеих темах сам по себе, а это правило
   перебивало бы его почти непрозрачным белым — от «жидкого стекла» не осталось бы ничего. */
[data-theme="light"] .room-chat-max,
[data-theme="light"] .room-chat-move { background: rgba(255,255,255,.82); }
[data-theme="light"] .room-chat-max:hover,
[data-theme="light"] .room-chat-move:hover { background: rgba(255,255,255,.96); }

/* ══════════════════════════════════════════════════════════════════
   Цвета платформы (Настройки → Система → Цвета)
   Редактор палитры + пипетка + живое превью интерфейса.
   ВАЖНО: превью (.tprev) красится ТОЛЬКО из переменных, которые ставятся инлайном
   на его контейнере (см. theme-settings.js). Поэтому оно показывает редактируемую
   тему, даже если сам интерфейс сейчас в другой — не завязывайте .tprev-* на
   [data-theme] и не пишите в них литеральные цвета.
   ⚠️ И НЕ ИСПОЛЬЗУЙТЕ в .tprev-* токены, зависящие от темы ВНЕ палитры:
   --lesson-*-fg, --diamond-ring, --toggle-on, --shadow. Контейнеру превью
   они НЕ ставятся и приедут по наследству с <html> — то есть из темы ИНТЕРФЕЙСА,
   а не редактируемой (в светлой/тёмной они разные). --grade-* — исключение: их
   ставит paintPreview (theme-settings.js) под редактируемую тему, использовать можно.
   ══════════════════════════════════════════════════════════════════ */
.clr-seg { display: inline-flex; gap: 4px; padding: 4px; flex-wrap: wrap;
  background: var(--surface-2); border: 1px solid var(--border-2); border-radius: 12px; }
.clr-seg-btn { display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  padding: 8px 14px; border: none; background: none; border-radius: 9px; cursor: pointer;
  color: var(--text-dim); font-family: var(--font); text-align: left;
  transition: background .15s, color .15s; }
.clr-seg-btn b { font-size: .88em; font-weight: 500; }
.clr-seg-btn span { font-size: .72em; color: var(--muted); }
.clr-seg-btn:hover { background: var(--surface); color: var(--text); }
.clr-seg-btn.active { background: var(--surface); color: var(--accent-text); box-shadow: 0 1px 6px rgba(0,0,0,.18); }
.clr-seg-btn.active b { font-weight: 600; }

/* Две колонки (поля + превью) — по ширине САМОЙ панели, а не окна: слева от неё ещё меню
   настроек, поэтому ориентироваться на ширину экрана нельзя. */
.clr { container-type: inline-size; }
/* Кому показывать палитру школы (роли) */
.clr-roles { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 8px; }
.clr-role { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 14px; cursor: pointer; font-size: .88em;
  background: var(--surface-2); border: 1px solid transparent; border-radius: var(--radius-sm);
  transition: border-color .12s; }
.clr-role:hover { border-color: var(--accent-border); }
.clr-role:has(input:checked) { border-color: var(--accent-border);
  background: color-mix(in srgb, var(--accent) 6%, var(--surface-2)); }

.clr-work { display: grid; grid-template-columns: 1fr; gap: 18px; align-items: start; margin-top: 20px; }
/* Без min-width:0 колонка-грид растягивается по min-content карточек и вылезает за экран на мобиле */
.clr-work > * { min-width: 0; }
@container (min-width: 820px) {
  .clr-work { grid-template-columns: minmax(0, 1fr) minmax(300px, 340px); }
  .clr-preview { position: sticky; top: 74px; }   /* видно при прокрутке длинного списка цветов */
}
.clr-editor { margin: 0; }
.clr-themes { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; flex-wrap: wrap; }
.clr-themes-cap { font-size: .86em; color: var(--text-dim); }
.clr-group { margin-top: 22px; }
.clr-group-head { display: flex; flex-direction: column; gap: 2px;
  margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
.clr-group-head b { font-size: .95em; font-weight: 600; }
.clr-group-head span { font-size: .78em; color: var(--muted); }
/* «Тонкая настройка» — свёрнутый блок дочерних оттенков: основной список цветов остаётся
   коротким, редкие строки не мешаются. Первый clr-group внутри — без лишнего отступа сверху. */
.clr-adv { margin-top: 26px; border: 1px solid var(--border); border-radius: var(--radius-sm); }
.clr-adv > summary { display: flex; align-items: baseline; gap: 10px; padding: 12px 14px;
  cursor: pointer; user-select: none; list-style: none; }
.clr-adv > summary::-webkit-details-marker { display: none; }
.clr-adv > summary::before { content: '▸'; font-size: .8em; color: var(--muted);
  transition: transform .15s; }
.clr-adv[open] > summary::before { transform: rotate(90deg); }
.clr-adv > summary b { font-size: .95em; font-weight: 600; }
.clr-adv > summary .clr-adv-cap { font-size: .78em; color: var(--muted); }
.clr-adv > summary:hover b { color: var(--accent-text); }
.clr-adv > .clr-group { margin-top: 4px; padding: 0 14px 6px; }
.clr-rows { display: flex; flex-direction: column; gap: 4px; }
.clr-row { display: flex; align-items: center; gap: 14px; padding: 8px 10px;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  transition: background .12s, border-color .12s; }
.clr-row:hover { background: var(--surface-2); }
/* Изменённый (не стандартный) цвет — подсвечиваем строку, чтобы было видно, что тронуто */
.clr-row-custom { border-color: var(--accent-border); background: color-mix(in srgb, var(--accent) 5%, transparent); }
.clr-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.clr-info b { font-size: .88em; font-weight: 500; }
.clr-info span { font-size: .76em; color: var(--muted); line-height: 1.4; }
/* Метка «наследует от родителя» у дочернего токена без своего цвета */
.clr-info .clr-inherit { font-size: .72em; color: var(--accent-text); opacity: .85; }
.clr-ctl { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.clr-hex { width: 108px; padding: 8px 10px; font-size: .82em;
  font-family: ui-monospace, Consolas, monospace; }
.clr-swatch { width: 36px; height: 36px; flex: 0 0 auto; border-radius: 9px; cursor: pointer;
  border: 1px solid var(--border-2); box-shadow: inset 0 0 0 1px rgba(0,0,0,.15); }
.clr-swatch:hover { border-color: var(--accent); transform: scale(1.05); }
.clr-reset { width: 28px; height: 28px; flex: 0 0 auto; border: none; border-radius: 8px;
  background: none; color: var(--muted); cursor: pointer; font-size: 1em; }
.clr-reset:hover { color: var(--accent-text); background: var(--surface); }
.clr-foot { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--border); }
.clr-status { font-size: .8em; color: var(--muted); }
.clr-status.ok { color: var(--accent-text); }
.clr-status.err { color: var(--danger); }

/* Профили оформления: заголовок панели + выпадающий список сохранённых палитр */
.clr-head-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.clr-presets { position: relative; }
.clr-presets-btn { display: inline-flex; align-items: center; gap: 6px; }
.clr-presets-caret { font-size: .8em; color: var(--muted); transition: transform .15s ease; }
.clr-presets.open .clr-presets-caret { transform: rotate(180deg); }
.clr-preset-pop { position: absolute; top: calc(100% + 6px); right: 0; z-index: 40; width: 280px;
  max-width: min(280px, 86vw); display: none; flex-direction: column; gap: 10px; padding: 12px;
  background: var(--surface); border: 1px solid var(--border-2); border-radius: 12px; box-shadow: var(--shadow); }
.clr-presets.open .clr-preset-pop { display: flex; }
.clr-preset-save { display: flex; gap: 6px; }
.clr-preset-save .clr-preset-name { flex: 1; min-width: 0; }
.clr-preset-cap { font-size: .68em; color: var(--muted); text-transform: uppercase; letter-spacing: .6px; }
.clr-preset-list { display: flex; flex-direction: column; gap: 4px; max-height: 260px; overflow-y: auto; }
.clr-preset-row { display: flex; align-items: center; gap: 6px; }
.clr-preset-apply { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  cursor: pointer; color: var(--text); font-family: var(--font); font-size: .88em; text-align: left;
  transition: background .12s ease, border-color .12s ease; }
.clr-preset-apply:hover { background: var(--surface); border-color: var(--accent-border); }
.clr-preset-dot { flex: 0 0 auto; width: 12px; height: 12px; border-radius: 50%;
  background: var(--accent); border: 1px solid color-mix(in srgb, var(--text) 20%, transparent); }
.clr-preset-nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.clr-preset-del { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0; background: none; border: 1px solid var(--border); border-radius: 8px;
  color: var(--muted); cursor: pointer; transition: color .12s ease, border-color .12s ease, background .12s ease; }
.clr-preset-del:hover { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 50%, transparent);
  background: color-mix(in srgb, var(--danger) 12%, transparent); }
.clr-preset-del svg { width: 15px; height: 15px; }
.clr-preset-empty { font-size: .82em; color: var(--muted); padding: 6px 2px; }
.clr-off { display: flex; flex-direction: column; gap: 6px; }
.clr-off b { font-size: .95em; }
.clr-off p { font-size: .84em; color: var(--muted); line-height: 1.55; }

.clr-preview { padding: 16px;
  background: var(--surface); border: 1px solid transparent; border-radius: var(--radius); }
.clr-preview-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 12px; }
.clr-preview-head span { font-size: .78em; color: var(--muted); }

/* ── Пипетка (палитра как в графическом редакторе) ────────────────── */
.cp { position: fixed; z-index: 200; width: 248px; padding: 12px; display: flex; flex-direction: column; gap: 10px;
  background: var(--surface); border: 1px solid var(--border-2); border-radius: var(--radius); box-shadow: var(--shadow); }
.cp-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.cp-title { font-size: .82em; color: var(--text-dim); }
.cp-x { border: none; background: none; color: var(--muted); cursor: pointer; font-size: .85em; }
.cp-x:hover { color: var(--text); }
.cp-sv { position: relative; height: 132px; border-radius: 9px; cursor: crosshair;
  border: 1px solid var(--border-2); touch-action: none; }
.cp-dot { position: absolute; width: 12px; height: 12px; border-radius: 50%; pointer-events: none;
  border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,.5); transform: translate(-50%, -50%); }
.cp-hue { position: relative; height: 12px; border-radius: 99px; cursor: pointer; touch-action: none;
  background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); }
.cp-hue-dot { position: absolute; top: 50%; width: 14px; height: 14px; border-radius: 50%; pointer-events: none;
  background: #fff; border: 1px solid rgba(0,0,0,.35); transform: translate(-50%, -50%); }
.cp-row { display: flex; align-items: center; gap: 8px; }
.cp-out { width: 32px; height: 32px; flex: 0 0 auto; border-radius: 8px; border: 1px solid var(--border-2); }
.cp-hex { flex: 1; min-width: 0; padding: 8px 10px; font-size: .82em;
  font-family: ui-monospace, Consolas, monospace; }
.cp-presets { display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px; }
.cp-preset { width: 100%; aspect-ratio: 1; border-radius: 5px; cursor: pointer;
  border: 1px solid var(--border-2); transition: transform .1s; }
.cp-preset:hover { transform: scale(1.15); }
.cp-reset { width: 100%; }

/* ── Живое превью интерфейса ──────────────────────────────────────── */
.tprev { border: 1px solid var(--border); border-radius: 12px; overflow: hidden;
  background: var(--bg); color: var(--text); font-size: 13px; }
.tprev-top { display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  background: var(--tb-bg); border-bottom: 1px solid var(--border); }
.tprev-brand { flex: 0 0 auto; font-size: .7em; font-weight: 300; letter-spacing: 2px; color: var(--tb-text); }
.tprev-nav { display: flex; gap: 2px; flex: 1; min-width: 0; overflow: hidden; }
.tprev-nav > span { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap;
  padding: 4px 7px; border-radius: 6px; font-size: .68em; color: var(--tb-text-dim); }
.tprev-nav .active { color: var(--tb-accent); background: var(--tb-surface-2); }
.tprev-badge { display: inline-grid; place-items: center; min-width: 14px; height: 14px; padding: 0 4px;
  border-radius: 99px; background: var(--tb-accent); color: var(--tb-on-accent);
  font-size: .7em; font-style: normal; font-weight: 700; }
.tprev-ava { flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center;
  background: var(--tb-surface-2); color: var(--tb-accent); font-size: .68em; font-weight: 600; }
.tprev-body { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 10px; background: var(--bg); }
.tprev-card { display: flex; flex-direction: column; gap: 6px; min-width: 0; padding: 9px 10px;
  background: var(--surface); border: 1px solid transparent; border-radius: 10px; }
.tprev-card-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.tprev-card-head b { font-size: .78em; font-weight: 600; color: var(--text); }
.tprev-dim { font-size: .68em; color: var(--muted); }
/* Расписание — кусочек недельной сетки (как на самом экране расписания) */
.tprev-wide { grid-column: 1 / -1; }
/* Внешний край — как у настоящей сетки: своей обводки нет, рамка — кольцо по правке школы
   (`--sched-frame-line`). Прежняя постоянная обводка показывала рамку, которой в
   расписании нет. */
.tprev-grid { display: grid; grid-template-columns: 38px repeat(3, 1fr);
  box-shadow: 0 0 0 1px var(--sched-frame-line); border-radius: 8px; overflow: hidden; position: relative;
  /* Та же формула, что у .sched-grid: подстановка color-mix обязана произойти НА дереве
     превью, где свои --border-2/--text-dim (инлайн от редактируемой темы). */
  --today-line: color-mix(in srgb, color-mix(in srgb, var(--border-2) 65%, var(--text-dim) 35%) 27%, transparent); }
.tprev-gh { padding: 4px 2px; text-align: center; font-size: .6em; line-height: 1.3;
  background: var(--sched-head-bg); color: var(--sched-head-text);
  border-left: 1px solid var(--sched-grid); border-bottom: 1px solid var(--sched-grid); }
.tprev-gh-corner { border-left: none; }
.tprev-gh b { display: block; font-weight: 600; }
.tprev-gh span { color: var(--sched-head-date); }
/* Сегодняшний день — как в настоящей сетке: НЕ акцентная подложка (её убрали ещё при отказе
   от цветной полоски дня), а 7% текста поверх серой шапки; колонку обводит отдельный оверлей. */
.tprev-gh.today { background: color-mix(in srgb, var(--sched-head-today) 7%, var(--sched-head-bg)); }
.tprev-gh.today b, .tprev-gh.today span { color: var(--sched-head-today); }
.tprev-gt { padding: 6px 5px 0; text-align: right; font-size: .58em; color: var(--muted);
  border-bottom: 1px solid var(--sched-grid); }
.tprev-gc { min-height: 26px; padding: 2px; border-left: 1px solid var(--sched-grid); border-bottom: 1px solid var(--sched-grid); }
/* Прошедший час — та же штриховка, что .sched-cell.fill (5% текста + фон 1%; прогрессию
   часа в превью не считаем — штриховка полная). Раньше штриховались ВСЕ пустые ячейки —
   превью выглядело темнее настоящей сетки, где будущее свободное время чистое. */
.tprev-gc.past { background-color: color-mix(in srgb, var(--text) 1%, transparent);
  background-image: repeating-linear-gradient(45deg,
    color-mix(in srgb, var(--text) 5%, transparent) 0 1.5px, transparent 1.5px 7px); }
/* Отмеченный рабочий час — как .sched-cell.work (`--work-fill`). */
.tprev-gc.work { background-color: var(--work-fill); }
/* Рамка сегодняшней колонки — ОДИН элемент-оверлей поверх колонки, как .sched-today-overlay
   (тень на каждой ячейке рвалась бы линиями сетки). Сетка: 4 колонки (угол + 3 дня),
   4 строки (шапка + 3 часа) — сегодня колонка 3; последний в DOM, поэтому ложится НАД.
   ⚠️ `position: absolute` обязателен: элемент с ЯВНЫМИ колонкой и строкой грид размещает
   ДО автоматических, и без выноса из потока оверлей занимал колонку 3 — ячейки обтекали
   его, и превью показывало перемешанную сетку («Ср» уезжала во вторую строку). Абсолютный
   элемент грида в автоплейсменте не участвует, а область (колонка 3, строки 1–4) остаётся
   его контейнером — встаёт ровно туда же. */
.tprev-todayline { grid-column: 3; grid-row: 1 / span 4; pointer-events: none;
  position: absolute; inset: 0;
  box-shadow: inset 0 0 0 1px var(--today-line); }
.tprev-chip { display: flex; flex-direction: column; padding: 3px 5px; border-radius: 5px;
  font-size: .58em; line-height: 1.25;
  background: var(--surface); border: 0; color: var(--text-dim); }
.tprev-chip b { font-weight: 700; }
/* Сплошные заливки — как в самой сетке (см. .lesson-chip): превью обязано показывать то,
   что человек увидит в расписании, иначе настраивать цвет по нему бессмысленно. */
.tprev-chip.done { color: #fff; background: var(--lesson-done); }
.tprev-chip.miss { color: #fff; background: var(--lesson-noshow); }
.tprev-chip.plan { color: #fff; background: var(--lesson-upcoming); }
.tprev-legend { display: flex; gap: 9px; flex-wrap: wrap; }
.tprev-lg { display: inline-flex; align-items: center; gap: 4px; font-size: .62em; color: var(--muted); }
.tprev-lg::before { content: ''; width: 7px; height: 7px; border-radius: 2px; background: var(--border-2); }
.tprev-lg.done::before { background: var(--lesson-done); }
/* Точка легенды = ЗАЛИВКА чипа (--lesson-noshow), а не сырой --noshow: у неявки они разные
   (чип затемнён color-mix'ом ради белого текста), и раньше точка легенды врала о цвете. */
.tprev-lg.miss::before { background: var(--lesson-noshow); }
.tprev-lg.plan::before { background: var(--lesson-upcoming); }
.tprev-badge-acc { flex: 0 0 auto; padding: 2px 7px; border-radius: 99px; font-size: .62em;
  color: var(--badge-accent); border: 1px solid var(--accent-border);
  background: color-mix(in srgb, var(--badge-accent) 10%, transparent); }
.tprev-hw { align-self: flex-start; padding: 3px 6px; border-radius: 6px; font-size: .64em;
  color: var(--warn); border: 1px solid color-mix(in srgb, var(--warn) 40%, transparent);
  background: color-mix(in srgb, var(--warn) 12%, transparent); }
.tprev-btns { display: flex; gap: 5px; flex-wrap: wrap; margin-top: auto; }
.tprev-btn { padding: 5px 9px; border-radius: 7px; font-size: .66em; cursor: default; font-family: var(--font);
  background: var(--surface); border: 1px solid var(--border-2); color: var(--text); }
.tprev-btn.primary { background: var(--btn-primary); border-color: var(--btn-primary); color: var(--on-btn-primary); font-weight: 600; }
.tprev-btn.danger { color: var(--btn-danger); border-color: transparent;
  background: color-mix(in srgb, var(--btn-danger) 10%, transparent); }
.tprev-btn.sm { align-self: flex-start; }
/* Вкладки класса — состояние «выбрано» (--selected) в тех же пропорциях, что у реальных
   вкладок/пунктов выбора (color-mix 12% + цветной текст). */
.tprev-tabs { display: flex; gap: 4px; }
.tprev-tabs > span { padding: 3px 7px; border-radius: 6px; font-size: .66em;
  background: var(--surface-2); color: var(--text-dim); }
.tprev-tabs > span.active { color: var(--selected); font-weight: 600;
  background: color-mix(in srgb, var(--selected) 12%, transparent); }
/* Полоса прогресса плеера — как .player-progress в реальном уроке. */
.tprev-progress { flex: 0 0 auto; height: 4px; border-radius: 99px; background: var(--surface-2); overflow: hidden; }
.tprev-progress > div { height: 100%; width: 64%; background: var(--progress); }
.tprev-bubble { max-width: 84%; display: flex; align-items: flex-end; gap: 6px;
  padding: 5px 8px; border-radius: 10px; font-size: .68em; }
.tprev-bubble.theirs { align-self: flex-start; background: var(--surface-2);
  border: 1px solid transparent; color: var(--text); border-bottom-left-radius: 3px; }
.tprev-bubble.mine { align-self: flex-end; background: var(--chat-mine);
  color: var(--on-chat-mine); border-bottom-right-radius: 3px; }
/* В светлой теме свои сообщения — мягкая подложка тона акцента (как в самом мессенджере) */
.tprev-light .tprev-bubble.mine { background: color-mix(in srgb, var(--chat-mine) 28%, #fff);
  color: color-mix(in srgb, var(--chat-mine) 78%, #000); }
.tprev-time { font-style: normal; font-size: .74em; opacity: .65; }
.tprev-input { display: flex; align-items: center; gap: 6px; margin-top: 2px; padding: 5px 6px;
  background: var(--bg); border: 1px solid var(--border-2); border-radius: 8px; }
.tprev-ph { flex: 1; font-size: .66em; color: var(--muted); }
.tprev-send { width: 20px; height: 20px; flex: 0 0 auto; border-radius: 6px; display: grid; place-items: center;
  background: var(--btn-primary); color: var(--on-btn-primary); font-size: .58em; }
.tprev-ex { font-size: .72em; line-height: 2; color: var(--text); }
.tprev-gap { display: inline-block; min-width: 40px; text-align: center; font-style: normal;
  color: var(--correct); border-bottom: 1px solid var(--border-2); }
.tprev-gap.filled { border-bottom-color: var(--correct); }
.tprev-opt { display: flex; align-items: center; justify-content: space-between; gap: 6px;
  padding: 4px 7px; border-radius: 7px; font-size: .68em;
  background: var(--surface-2); border: 1px solid var(--border-2); color: var(--text-dim); }
.tprev-opt.correct { color: var(--correct); border-color: var(--correct);
  background: color-mix(in srgb, var(--correct) 14%, transparent); }
/* Неверный и выбранный варианты — те же пропорции, что у реального .choice-opt. */
.tprev-opt.wrong { color: var(--wrong); border-color: var(--wrong);
  background: color-mix(in srgb, var(--wrong) 12%, transparent); }
.tprev-opt.selected { color: var(--selected); border-color: var(--selected);
  background: color-mix(in srgb, var(--selected) 8%, transparent); }
.tprev-opt i { font-style: normal; }
@media (max-width: 560px) {
  .tprev-body { grid-template-columns: 1fr; }
  .clr-row { flex-wrap: wrap; }
  .clr-hex { width: 92px; }
}

/* ══════════════════════════════════════════════════════════════════
   Анимация фона (.bgfx) — Настройки → Система → Цвета → «Анимация фона»
   Правила производительности (держаться обязательно):
     • анимируем ТОЛЬКО transform/opacity — это композитор, без перерисовки страницы;
     • никаких filter: blur() на больших слоях — мягкость дают радиальные градиенты;
     • ничего не подгружается: только градиенты, ни картинок, ни JS-цикла.
   Совместимость (мобильные Safari):
     • цвета НЕ через color-mix — их считает JS и ставит rgba-строками в --fx-*
       (theme.js applyBgTo); в CSS ниже только форма эффекта + фолбэки var();
     • длительности анимаций ставит JS инлайном (настройка «скорость») — без calc() в CSS;
     • без отрицательных z-index: слой на z-index:0, контент (.app) поднят на z-index:1.
   Разметка одна и та же для страницы, превью и плиток (theme.js bgLayer()):
   .bgfx[data-effect] > span × 3.
   ══════════════════════════════════════════════════════════════════ */
.bgfx { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0;
  --fx-a: rgba(110,231,183,.6);   /* основной цвет эффекта (JS перезапишет из настроек) */
  --fx-b: rgba(47,111,89,.55);     /* дополнительный */
  --fx-c: rgba(110,231,183,.4);   /* смесь основного и дополнительного */
  --fx-line: rgba(110,231,183,.32);/* линии: сетка, лучи, диагонали */
  --fx-dot: rgba(110,231,183,.65); /* точки: орнамент, звёзды */
}
.bgfx-page { position: fixed; contain: layout paint; }
/* Контент — над фоновым слоем */
.app { position: relative; z-index: 1; }
.bgfx > span { position: absolute; display: block; will-change: transform, opacity; }

/* ── Северное сияние: три мягких пятна, медленно перетекающих друг в друга ── */
.bgfx[data-effect="aurora"] > span { width: 72%; height: 72%; border-radius: 50%; opacity: .85; }
.bgfx[data-effect="aurora"] > span:nth-child(1) { top: -22%; left: -12%;
  background: radial-gradient(circle, var(--fx-a), transparent 65%);
  animation: bgfx-a1 26s ease-in-out infinite alternate; }
.bgfx[data-effect="aurora"] > span:nth-child(2) { top: -5%; left: 45%;
  background: radial-gradient(circle, var(--fx-b), transparent 65%);
  animation: bgfx-a2 32s ease-in-out infinite alternate; }
.bgfx[data-effect="aurora"] > span:nth-child(3) { top: 38%; left: 8%;
  background: radial-gradient(circle, var(--fx-c), transparent 65%);
  animation: bgfx-a3 38s ease-in-out infinite alternate; }
@keyframes bgfx-a1 { from { transform: translate3d(0, 0, 0) scale(1); } to { transform: translate3d(16%, 12%, 0) scale(1.25); } }
@keyframes bgfx-a2 { from { transform: translate3d(0, 0, 0) scale(1.1); } to { transform: translate3d(-20%, 16%, 0) scale(.9); } }
@keyframes bgfx-a3 { from { transform: translate3d(0, 0, 0) scale(1); } to { transform: translate3d(18%, -16%, 0) scale(1.3); } }

/* ── Волны: три широких купола света у нижнего края, покачиваются в противофазе ── */
.bgfx[data-effect="waves"] > span { left: -25%; width: 150%; height: 60%; }
.bgfx[data-effect="waves"] > span:nth-child(1) { bottom: -26%;
  background: radial-gradient(ellipse 60% 90% at 50% 100%, var(--fx-a), transparent 70%);
  animation: bgfx-wave 14s ease-in-out infinite alternate; }
.bgfx[data-effect="waves"] > span:nth-child(2) { bottom: -30%; opacity: .8;
  background: radial-gradient(ellipse 70% 95% at 40% 100%, var(--fx-b), transparent 70%);
  animation: bgfx-wave 20s ease-in-out infinite alternate -6s; }
.bgfx[data-effect="waves"] > span:nth-child(3) { bottom: -34%; opacity: .7;
  background: radial-gradient(ellipse 80% 100% at 62% 100%, var(--fx-c), transparent 70%);
  animation: bgfx-wave 26s ease-in-out infinite alternate -14s; }
@keyframes bgfx-wave { from { transform: translate3d(-6%, 1.5%, 0); } to { transform: translate3d(6%, -1.5%, 0); } }

/* ── Плывущие шары: круги всплывают снизу вверх и растворяются
     (размер через padding-bottom, а не aspect-ratio — работает на старых iOS) ── */
.bgfx[data-effect="orbs"] > span { bottom: -32%; height: 0; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, var(--fx-a), var(--fx-b) 60%, transparent 72%); }
.bgfx[data-effect="orbs"] > span:nth-child(1) { width: 22%; padding-bottom: 22%; left: 10%;
  animation: bgfx-float 26s linear infinite; }
.bgfx[data-effect="orbs"] > span:nth-child(2) { width: 13%; padding-bottom: 13%; left: 52%;
  animation: bgfx-float 34s linear infinite -8s; }
.bgfx[data-effect="orbs"] > span:nth-child(3) { width: 30%; padding-bottom: 30%; left: 76%;
  animation: bgfx-float 42s linear infinite -20s; }
@keyframes bgfx-float {
  0% { transform: translate3d(0, 0, 0); opacity: 0; }
  15% { opacity: .75; }
  85% { opacity: .55; }
  100% { transform: translate3d(6%, -170%, 0); opacity: 0; }
}

/* ── Звёздное небо: поле звёзд медленно дрейфует, два слоя мерцают ── */
.bgfx[data-effect="stars"] > span { top: -20%; left: -20%; width: 140%; height: 140%;
  background-repeat: repeat; background-size: 230px 230px; }
.bgfx[data-effect="stars"] > span:nth-child(1) {
  background-image:
    radial-gradient(1.3px 1.3px at 17px 34px, var(--fx-dot) 50%, transparent 100%),
    radial-gradient(1px 1px at 94px 122px, var(--fx-dot) 50%, transparent 100%),
    radial-gradient(1.7px 1.7px at 152px 55px, var(--fx-dot) 50%, transparent 100%),
    radial-gradient(1px 1px at 205px 172px, var(--fx-dot) 50%, transparent 100%),
    radial-gradient(1.2px 1.2px at 60px 195px, var(--fx-dot) 50%, transparent 100%);
  animation: bgfx-drift 80s ease-in-out infinite alternate; }
.bgfx[data-effect="stars"] > span:nth-child(2) {
  background-image:
    radial-gradient(1.5px 1.5px at 45px 80px, var(--fx-dot) 50%, transparent 100%),
    radial-gradient(1px 1px at 130px 20px, var(--fx-dot) 50%, transparent 100%),
    radial-gradient(1.2px 1.2px at 190px 110px, var(--fx-dot) 50%, transparent 100%);
  animation: bgfx-twinkle 7s ease-in-out infinite alternate; }
.bgfx[data-effect="stars"] > span:nth-child(3) {
  background-image:
    radial-gradient(1.2px 1.2px at 80px 160px, var(--fx-dot) 50%, transparent 100%),
    radial-gradient(1.6px 1.6px at 168px 210px, var(--fx-dot) 50%, transparent 100%),
    radial-gradient(1px 1px at 22px 130px, var(--fx-dot) 50%, transparent 100%);
  animation: bgfx-twinkle 11s ease-in-out infinite alternate -4s; }
@keyframes bgfx-drift { from { transform: translate3d(0, 0, 0); } to { transform: translate3d(4%, 2%, 0); } }
@keyframes bgfx-twinkle { from { opacity: .3; } to { opacity: 1; } }

/* ── Лучи: веер света, очень медленно вращается ── */
.bgfx[data-effect="rays"] > span:nth-child(1) { top: -70%; left: -30%; width: 160%; height: 240%; opacity: .85;
  background: repeating-conic-gradient(from 0deg at 50% 50%,
    var(--fx-line) 0deg 4deg, transparent 4deg 16deg);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000, transparent 60%);
  mask-image: radial-gradient(circle at 50% 50%, #000, transparent 60%);
  animation: bgfx-rot 90s linear infinite; }
.bgfx[data-effect="rays"] > span:nth-child(n+2) { display: none; }

/* ── Сетка: решётка, уходящая вверх (шаг --step → цикл бесшовный) ── */
.bgfx[data-effect="grid"] { --step: 48px; }
.bgfx[data-effect="grid"] > span:nth-child(1) { top: -30%; left: -10%; width: 120%; height: 170%; opacity: .9;
  background-image:
    linear-gradient(to right, var(--fx-line) 1px, transparent 1px),
    linear-gradient(var(--fx-line) 1px, transparent 1px);
  background-size: var(--step) var(--step);
  -webkit-mask-image: radial-gradient(ellipse 70% 55% at 50% 35%, #000, transparent 75%);
  mask-image: radial-gradient(ellipse 70% 55% at 50% 35%, #000, transparent 75%);
  animation: bgfx-step 14s linear infinite; }
.bgfx[data-effect="grid"] > span:nth-child(n+2) { display: none; }

/* ── Орнамент: точечный узор, плывущий по диагонали ── */
.bgfx[data-effect="dots"] { --step: 26px; }
.bgfx[data-effect="dots"] > span:nth-child(1) { top: -25%; left: -25%; width: 150%; height: 150%;
  background-image: radial-gradient(var(--fx-dot) 1.4px, transparent 1.5px);
  background-size: var(--step) var(--step);
  -webkit-mask-image: radial-gradient(ellipse 65% 60% at 50% 35%, #000, transparent 80%);
  mask-image: radial-gradient(ellipse 65% 60% at 50% 35%, #000, transparent 80%);
  animation: bgfx-diag 22s linear infinite; }
.bgfx[data-effect="dots"] > span:nth-child(n+2) { display: none; }

@keyframes bgfx-rot { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes bgfx-step { from { transform: translate3d(0, 0, 0); } to { transform: translate3d(0, var(--step), 0); } }
@keyframes bgfx-diag { from { transform: translate3d(0, 0, 0); } to { transform: translate3d(var(--step), var(--step), 0); } }

/* ── Плетёнка: плетёный узор из «нитей» двух цветов, бесконечно ползёт вбок.
     Единственный эффект на background-position (не transform) — узор мелкий и тайлится,
     так что перерисовка дешёвая. Цвета нитей = --fx-a/--fx-b, подложка = --fx-c/--fx-line. */
.bgfx[data-effect="weave"] { --s: 140px; }
.bgfx[data-effect="weave"] > span:nth-child(1) { inset: 0; width: 100%; height: 100%;
  --_g: #0000 25%, var(--fx-a) 53% 147%, var(--fx-b) 153% 247%,
    var(--fx-a) 253% 347%, var(--fx-b) 353% 447%, var(--fx-a) 453% 547%, #0000 575%;
  --_s: calc(25%/3) calc(25%/4) at 50%;
  background:
    radial-gradient(var(--_s) 100%, var(--_g)),
    radial-gradient(var(--_s) 100%, var(--_g)) calc(var(--s)/2) calc(3*var(--s)/4),
    radial-gradient(var(--_s) 0, var(--_g)) calc(var(--s)/2) 0,
    radial-gradient(var(--_s) 0, var(--_g)) 0 calc(3*var(--s)/4),
    repeating-linear-gradient(90deg, var(--fx-c) calc(25%/-6) calc(25%/6), var(--fx-line) 0 calc(25%/2));
  background-size: var(--s) calc(3*var(--s)/2);
  animation: bgfx-weave 9s infinite linear; }
.bgfx[data-effect="weave"] > span:nth-child(n+2) { display: none; }
@keyframes bgfx-weave { to { background-position:
  var(--s) 0,
  calc(var(--s)/-2) calc(3*var(--s)/4),
  calc(3*var(--s)/2) 0,
  calc(-1*var(--s)) calc(3*var(--s)/4),
  0 0; } }

/* ── Радужное зерно: переливающийся конический градиент, «просеянный» сквозь сетку точек
     и шум (feTurbulence из data-URI — без внешних файлов). Мерцание = сдвиг маски-шума.
     Палитра фиксированная (радуга); из настроек действуют «Яркость» (--fx-op) и «Скорость». */
.bgfx[data-effect="grain"] { --grain-size: 22px; }
.bgfx[data-effect="grain"] > span:nth-child(1) { inset: 0; width: 100%; height: 100%; opacity: var(--fx-op, .85);
  background: conic-gradient(from 180deg at 50% 70%,
    #fafafa, #eec32d 72deg, #ec4b4b 144deg, #709ab9 216deg, #4dffbf 288deg, #fafafa 360deg);
  --grain-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='256' height='256'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='256' height='256' filter='url(%23n)'/%3E%3C/svg%3E");
  -webkit-mask:
    radial-gradient(circle at 50% 50%, #000 2px, transparent 2.5px) 50% 50% / var(--grain-size) var(--grain-size),
    var(--grain-noise) 256px 50% / 256px 256px;
  mask:
    radial-gradient(circle at 50% 50%, #000 2px, transparent 2.5px) 50% 50% / var(--grain-size) var(--grain-size),
    var(--grain-noise) 256px 50% / 256px 256px;
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
  animation: bgfx-flicker 20s infinite linear; }
.bgfx[data-effect="grain"] > span:nth-child(n+2) { display: none; }
@keyframes bgfx-flicker { to { -webkit-mask-position: 50% 50%, 0 50%; mask-position: 50% 50%, 0 50%; } }

/* ── Выбор эффекта в настройках: плитки с живым мини-превью ────────── */
.bgfx-pick { display: grid; grid-template-columns: repeat(auto-fill, minmax(126px, 1fr)); gap: 10px; }
.bgfx-opt { display: flex; flex-direction: column; gap: 7px; padding: 7px; cursor: pointer; text-align: left;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: var(--font); color: var(--text-dim); transition: border-color .12s, background .12s; }
.bgfx-opt:hover { border-color: var(--accent-border); }
.bgfx-opt.active { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 7%, var(--surface-2)); }
.bgfx-opt.active .bgfx-opt-name { color: var(--accent-text); }
.bgfx-opt-name { font-size: .8em; font-weight: 600; }
.bgfx-opt-hint { font-size: .7em; color: var(--muted); line-height: 1.35; }
/* Плитка = тот же слой, только маленький: шаг узоров уменьшен под размер */
.bgfx-tile { position: relative; height: 66px; border-radius: 7px; overflow: hidden;
  background: var(--bg); border: 1px solid var(--border-2); }
.bgfx-tile.bgfx[data-effect="grid"] { --step: 14px; }
.bgfx-tile.bgfx[data-effect="dots"] { --step: 10px; }
.bgfx-tile.bgfx[data-effect="weave"] { --s: 42px; }
.bgfx-tile.bgfx[data-effect="grain"] { --grain-size: 12px; }
.bgfx-tile.bgfx[data-effect="stars"] > span { background-size: 120px 120px; }
.bgfx-tile.bgfx[data-effect="none"]::after { content: '—'; position: absolute; inset: 0;
  display: grid; place-items: center; color: var(--muted); font-size: .9em; }

/* Настройки эффекта: цвета, прозрачность, скорость */
.bgfx-cfg { display: flex; flex-wrap: wrap; gap: 12px 22px; align-items: center; margin-top: 14px; }
.bgfx-cfg-item { display: flex; align-items: center; gap: 9px; font-size: .82em; color: var(--text-dim); }
.bgfx-cfg-item .clr-swatch { width: 30px; height: 30px; }
.bgfx-cfg input[type="range"] { width: 130px; accent-color: var(--accent); }
.bgfx-cfg-val { min-width: 44px; font-variant-numeric: tabular-nums; color: var(--muted); font-size: .92em; }
.bgfx-note { display: flex; align-items: center; gap: 8px; margin-top: 12px; }

/* Плашка «что вы сейчас редактируете» — школа или личное (частая путаница) */
.clr-scope-banner { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
  margin: 0 0 14px; padding: 9px 12px; border-radius: var(--radius-sm); font-size: .84em;
  border: 1px solid var(--accent-dim); color: var(--accent-text);
  background: color-mix(in srgb, var(--accent) 7%, transparent); }
.clr-scope-banner span { color: var(--text-dim); }
.clr-scope-banner.personal { border-color: var(--border-2); color: var(--text);
  background: var(--surface-2); }

/* Слой внутри превью интерфейса — под карточками */
.tprev-body { position: relative; }
.tprev-body > .tprev-card { position: relative; z-index: 1; }

/* ══════════════════════════════════════════════════════════════════
   Интерактивная доска (board). Намеренно тёмная в обеих темах —
   как видеозвонок; literal-цвета здесь НЕ идут через палитру школы.
   ══════════════════════════════════════════════════════════════════ */
body.board-view { overflow: hidden; }
.board-wrap {
  position: fixed; inset: 0; z-index: 50;
  display: flex; flex-direction: column;
  background: #0e1014; color: #e6ecf5;
  font-family: Inter, system-ui, sans-serif;
}
/* Встроенная в комнату класса доска: не на весь экран, а в левой панели комнаты
   (правая колонка со звонком и чатом остаётся рядом). Занимает почти всю высоту
   вьюпорта под шапкой; скруглена, чтобы читаться как отдельный инструмент. */
.board-wrap.embed {
  position: relative; inset: auto; z-index: 1;
  height: calc(100vh - 150px); min-height: 460px;
  border-radius: 14px; overflow: hidden;
  border: 1px solid rgba(255,255,255,0.08);
}
.room-board-host { flex: 1 1 auto; min-width: 0; padding: 0; }

/* Кнопки управления доской (от комнаты) в топбаре доски — тёмная тема доски.
   Компактные (иконка + короткая подпись); nowrap, чтобы не «съезжали» на вторую строку. */
.board-room-ctrls { display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; }
.board-rc-btn {
  display: inline-flex; align-items: center; gap: 5px; cursor: pointer; flex: 0 0 auto;
  height: 30px; padding: 0 9px; border-radius: 8px; font-size: 12.5px; white-space: nowrap;
  background: #232a34; color: #cdd6e3; border: 1px solid rgba(255,255,255,0.12);
}
.board-rc-btn:hover { background: #2b333f; color: #fff; }
.board-rc-btn.primary { background: #4dabf7; color: #071018; border-color: #4dabf7; }
.board-rc-btn.primary:hover { background: #6cb9f8; }
.board-rc-btn.active { background: #4dabf7; color: #071018; border-color: #4dabf7; }
.board-rc-ico { font-size: 14px; line-height: 1; }
.board-rc-ico.ico svg { width: 14px; height: 14px; display: block; }
/* Фон-кнопка в панели инструментов доски (переехала из топбара). Подпись «Фон» рядом
   со свотчем — без неё было неясно, что кружок красит именно фон холста, а не что-то
   ещё. Кнопка шире обычных квадратных инструментов — панель авто-подстроится (flex
   column + align-items:center у .board-toolbar). */
/* «Фон» — кнопка с подписью, шире квадратных инструментов (специфичность .board-tool.…
   выше одиночного .board-tool ниже по каскаду, иначе та переопределила бы ширину). */
.board-tool.board-bg-tool {
  width: auto; height: 28px; padding: 0 9px; border-radius: 8px;
  display: inline-flex; align-items: center; gap: 6px;
}
.board-bg-tool .board-bg-swatch { width: 16px; height: 16px; border-radius: 5px; border: 1px solid rgba(255,255,255,0.25); display: block; flex: 0 0 auto; }
.board-bg-label { font-size: 11px; color: #cdd6e3; white-space: nowrap; }
.board-bg-tool:hover .board-bg-label { color: #fff; }
/* Ориентация — теперь БЕЗ подписи, обычный квадратный инструмент (наследует .board-tool). */
.board-orient-tool .ico svg { width: 16px; height: 16px; }

/* Маленький кружок-палитра рядом с настройками объекта (текст/фигура) — быстрая
   смена цвета именно выделенного объекта, без похода к общей палитре слева. */
.board-obj-swatch {
  width: 20px; height: 20px; border-radius: 50%; flex: 0 0 auto; cursor: pointer;
  border: 2px solid rgba(255,255,255,0.3); padding: 0;
}
.board-obj-swatch:hover { border-color: rgba(255,255,255,0.6); }

/* «Указать» — плавает у угла рамки выделенного объекта (не в фиксированной панели),
   доступна только в «режиме урока» (см. board.js updatePointBtn). */
.board-point-fab {
  position: absolute; z-index: 6; width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; padding: 0; cursor: pointer;
  background: rgba(20,23,29,0.94); border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 4px 14px rgba(0,0,0,0.45); color: #cdd6e3;
}
.board-point-fab:hover { background: #232a34; color: #fff; border-color: #4dabf7; }
.board-point-fab .ico svg { width: 16px; height: 16px; }

/* Синхронный полноэкранный режим доски: доска на весь экран, чат+звонок «парят» поверх.
   Управление доской остаётся в её топбаре (см. board-room-ctrls). z-index доски НИЖЕ
   модалок (100) — иначе выбор доски/картинки открывался бы ЗА доской в этом режиме. */
body.room-board-fs { overflow: hidden; }
body.room-board-fs .board-wrap.embed {
  position: fixed; inset: 0; z-index: 90;
  height: auto; min-height: 0; border-radius: 0; border: none;
}
/* room-right становится «прозрачным» оверлеем — держит только область звонка (кнопка
   «Позвонить» + пилюля «Ожидание ученика»). Размещаем В НИЗУ СЛЕВА — вверху справа
   уже топбар доски (зум/действия), там кнопки наезжали друг на друга («ХАОС»). Чат
   «выпадает» отдельным плавающим виджетом (см. ниже). */
body.room-board-fs .room-right {
  position: fixed; left: 14px; bottom: 14px; right: auto; top: auto; z-index: 95;
  width: auto; max-width: 340px; height: auto; min-height: 0; max-height: none;
  background: none; gap: 8px; pointer-events: none;
  /* ⚠️ Рамку и поля мобильной доки (@media 900: .room-right — панель у низа экрана с
     border-top) снимаем ЯВНО: здесь контейнер прозрачный и висит поверх холста, и та
     рамка превращалась в тёмную линию, оторванно висящую над плашкой «Ученик офлайн».
     Та же правка уже сделана для звонка (body.call-active … .room-right). */
  border-top: none; padding: 0;
}
body.room-board-fs .room-top-row { pointer-events: auto; flex-wrap: wrap; }   /* кнопка «Позвонить» кликабельна */
/* Пилюля «Ожидание участника» (room-wait) в обычной раскладке растягивается на всю строку
   рядом с кнопкой «Позвонить» (flex:1 1 auto) с почти прозрачным фоном (8%) — задумано для
   фона комнаты, где рядом есть другая chrome. Плавающей поверх тёмного холста доски (когда
   партнёр офлайн и кнопки звонка нет — пилюля висит в одиночестве) та же прозрачная широкая
   полоса читалась как «оторванная линия» сверху и текст отдельно снизу, а не единый бейдж.
   По ширине под контент + непрозрачная тёмная подложка с блюром — тот же язык, что у
   остальных плавающих виджетов доски (.room-chat-move/.call-ctrls-mini). */
body.room-board-fs .room-wait {
  flex: 0 1 auto; width: fit-content; max-width: 100%;
  /* ⚠️ Здесь фон НЕ по --wait-c (базовое правило .room-wait теперь сплошная заливка сигнальным
     цветом) — плавающая поверх холста доски пилюля держит тот же нейтральный тёмный фон, что и
     остальные плавающие виджеты (.room-chat-move/.call-ctrls-mini), а светофор возвращаем через
     цвет текста и кнопок ниже, иначе на холсте всех цветов сразу пилюля спорила бы с рисунком. */
  background: rgba(20, 20, 20, .78); backdrop-filter: blur(4px);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .4);
  color: var(--wait-c);
}
body.room-board-fs .room-wait-call,
body.room-board-fs .room-wait-noshow-btn {
  color: var(--wait-c); background: color-mix(in srgb, var(--wait-c) 14%, transparent);
  border-color: color-mix(in srgb, var(--wait-c) 45%, transparent);
}
body.room-board-fs .room-wait-call:hover:not(:disabled),
body.room-board-fs .room-wait-noshow-btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--wait-c) 24%, transparent);
}

/* Плавающий чат — таскается за круглую кнопку-«крест» слева сверху (attachChatDrag),
   сворачивается кнопкой ⟩ справа. Верхней панели-грипа НЕТ (забирала место у ленты).
   Ширина −10%, высота по умолчанию. Позиция по умолчанию — правый нижний угол. */
body.room-board-fs .room-chat {
  pointer-events: auto;
  position: fixed; right: 16px; bottom: 16px; left: auto; top: auto;
  width: 288px; height: 360px; min-height: 0; max-height: none;
  /* z 96: выше доски (90), но НИЖЕ модалок (100) — выбор доски/картинки перекрывает чат. */
  z-index: 96; border-radius: 14px; box-shadow: 0 14px 44px rgba(0,0,0,.55);
}
/* Свёрнутый чат (облачко) ВСЕГДА в правом нижнем углу экрана, где бы ни таскали
   открытый чат. `!important` перебивает inline-координаты от перетаскивания; при
   разворачивании (класс .collapsed исчезает) inline-позиция снова в силе. */
body.room-board-fs .room-chat.collapsed {
  width: auto; height: auto; box-shadow: none;
  left: auto !important; top: auto !important; right: 20px !important; bottom: 20px !important;
}
/* В режиме доски чат-на-весь-экран не нужен. «Язычок» высоты (room-chat-resize) —
   нужен на ПК (тянем мышью за него), но на мобиле — просто непонятная серая полоса
   под полем ввода (тач не ассоциирует тонкую полоску с ресайзом) — там чат и так имеет
   разумную дефолтную высоту (см. @media max-width:900px ниже), ручка там скрыта. */
body.room-board-fs .room-chat-max { display: none; }
@media (min-width: 901px) {
  /* «Язычок» компактнее (меньше отступа снизу) — та же логика, что в базовом правиле
     выше: штрихи приподняты над нижним краем, сверху отступа нет. */
  body.room-board-fs .room-chat:not(.collapsed) .room-chat-resize { display: flex; padding: 0 0 2px; gap: 2px; }
}
/* Круглая кнопка перемещения (крест-стрелки) слева сверху — видна только в режиме доски.
   Тот же вид/цвет, что у кнопки «свернуть» (.room-chat-min). ⚠️ Цвет/фон задаются на
   САМОМ классе (низкая специфичность), а показ (display) — на board-fs-селекторе: иначе
   тёмный фон в high-specificity правиле перебивал бы светлую тему ([data-theme=light]
   .room-chat-move) и кнопка оставалась чёрной — это и был баг со скриншота. */
.room-chat-move {
  display: none; align-items: center; justify-content: center;
  position: absolute; top: 8px; left: 8px; z-index: 6; width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--border-2); background: rgba(20,20,20,.7); color: var(--text-dim);
  cursor: move; touch-action: none; user-select: none; -webkit-user-select: none; backdrop-filter: blur(4px);
}
body.room-board-fs .room-chat:not(.collapsed) .room-chat-move { display: flex; }
.room-chat-move:hover { color: var(--text); border-color: var(--accent-border); }
.room-chat-move svg { width: 18px; height: 18px; flex: 0 0 auto; }
/* Кнопка сворачивания — того же размера (высота), что и кнопка перемещения. */
/* ⚠️ right держим таким же, как в обычном режиме: полоса прокрутки ленты есть и здесь,
   и кнопка не должна на неё наезжать (8px клали её ровно на ползунок). */
body.room-board-fs .room-chat-min { top: 8px; right: 15px; width: 34px; height: 34px; font-size: 1.15em; }
/* Лента чата — верхний отступ под кнопки в углах; лишний padding-top (10px из базы)
   не нужен, содержимое кнопок и так не липнет.
   ⚠️ flex/max-height здесь ОБЯЗАТЕЛЬНЫ — они снимают мобильное правило
   `@media(max-width:900px){.room-chat-feed{flex:0 0 auto;max-height:150px}}`. То правило
   написано для ОБЫЧНОЙ мобильной раскладки, где чат — дока высотой ПО КОНТЕНТУ
   (`height:auto`): там ограничение ленты просто делает доку компактной. Но в режиме доски
   у чата ФИКСИРОВАННАЯ высота (min(52vh,340px)), а лента с `flex:0 0 auto` расти
   отказывается → лента 150px + строка ввода ~50px внутри контейнера 340px оставляли
   ~140px МЁРТВОГО МЕСТА ПОД полем ввода. Это и была жалоба «куча пустого места под чатом»
   (дважды промахнулся мимо: ни ручка ресайза, ни выравнивание сообщений тут ни при чём). */
body.room-board-fs .room-chat-feed { padding: 44px 12px 8px; flex: 1 1 auto; max-height: none; }
/* Нижняя панель (ввод + скрепка + отправить) компактнее на ~15% — занимала много места. */
body.room-board-fs .room-chat-input { padding: 8px 10px; gap: 6px; }
body.room-board-fs .room-chat-input .input { padding: 8px 12px; }
body.room-board-fs .room-chat-clip { width: 32px; height: 32px; }
/* Селектор ученика (если несколько) идёт первым — ему нужен отступ под круглые кнопки;
   тогда лента ниже возвращает обычный верхний отступ. */
body.room-board-fs .room-chat-selrow { padding: 44px 10px 6px; }
body.room-board-fs .room-chat-selrow ~ .room-chat-feed { padding-top: 8px; }

/* Видео-виджет звонка в режиме доски (мобиле) — вдвое меньше: на весь экран доски
   он слишком крупный поверх контента, а руками не уменьшить (ручка ресайза есть
   только у ПК-варианта .call-square, не у .mobile). Панель кнопок под видео
   уменьшена пропорционально. */
body.room-board-fs .call-square.mobile { width: 75px; height: 75px; border-radius: 10px; }
body.room-board-fs .call-widget:not(.desktop) .call-ctrls-mini { gap: 5px; padding: 4px; }
body.room-board-fs .call-widget:not(.desktop) .call-btn { width: 26px; height: 26px; }
body.room-board-fs .call-widget:not(.desktop) .call-btn svg { width: 13px; height: 13px; }
body.room-board-fs .call-widget:not(.desktop) .call-timer { font-size: .58em; padding: 1px 6px; }

/* Узкая встроенная доска: топбар не помещает подписи — оставляем только иконки кнопок.
   ⚠️ Кроме кнопок БЕЗ иконки (.primary — сейчас это только «Позвать»): у неё скрывать
   подпись нельзя — кнопка станет пустой (был реальный баг: пустая синяя пилюля в топбаре
   на мобиле, ни иконки, ни текста). */
@media (max-width: 1200px) {
  .board-room-ctrls .board-rc-btn:not(.primary) .board-rc-lbl { display: none; }
  .board-rc-btn { padding: 0 8px; }
}
@media (max-width: 900px) {
  .board-wrap.embed { height: 72vh; min-height: 380px; }
  .room-board-host.room-book-pane { padding: 0; }
  body.room-board-fs .room-right { left: 8px; bottom: 8px; right: auto; top: auto; }
  /* Плавающий чат уже экрана на мобиле — не перекрывает всю доску (ещё −12% по
     ширине: на телефоне он закрывал больше половины холста). Высота ещё −23%
     (52vh/340 → 40vh/260): владелец продукта попросил больше «воздуха» над чатом —
     видно больше холста доски позади плавающей карточки. */
  body.room-board-fs .room-chat { width: min(68vw, 240px); height: min(40vh, 260px); right: 10px; bottom: 10px; }
}
.board-topbar {
  position: relative; flex: 0 0 auto; height: 52px;
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 14px; gap: 12px;
  background: #14171d; border-bottom: 1px solid rgba(255,255,255,0.08);
}
.board-tb-left, .board-tb-right { display: flex; align-items: center; gap: 12px; min-width: 0; }
/* Масштаб — по центру рамки топбара, не привязан к левой/правой группе кнопок. */
.board-tb-center {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  display: flex; align-items: center; pointer-events: none;
}
.board-tb-center > * { pointer-events: auto; }
.board-title { font-weight: 600; font-size: 15px; color: #e6ecf5; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.board-presence { display: flex; gap: 6px; }
.board-dot {
  width: 26px; height: 26px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; color: #0e1014;
  box-shadow: 0 0 0 2px #14171d;
}
.board-follow { background: #232a34; color: #cdd6e3; border: 1px solid rgba(255,255,255,0.12); }
.board-follow.active { background: #4dabf7; color: #071018; border-color: #4dabf7; }
.board-follow:disabled { opacity: .45; cursor: not-allowed; }

.board-zoom { display: flex; align-items: center; gap: 4px; background: #232a34; border-radius: 9px; padding: 3px; }
.board-zbtn {
  border: 0; background: transparent; color: #cdd6e3; cursor: pointer;
  width: 28px; height: 28px; border-radius: 7px; font-size: 16px; line-height: 1;
}
.board-zbtn:hover { background: rgba(255,255,255,0.08); }
.board-zfit { width: auto; padding: 0 10px; font-size: 12px; }
.board-zoom-val { font-size: 12px; color: #9aa6b6; min-width: 40px; text-align: center; }

.board-stage { position: relative; flex: 1 1 auto; overflow: hidden; touch-action: none; cursor: default; }

/* Левая плавающая панель инструментов. max-height/overflow — страховка на случай
   ОЧЕНЬ короткого экрана; в обычном виде (палитра — сетка 2×5, максимум 10 цветов)
   содержимое и так помещается без скролла — см. .board-colors ниже.
   ⚠️ Панель сужена (padding 6→5, кнопки 32→30, зазоры 4→3): на телефоне она отъедала
   заметную полосу от холста — «немного уже» без потери кликабельности (30px всё ещё
   выше минимума тач-таргета внутри самой доски, где палец и так точнее целится). */
.board-toolbar {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%); z-index: 5;
  display: flex; flex-direction: column; align-items: center; gap: 3px; padding: 5px;
  background: rgba(20,23,29,0.94); border: 1px solid rgba(255,255,255,0.09);
  border-radius: 13px; box-shadow: 0 8px 30px rgba(0,0,0,0.45);
  max-height: calc(100% - 24px); overflow-y: auto;
}
/* Тело панели (всё, кроме кнопки «свернуть») — прячется в свёрнутом состоянии. */
.board-tb-body { display: flex; flex-direction: column; align-items: center; gap: 5px; }
.board-toolbar.collapsed .board-tb-body { display: none; }
/* Кнопка «свернуть/развернуть» — компактная полоска сверху панели. Цвет — акцентный
   синий (был серым #8f9aab): серую кнопку среди ярких иконок инструментов не замечали
   вовсе, а это единственный путь свернуть панель на узком экране. */
.board-tb-collapse {
  width: 32px; height: 22px; border-radius: 7px; border: 0; cursor: pointer;
  background: transparent; color: #4dabf7; display: flex; align-items: center; justify-content: center;
}
.board-tb-collapse:hover { background: rgba(77,171,247,0.16); color: #fff; }
.board-tb-collapse .ico svg { width: 16px; height: 16px; }
/* Инструменты — в ДВЕ колонки (как палитра цветов ниже): панель тулбара узкая и
   высокая, две колонки экономят вертикаль и визуально рифмуются с сеткой цветов. */
.board-tools { display: grid; grid-template-columns: repeat(2, 30px); gap: 2px; justify-content: center; }
.board-tool {
  width: 30px; height: 30px; border-radius: 8px; border: 0; cursor: pointer;
  background: transparent; color: #cdd6e3; font-size: 15px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.board-tool:hover { background: rgba(255,255,255,0.09); color: #fff; }
.board-tool.active { background: #4dabf7; color: #071018; }
/* Все инструменты — теперь одни и те же векторные иконки (icons.js), одного размера
   и толщины обводки; раньше половина рисовалась текстовыми глифами шрифта ОС
   (▹✋T□◯△╱↗) и визуально не совпадала с image/video/audio. */
.board-tool .ico svg { width: 15px; height: 15px; }
/* Указка — красный светящийся глиф (теперь SVG-точка, не текстовый «●»): text-shadow
   на векторную фигуру не действует, свечение даёт drop-shadow на фильтре. */
.board-tool-laser { color: #ff5a5a; filter: drop-shadow(0 0 4px rgba(255,60,60,0.7)); }
.board-tool-laser.active { background: #ff5a5a; color: #fff; filter: none; }
/* Инструменты создания приглушаются в режиме «только просмотр» (см. canEdit) —
   кнопка остаётся кликабельной (тост объясняет), просто визуально неактивна. */
.board-tool-disabled { opacity: .35; }
.board-tb-sep { height: 1px; background: rgba(255,255,255,0.1); margin: 2px 0; align-self: stretch; }
/* Сетка 2×5 — палитра лимитирована 10 цветами (см. MAX_PALETTE_COLORS в model.js),
   поэтому ровно 2 столбца всегда помещаются без переполнения по ширине панели
   (панель тулбара вертикальная — узкая и высокая, а не широкая). */
.board-colors { display: grid; grid-template-columns: repeat(2, 14px); grid-auto-flow: row; gap: 4px; justify-content: center; }
.board-swatch {
  width: 14px; height: 14px; border-radius: 50%; cursor: grab; touch-action: none;
  border: 2px solid transparent; padding: 0; transition: transform .1s ease, opacity .1s ease;
}
.board-swatch:active { cursor: grabbing; }
.board-swatch.active { border-color: #fff; box-shadow: 0 0 0 2px #4dabf7; }
.board-swatch-add {
  background: transparent; color: #cdd6e3; border: 1px dashed rgba(255,255,255,0.35);
  display: flex; align-items: center; justify-content: center; font-size: 13px; line-height: 1;
  width: 14px; height: 14px; cursor: pointer;
}
.board-swatch-add:hover { border-color: #4dabf7; color: #fff; }

/* Настройки ручки (толщина + цвет) — выезжают справа от панели инструментов напротив
   кнопки «Ручка», пока она выбрана (left/top ставит board.js positionPenPanel). Цвета —
   та же палитра, что в левой панели, только свотчи крупнее: здесь по ним целятся чаще. */
.board-pen-panel {
  position: absolute; z-index: 6;
  display: flex; flex-direction: column; gap: 6px; padding: 8px 10px 10px;
  background: rgba(20,23,29,0.96); border: 1px solid rgba(255,255,255,0.10);
  border-radius: 13px; box-shadow: 0 12px 36px rgba(0,0,0,0.5);
}
.board-pen-label { font-size: 11px; color: #9aa6b6; }
.board-pen-widths { display: flex; gap: 3px; }
.board-pen-w {
  width: 30px; height: 30px; border-radius: 8px; border: 0; padding: 0; cursor: pointer;
  background: transparent; display: flex; align-items: center; justify-content: center;
}
.board-pen-w:hover { background: rgba(255,255,255,0.09); }
.board-pen-w.active { background: rgba(77,171,247,0.18); box-shadow: inset 0 0 0 1.5px #4dabf7; }
/* Кружок-образец толщины красится текущим цветом; тонкое кольцо — чтобы тёмный цвет
   не пропадал на тёмной панели. */
.board-pen-dot { display: block; border-radius: 50%; box-shadow: 0 0 0 1px rgba(255,255,255,0.22); }
.board-pen-colors { display: grid; grid-template-columns: repeat(5, 20px); gap: 7px; }
.board-pen-swatch {
  width: 20px; height: 20px; border-radius: 50%; padding: 0; cursor: pointer;
  border: 2px solid transparent;
}
.board-pen-swatch.active { border-color: #fff; box-shadow: 0 0 0 2px #4dabf7; }
.board-pen-add {
  background: transparent; color: #cdd6e3; border: 1px dashed rgba(255,255,255,0.35);
  display: flex; align-items: center; justify-content: center; font-size: 13px; line-height: 1;
}
.board-pen-add:hover { border-color: #4dabf7; color: #fff; }

/* Текстура фона — панель рядом с панелью инструментов (основа — .board-pen-panel):
   плитки штампов (встроенные пиксельные + библиотека школы + «добавить PNG») и
   ползунки размера/прозрачности/поворота/расстояния. Доска всегда тёмная — литералы. */
.board-tex-panel { width: 244px; gap: 8px; }
.board-tex-head { display: flex; align-items: center; justify-content: space-between; }
.board-tex-title { font-size: 12.5px; font-weight: 600; color: #e6ecf5; }
.board-tex-head .board-act { min-width: 26px; height: 26px; }
.board-tex-head .ico svg { width: 14px; height: 14px; }
.board-tex-grid { display: grid; grid-template-columns: repeat(5, 40px); gap: 6px; justify-content: space-between; }
/* ⚠️ Размер плитки — явный, без aspect-ratio: у <button> в ячейке 1fr Safari брал ширину
   по содержимому, плитки распирали сетку и наезжали на ползунки. */
.board-tex-tile {
  -webkit-appearance: none; appearance: none; box-sizing: border-box;
  position: relative; width: 40px; height: 40px; min-width: 0; margin: 0;
  border-radius: 9px; padding: 6px; cursor: pointer;
  background: #232a34; border: 1.5px solid transparent; color: #9aa6b6;
  display: flex; align-items: center; justify-content: center;
}
.board-tex-tile:hover { border-color: rgba(255,255,255,0.25); }
.board-tex-tile.active { border-color: #4dabf7; box-shadow: 0 0 0 2px rgba(77,171,247,0.25); }
.board-tex-tile img { width: 100%; height: 100%; object-fit: contain; display: block; }
.board-tex-tile .board-tex-pixel { image-rendering: pixelated; }
.board-tex-tile .ico svg { width: 16px; height: 16px; }
.board-tex-add { border: 1.5px dashed rgba(255,255,255,0.3); background: transparent; }
.board-tex-add:hover { border-color: #4dabf7; color: #fff; }
.board-tex-add .spinner { width: 16px; height: 16px; }
.board-tex-del {
  position: absolute; top: -5px; right: -5px; width: 16px; height: 16px; border-radius: 50%;
  background: #3a4350; color: #fff; display: none; align-items: center; justify-content: center;
}
.board-tex-del .ico svg { width: 10px; height: 10px; }
.board-tex-tile:hover .board-tex-del { display: flex; }
@media (hover: none) { .board-tex-del { display: flex; } }
.board-tex-settings { display: flex; flex-direction: column; gap: 4px; padding-top: 4px; border-top: 1px solid rgba(255,255,255,0.08); }
.board-tex-row { display: grid; grid-template-columns: 84px 1fr 38px; align-items: center; gap: 6px; }
.board-tex-range.board-tex-range { width: 100%; min-width: 0; margin: 0; }   /* ширина — по колонке ряда, остальное — общий слайдер доски */
.board-tex-val { font-size: 11px; color: #cdd6e3; text-align: right; font-variant-numeric: tabular-nums; }
/* Кнопка «Текстура» в панели инструментов: в квадратике — текущий штамп (или пусто). */
.board-tex-swatch { overflow: hidden; display: flex !important; align-items: center; justify-content: center; background: #232a34; }
.board-tex-swatch img { width: 12px; height: 12px; object-fit: contain; image-rendering: pixelated; }
.board-tex-swatch-empty { background: repeating-linear-gradient(45deg, #2b323d 0 3px, #1b2028 3px 6px); }

/* Верхне-правая плавающая панель действий — над ВЫДЕЛЕННЫМ ОБЪЕКТОМ (тумблер
   «редактирование ученику» отсюда уехал в топбар, см. .board-edit-toggle ниже).
   Теперь ДВЕ части: .board-act-body (сами кнопки, в один ряд без переноса — стало
   ощутимо уже без переехавшего тумблера) + .board-act-collapse (сворачивает body,
   тот же приём, что у левой панели). Обёртка больше не тянется на 60vw и не
   переносится — весь набор помещается в одну строку на реалистичной ширине экрана. */
.board-actions {
  position: absolute; right: 12px; top: 12px; z-index: 5;
  display: flex; align-items: center; gap: 4px; padding: 5px;
  background: rgba(20,23,29,0.94); border: 1px solid rgba(255,255,255,0.09);
  border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.45);
}
.board-act-body { display: flex; align-items: center; gap: 3px; }
.board-actions.collapsed .board-act-body { display: none; }
.board-act-collapse {
  width: 26px; height: 26px; border-radius: 7px; border: 0; cursor: pointer; flex: 0 0 auto;
  background: transparent; color: #4dabf7; display: flex; align-items: center; justify-content: center;
}
.board-act-collapse:hover { background: rgba(77,171,247,0.16); color: #fff; }
.board-act-collapse .ico svg { width: 15px; height: 15px; }
.board-act {
  min-width: 30px; height: 30px; border-radius: 8px; border: 0; cursor: pointer;
  background: transparent; color: #cdd6e3; font-size: 15px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center; padding: 0 5px;
}
.board-act:hover { background: rgba(255,255,255,0.09); color: #fff; }
.board-act:disabled { opacity: .35; cursor: default; }
.board-act-danger:hover { background: rgba(255,80,80,0.22); color: #ff9a9a; }
.board-act .ico svg { width: 15px; height: 15px; }
.board-act-sep { width: 1px; height: 20px; background: rgba(255,255,255,0.12); margin: 0 2px; }

/* Тумблер «Разрешить редактирование ученику» — теперь В ТОПБАРЕ (board-tb-right,
   рядом с присутствием и «Закрыть»), а не в панели действий: это настройка ДОСКИ
   целиком, а не команда над выделенным объектом, и в топбаре достаточно места для
   подписи «Ученик:» вместо тесной тройки иконок. Карандаш крупнее соседних элементов
   топбара — он и объясняет, что кнопка про ПРАВКУ. */
.board-edit-toggle { gap: 6px; }
.board-edit-toggle .board-edit-pen svg { width: 17px; height: 17px; }
/* ⚠️ Активное состояние несёт ЧЕКБОКС, а не заливка кнопки: сплошной синий фон на
   кнопке с подписью читался бы как «нажата», а не как «включено у собеседника». */
.board-edit-toggle:not(.active) { opacity: .65; }
.board-check {
  position: relative; flex: 0 0 auto; width: 14px; height: 14px;
  border: 1.5px solid rgba(255,255,255,0.45); border-radius: 4px;
}
.board-edit-toggle.active .board-check { background: #4dabf7; border-color: #4dabf7; }
/* Галочка — уголок из двух границ (ещё одна иконка ради 8 пикселей не нужна). */
.board-edit-toggle.active .board-check::after {
  content: ''; position: absolute; left: 3.5px; top: 0.5px; width: 4px; height: 8px;
  border: solid #071018; border-width: 0 2px 2px 0; transform: rotate(42deg);
}

/* Инлайн-редактор текста поверх canvas */
.board-textedit {
  position: absolute; z-index: 6; margin: 0; resize: none; overflow: hidden;
  background: rgba(20,30,45,0.35); border: 1px solid #4dabf7; border-radius: 6px;
  color: #f1f3f5; font-family: Inter, system-ui, sans-serif; line-height: 1.25;
  outline: none; box-shadow: 0 0 0 3px rgba(77,171,247,0.25);
}

/* Модалка секций */
.board-sec-list { display: flex; flex-direction: column; gap: 8px; transition: opacity .15s ease; }
.board-sec-list.board-sec-list-off { opacity: .45; pointer-events: none; }
.board-sec-row { display: flex; align-items: center; gap: 6px; }
.board-sec-row .input { flex: 1; }
.board-sec-eye { color: var(--muted); }
.board-sec-eye.active { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, transparent); }
.board-sec-eye .ico svg { width: 14px; height: 14px; }

/* Модалка «Видео по ссылке» (инструмент «Видео») */
.board-video-form .input { width: 100%; margin: 12px 0; }

/* Плеер видео по ссылке (двойной клик по embed-объекту) — iframe площадки в модалке.
   Само окно модалки — без рамки/паддинга (:has переопределяет общий .modal), видео
   заполняет окно от края до края; заголовок — компактной полоской сверху. */
.board-embed-modal { line-height: 0; }
.board-embed-frame { width: 100%; aspect-ratio: 16 / 9; border: 0; border-radius: 0; display: block; }
.modal:has(> .board-embed-modal) { padding: 0; border: none; overflow: hidden; }
.modal:has(> .board-embed-modal) .modal-title { margin: 0; padding: 14px 16px 10px; }

/* Встроенный плеер видео (iframe поверх холста) — играет прямо на доске, следует за
   объектом (позиция выставляется из board.js каждый кадр). Панель мини-кнопок сверху. */
.board-inline-player { position: absolute; z-index: 6; }
.board-inline-frame { width: 100%; height: 100%; border: 0; border-radius: 6px;
  display: block; background: #000; }
.board-video-ctrls { position: absolute; right: 0; display: flex; gap: 6px; z-index: 7; }
.board-vc-btn { width: 30px; height: 30px; display: grid; place-items: center; cursor: pointer;
  border-radius: 7px; border: 1px solid rgba(255,255,255,.14);
  background: rgba(20,22,28,.82); color: #e6ecf5; backdrop-filter: blur(6px);
  transition: background .12s, border-color .12s, color .12s; }
.board-vc-btn:hover { background: rgba(40,44,54,.92); }
.board-vc-btn .ico svg { width: 16px; height: 16px; }
.board-vc-sync.active { background: var(--accent); color: var(--on-accent, #0b0f14); border-color: transparent; }

@media (max-width: 760px) {
  .board-topbar { height: 48px; padding: 0 8px; gap: 8px; }
  .board-title { display: none; }
  /* Масштаб (−/55%/+) на телефоне убран целиком: пинч двумя пальцами уже масштабирует
     холст (см. _beginPinch/_updatePinch), а плашка занимала центр узкого топбара и
     теснила «Доски»/«Позвать» слева и тумблер «Ученик:»/«Закрыть» справа. */
  .board-zoom { display: none; }
  .board-toolbar { left: 6px; padding: 4px; gap: 2px; }
  .board-actions { right: 6px; top: 6px; }
}

/* ── Библиотека досок («Мои доски» + доски класса) ── */
.board-lib-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
.board-lib-card {
  background: var(--surface); border: 1px solid transparent; border-radius: 14px;
  overflow: hidden; display: flex; flex-direction: column;
}
.board-lib-thumb {
  position: relative; height: 140px; border: 0; cursor: pointer; width: 100%;
  background: linear-gradient(135deg, #1a1f28, #12151b); display: flex; align-items: center; justify-content: center;
  padding: 0; overflow: hidden;
}
.board-lib-thumb:hover { filter: brightness(1.12); }
/* Миниатюра = настоящий мини-рендер доски (см. board/preview.js), а не иконка-заглушка.
   position:absolute — НЕ полагаемся на flex-центрирование родителя (.board-lib-thumb):
   canvas — replaced-элемент, и его flex-basis по intrinsic-размеру мог оставлять едва
   заметный зазор по краю вместо полного растяжения на 100% бокса. */
.board-lib-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
/* ⚠️ Лёгкое затемнение поверх мини-рендера доски (просьба владельца продукта): яркие
   превью (цветные доски, картинки в них) иначе перетягивают внимание с остального
   содержимого страницы класса. Исключены превью-доска упражнения (.ex-board-thumb) и
   крупный просмотр чужого шаблона (.board-preview-big) — там доску, наоборот, нужно
   рассмотреть. Заглушка «Нет превью» уехала на ::before, чтобы ::after освободить. */
.board-lib-thumb:not(.ex-board-thumb):not(.board-preview-big)::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg, rgba(6,8,12,0.14), rgba(6,8,12,0.24) 42%, rgba(6,8,12,0.52));
}
/* Не удалось загрузить содержимое (нет доступа/ошибка) — нейтральная заглушка вместо пустоты. */
.board-lib-thumb.no-preview::before {
  content: 'Нет превью'; position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; font-size: .78em; color: var(--muted); z-index: 1;
}
.board-lib-thumb-ico { font-size: 34px; opacity: .8; }   /* legacy: заглушка старых карточек */
.board-lib-count {
  position: absolute; right: 8px; bottom: 8px; font-size: .72em; color: #cdd6e3;
  background: rgba(0,0,0,0.45); padding: 2px 7px; border-radius: 99px;
}
.board-lib-body { padding: 11px 12px 12px; display: flex; flex-direction: column; gap: 6px; }
.board-lib-title { font-weight: 600; font-size: .95em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.board-lib-meta { font-size: .76em; color: var(--muted); }
/* ⚠️ «Открыть» ТЯНЕТСЯ на всю свободную ширину, а не жмётся вправо (как было):
   прижатая кнопка не доходила до левого края карточки, где начинается её название, и
   выглядела съехавшей. Корзина остаётся фиксированной справа. */
.board-lib-actions { display: flex; gap: 6px; align-items: center; margin-top: 4px; }
.board-lib-actions > .btn-primary { flex: 1 1 auto; justify-content: center; }
/* Вертикальный padding — ТОТ ЖЕ, что у .btn-sm (7px): раньше был padding:0 8px — без вертикальных
   полей и без .btn-sm{min-height} (та работает только в мобильном @media ≤760px) кнопка-иконка
   на десктопе схлопывалась вдвое ниже соседней «Открыть» (16px против 32px). Совпадающий
   вертикальный padding при общем line-height/font-size (обе .btn-sm) даёт РОВНО одинаковую
   высоту из коробки — не хрупкое фиксированное число, которое расползлось бы при правке шрифта. */
.board-lib-actions .btn-icon { padding: 7px 9px; }
.board-lib-del:hover { color: var(--danger); }

/* ── Карточка на странице «Материалы → Доски» ──
   Превью КВАДРАТНОЕ. Высоту даём padding-top:100%, а не aspect-ratio — старые мобильные
   WebView его не умеют (см. .ex-board-thumb). Все дети превью позиционированы абсолютно,
   так что нулевая высота контента бокс не ломает. */
.board-lib-card--lib .board-lib-thumb { display: block; height: 0; padding-top: 100%; }
/* Название + тонкий карандаш сразу за ним; корзина — в конце строки. min-width:0 у
   названия, иначе длинное имя растягивает карточку, а не уходит в многоточие. */
.board-lib-head { display: flex; align-items: center; gap: 4px; min-width: 0; }
.board-lib-head .board-lib-title { min-width: 0; }
.board-lib-tool {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0; border: 0; border-radius: 7px; cursor: pointer;
  background: transparent; color: var(--muted); transition: color .15s, background .15s;
}
.board-lib-tool > svg { width: 14px; height: 14px; }
.board-lib-tool:hover { color: var(--text); background: var(--surface-2); }
.board-lib-head .board-lib-del { margin-left: auto; }
.board-lib-head .board-lib-del:hover { color: var(--danger); }
/* Две кнопки одинаковой ширины: flex-basis 0 делит строку поровну независимо от длины текста. */
.board-lib-card--lib .board-lib-actions > .btn { flex: 1 1 0; min-width: 0; justify-content: center; white-space: nowrap; }

/* ⚠️ В ПАНЕЛИ КЛАССА и в модалке «Все доски» карточка лежит на --surface —
   ТОМ ЖЕ фоне, что у самой панели/модалки: миниатюра со своим градиентом это
   маскирует, а ниже неё (название + кнопки) карточка сливалась с фоном
   насквозь, граница пропадала. Внутри этих двух контейнеров карточка уходит
   на тон дальше (--surface-2 — чуть светлее в тёмной теме, чуть темнее в
   светлой), а кнопки — ещё на тон дальше (--border-2), чтобы не слиться уже
   с самой карточкой. На отдельной странице «Мои доски» карточка лежит прямо
   на фоне страницы (--bg) — там такого совпадения нет, трогать незачем. */
.cd-panel .board-lib-card, .modal .board-lib-card { background: var(--surface-2); }
.cd-panel .board-lib-actions .btn-primary, .modal .board-lib-actions .btn-primary,
.cd-panel .board-lib-actions .btn-icon, .modal .board-lib-actions .btn-icon {
  background: var(--border-2);
}
/* «Открыть» тут — НЕ акцентная заливка: она и так растянута на всю ширину
   карточки, и яркий цвет посреди плотной сетки спорит с интерфейсом сильнее,
   чем помогает её найти. Нейтральный серый — тем же приёмом, что у соседней
   кнопки-корзины: обычный цвет текста, рамка и тень акцента сняты. */
.cd-panel .board-lib-actions .btn-primary, .modal .board-lib-actions .btn-primary {
  border-color: transparent; color: var(--text); box-shadow: none;
}
.cd-panel .board-lib-actions .btn-primary:hover, .modal .board-lib-actions .btn-primary:hover {
  filter: brightness(1.12);
}

/* Предпросмотр чужой ПУБЛИЧНОЙ доски (окно «👁 Просмотр») — крупнее карточки,
   не кликабельна (это не кнопка, live-редактирование чужого шаблона не разрешено). */
.board-preview-big { height: 320px; cursor: default; border-radius: 10px; }
.board-preview-big:hover { filter: none; }

.board-pick-classes { display: flex; flex-direction: column; gap: 8px; max-height: 320px; overflow-y: auto; }
.board-pick-class {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 11px 14px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text); cursor: pointer; font-size: .95em; text-align: left;
}
.board-pick-class:hover { border-color: var(--accent); background: var(--surface); }
.board-pick-arrow { color: var(--muted); font-size: .85em; white-space: nowrap; }

/* Переключатель досок класса + кнопка фона — тёмные, литеральные (как .board-follow/.board-zbtn) */
.board-switch-btn, .board-bg-btn {
  background: transparent; color: #cdd6e3; border: 1px solid rgba(255,255,255,0.12);
  border-radius: 8px; padding: 0 10px; height: 30px; display: inline-flex; align-items: center; gap: 6px;
  cursor: pointer; font-size: .85em; white-space: nowrap;
}
.board-switch-btn:hover, .board-bg-btn:hover { background: rgba(255,255,255,0.08); color: #fff; }
.board-switch-btn .ico svg { width: 14px; height: 14px; flex: 0 0 auto; }
.board-bg-swatch { width: 14px; height: 14px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.35); display: inline-block; flex: 0 0 auto; }

/* Контекстная панель текста (размер/начертание/шрифт) — плавает РЯДОМ с самим
   текстом (позиция ставится JS в board.js positionTextToolbar/left/top инлайном),
   а не в фиксированном углу экрана. */
.board-text-toolbar {
  position: absolute; z-index: 6;
  /* Колонка из отдельных строк (.board-tt-row): каждая строка сайзится под свой контент
     независимо, поэтому «застревания» на max-width (старый баг «много пустого места»)
     нет. align-items:stretch + центрирование строк — короткая строка не висит слева. */
  display: flex; flex-direction: column; align-items: stretch; gap: 5px; padding: 6px;
  width: fit-content; max-width: min(94vw, 520px);
  background: rgba(20,23,29,0.96); border: 1px solid rgba(255,255,255,0.10);
  border-radius: 14px; box-shadow: 0 12px 36px rgba(0,0,0,0.5);
}
/* Одна строка контролов внутри панели — сама решает перенос на узких экранах
   (запасной вариант), но обычно умещается в один ряд. */
.board-tt-row { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }

/* Панель настройки ФИГУРЫ — чипы-группы с фоном вместо тонких разделителей-«мусора». */
.board-shape-toolbar .board-tt-row { justify-content: center; gap: 6px; }
.board-shape-toolbar .board-tt-group {
  background: rgba(255,255,255,0.06); border-radius: 10px; padding: 3px; gap: 1px;
}
.board-shape-toolbar .board-act { min-width: 26px; height: 26px; font-size: 14px; }
.board-shape-toolbar .board-act .ico svg,
.board-shape-toolbar .ico svg { width: 14px; height: 14px; }
.board-shape-toolbar .board-obj-swatch { margin: 0 3px; }
.board-shape-toolbar .board-tt-opacity { width: 68px; }
.board-shape-toolbar .board-tt-size { min-width: 20px; font-size: .8em; }
.board-shape-toolbar .board-tt-size-input { width: 36px; height: 24px; }
/* «Голые» иконки-подписи прямо в панели (радиус/контраст/насыщенность) — поясняют,
   что означает соседний ползунок/степпер (раньше «48» без иконки было непонятно). */
.board-text-toolbar .ico { color: #9aa6b6; }
.board-text-toolbar .ico svg { width: 15px; height: 15px; }
.board-tt-size { min-width: 24px; text-align: center; font-size: .85em; color: #cdd6e3; }
/* Поле точного размера текста — вписать число с клавиатуры вместо кликов по A−/A+. */
.board-tt-size-input {
  width: 40px; height: 26px; text-align: center; font-size: .85em;
  background: #232a34; color: #e6ecf5; border: 1px solid rgba(255,255,255,0.12);
  border-radius: 6px; -moz-appearance: textfield;
}
.board-tt-size-input::-webkit-outer-spin-button,
.board-tt-size-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.board-tt-size-input:focus { outline: none; border-color: #4dabf7; }
/* Группы внутри панели формы (ядро/радиус) — независимо скрываются по типу объекта
   (картинка видит только радиус, фигуры — ядро+радиус, линии/стрелки — ядро+концы). */
.board-tt-group { display: flex; align-items: center; gap: 4px; }
.board-act.active { background: #4dabf7; color: #071018; }
.board-tt-italic { font-style: italic; }
.board-tt-underline { text-decoration: underline; }
.board-tt-strike { text-decoration: line-through; }
/* Стикер: строка пресетов цвета заметки (квадратики — как пачка бумажных стикеров)
   и кнопка «A» с полоской — цвет текста (кружок рядом — «свой» цвет заметки). */
.board-tt-sticker-row { justify-content: center; gap: 6px; padding: 2px 0 4px; }
.board-tt-sticker-bg { border-radius: 5px; }
.board-tt-textcolor { flex-direction: column; gap: 1px; font-weight: 700; line-height: 1; }
.board-tt-textcolor-bar { display: block; width: 14px; height: 3px; border-radius: 2px; box-shadow: 0 0 0 1px rgba(255,255,255,0.25); }
.board-tt-font {
  background: #232a34; color: #e6ecf5; border: 1px solid rgba(255,255,255,0.12);
  border-radius: 7px; height: 30px; padding: 0 8px; font-size: .85em; cursor: pointer;
}
/* Слайдеры доски — прозрачность в панели формы и ползунки текстуры фона (тёмная тема,
   literal-цвета). ⚠️ Без своего бегунка Safari рисует родной — крупную белую «таблетку». */
.board-tt-opacity, .board-tex-range { -webkit-appearance: none; appearance: none; width: 84px; height: 4px;
  background: rgba(255,255,255,0.18); border-radius: 99px; outline: none; cursor: pointer; }
.board-tt-opacity::-webkit-slider-thumb, .board-tex-range::-webkit-slider-thumb { -webkit-appearance: none; appearance: none;
  width: 14px; height: 14px; border-radius: 50%; background: #4dabf7; cursor: pointer; }
.board-tt-opacity::-moz-range-thumb, .board-tex-range::-moz-range-thumb {
  width: 14px; height: 14px; border-radius: 50%; background: #4dabf7; border: 0; cursor: pointer; }

@media (max-width: 760px) {
  .board-switch-btn { padding: 0 8px; font-size: .8em; }
  .board-bg-btn span:last-child { display: none; }
}

/* ── Доска-упражнение (SlideType.BOARD) ─────────────────────────────
   Превью во ВСЮ ширину плашки упражнения (как остальные типы), пропорция 3:2 —
   3 по ширине. Высоту задаём padding-top'ом, а НЕ aspect-ratio: тот же довод, что
   у .ex-video выше — старые мобильные WebView (их на рынке РФ ещё много) aspect-ratio
   не умеют, и контейнер схлопнулся бы в нулевую высоту.
   Правила идут ПОСЛЕ .board-lib-thumb (общий блок досок) и потому переопределяют его
   фиксированную высоту 140px — элемент несёт оба класса, чтобы переиспользовать
   готовые стили канваса и заглушки «Нет превью». */
.ex-board {
  display: block; width: 100%; padding: 0; margin: 0; border: 0; background: none;
  text-align: left; color: inherit; font: inherit; cursor: pointer;
  position: relative;   /* якорь для .ex-board-hint (иначе уедет к произвольному предку) */
}
.ex-board[disabled] { opacity: .6; cursor: progress; }
.ex-board-thumb {
  position: relative; width: 100%; height: 0; padding-top: 66.667%;   /* 3:2 — ширина:высота */
  border-radius: 10px; overflow: hidden;
  /* Рамка — inset-тенью, а НЕ border: border добавил бы 2px к высоте бокса и пропорция
     стала бы 1.494 вместо ровно 1.5 (проверено замером). Тень размеров не меняет. */
  box-shadow: inset 0 0 0 1px var(--border);
  background: linear-gradient(135deg, #1a1f28, #12151b);
  transition: box-shadow .15s, filter .15s;
}
.ex-board:hover .ex-board-thumb { box-shadow: inset 0 0 0 1px var(--accent-border); filter: brightness(1.08); }
/* Подсказка поверх превью — появляется на наведении (на тач-устройствах видна всегда:
   там ховера нет, а понять, что плашка кликабельна, всё равно нужно). */
.ex-board-hint {
  position: absolute; left: 50%; bottom: 12px; transform: translateX(-50%);
  padding: 6px 14px; border-radius: 99px; font-size: .8em; white-space: nowrap;
  background: rgba(0,0,0,.6); color: #fff; backdrop-filter: blur(4px);
  opacity: 0; transition: opacity .15s; pointer-events: none;
}
.ex-board:hover .ex-board-hint, .ex-board:focus-visible .ex-board-hint { opacity: 1; }
@media (hover: none) { .ex-board-hint { opacity: .85; } }
/* Кнопки в конструкторе упражнения-доски */
.ex-board-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 4px; }

/* ── Упражнение «Список слов» (SlideType.WORDS) ─────────────────────
   Слово изучаемого языка — перевод, рядом озвучка и «в мой словарь». */
/* font-size: .93em — плашки со словом+переводом были заметно крупнее и выше, чем нужно
   для короткой строки (слово — транскрипция — перевод); чуть мельче текст и меньше
   padding/gap ниже вместе дают ощутимо более компактный список на мобиле. */
.ex-words { display: flex; flex-direction: column; gap: 2px; font-size: .93em; }
.exw-row {
  display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 9px;
  background: var(--surface-2); border: 1px solid transparent;
}
.exw-row + .exw-row { margin-top: 4px; }
.exw-main { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px; flex: 1 1 auto; min-width: 0; }
.exw-word { font-weight: 500; }
.exw-tr { color: var(--muted); font-size: .85em; }
/* Часть речи ("сущ.", "глаг."…) — из синонимов Google (dt=bd), выбирается вместе
   с вариантом перевода при добавлении слова (см. .exw-chip-pos ниже). */
.exw-pos { font-size: .72em; color: var(--muted); padding: 1px 7px; border-radius: 99px;
  border: 1px solid var(--border-2); white-space: nowrap; }
.exw-dash { color: var(--muted); }
.exw-translation { color: color-mix(in srgb, var(--text-dim) 78%, transparent); }
.exw-actions { display: flex; gap: 5px; flex: 0 0 auto; }
.exw-ico {
  width: 27px; height: 27px; border-radius: 7px; display: grid; place-items: center; cursor: pointer;
  background: var(--surface); border: 1px solid var(--border-2); color: var(--text-dim); position: relative;
}
.exw-ico:hover { color: var(--text); border-color: var(--accent-border); }
/* Значок закрашен целиком (ICON_BOOKMARK_FILLED) — уже в словаре. Фон вместо простой
   смены цвета контура: закрашенный контур того же цвета, что фон, был бы почти не
   виден, а сравнение «пусто/заполнено» — это и есть весь сигнал. */
.exw-ico.added { color: var(--accent-text); border-color: var(--accent-border);
  background: color-mix(in srgb, var(--accent) 12%, transparent); cursor: default; }
.exw-ico svg { display: block; }
/* Конструктор: список добавленных слов + варианты перевода */
.exw-edit-list { display: flex; flex-direction: column; gap: 6px; }
.exw-edit-row {
  display: flex; align-items: baseline; gap: 7px; padding: 7px 10px; border-radius: 8px;
  background: var(--surface-2); border: 1px solid transparent;
}
.exw-del {
  margin-left: auto; align-self: center; background: none; border: 0; cursor: pointer;
  color: var(--muted); padding: 2px 4px; border-radius: 6px; flex: 0 0 auto;
}
.exw-del:hover { color: var(--danger); }
.exw-del svg { display: block; width: 15px; height: 15px; }
.exw-variants {
  margin: 4px 0 10px; padding: 10px 12px; border-radius: 10px;
  background: var(--surface-2); border: 1px dashed var(--border-2);
}
.exw-chips { display: flex; flex-wrap: wrap; gap: 7px; margin: 8px 0; }
/* Чип варианта перевода: слово + часть речи под ним (мельче, приглушённая) — помогает
   учителю выбрать нужный смысл слова, не открывая внешний словарь. */
.exw-chip {
  display: inline-flex; flex-direction: column; align-items: center; gap: 1px; line-height: 1.25;
  padding: 6px 14px; border-radius: 14px; cursor: pointer; font-size: .9em;
  background: var(--surface); border: 1px solid var(--border-2); color: var(--text);
}
.exw-chip:hover { border-color: var(--accent-border); background: var(--selected); }
.exw-chip-pos { font-size: .78em; color: var(--muted); }
.exw-own { margin-top: 4px; }

/* ── Упражнения с картинкой (5 видов, один рендерер) ───────────── */
.imgex { display: flex; flex-direction: column; gap: 12px; }
.imgex-task { font-size: 1.02em; color: var(--text); white-space: pre-wrap; }
.imgex-statement { font-size: 1em; color: var(--text); white-space: pre-wrap; }
/* Ширина — во всю карточку упражнения, как у обычного слайда IMAGE (.ex-render .ex-img),
   а не фиксированный узкий блок: иначе картинка «прилипает» слева с пустым полем справа. */
.imgex-img { width: 100%; max-height: 60vh; aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: var(--radius-sm); background: var(--surface-2); display: block; }
.imgex-img-empty { display: flex; align-items: center; justify-content: center; color: var(--muted);
  font-size: .85em; border: 1px dashed var(--border-2); }
.imgex-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.imgex-card { padding: 4px; background: var(--bg); border: 2px solid transparent;
  border-radius: var(--radius-sm); cursor: pointer; transition: border-color .15s; }
.imgex-card:hover { border-color: var(--accent-border); }
.imgex-card.sel { border-color: var(--selected); }
.imgex-card.correct { border-color: var(--correct); }
.imgex-card.wrong { border-color: var(--wrong); }
.imgex-opts { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.imgex-opt { text-align: left; padding: 10px 14px; background: var(--bg); border: 1px solid transparent;
  border-radius: var(--radius-sm); color: var(--text); cursor: pointer; font-size: .95em;
  font-family: var(--font); transition: border-color .15s, background .15s; }
.imgex-opt:hover { border-color: var(--accent-border); }
.imgex-opt.sel { border-color: var(--selected); background: color-mix(in srgb, var(--selected) 8%, transparent); }
.imgex-opt.correct { border-color: var(--correct); background: color-mix(in srgb, var(--correct) 14%, transparent); }
.imgex-opt.wrong { border-color: var(--wrong); background: color-mix(in srgb, var(--wrong) 12%, transparent); }
/* ⚠️ ОДИН ВИД У ВСЕХ ОТВЕТОВ (правило дизайн-системы, решение владельца продукта).
   Плашка варианта, кнопка «верно/неверно», слово из банка, вписываемый пропуск и слот
   для перетаскивания — это ОДНО И ТО ЖЕ по смыслу: то, что ученик выбирает, тащит или
   заполняет. А раскрашены они были по-разному: варианты ответа — чёрной вставкой
   (`--bg`), слова DND — светлой плиткой (`--surface`) с рамкой, банк FILL — пилюлей
   на 99px. Рядом в одном уроке это читалось как два разных интерфейса.
   Общий вид: заливка `--surface-2`, рамка обесцвечена, радиус `--radius-sm`.
   ⚠️ Правило стоит ПОСЛЕ всех своих селекторов НАМЕРЕННО, но держится не на порядке,
   а на специфичности: у состояний (.sel/.selected/.correct/.wrong/:hover/.drop/:focus)
   она выше (0,2,0), поэтому они по-прежнему красят рамку и фон поверх общего вида.
   ⚠️ Слот и слово в нём теперь ОДНОГО цвета — так и задумано: положенное слово читается
   как заполненный пропуск (ровно как в FILL), а не как плитка внутри коробки. */
.choice-opt, .imgex-opt, .imgex-card,
.fill-input, .wb-chip, .dnd-word, .dnd-slot, .match-card {
  background: var(--surface-2);
  border-color: transparent;
  border-radius: var(--radius-sm);
}

.imgex-tf { display: flex; gap: 10px; margin-top: 12px; }
.imgex-tf-btn { flex: 1; text-align: center; font-weight: 500; letter-spacing: .5px; }
.imgex-input, .imgex-area { margin-top: 12px; width: 100%; }
.imgex-count { font-size: .82em; color: var(--muted); margin-top: 6px; }
.imgex-count.ok { color: var(--correct); }
.imgex-reqs { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.imgex-req { font-size: .78em; padding: 3px 9px; border-radius: 999px; color: var(--text-dim);
  background: var(--surface-2); border: 1px solid var(--border-2); }
/* «Проверить» — справа (решение владельца продукта; так же у .choice-multi-bar). */
.imgex-actions { display: flex; justify-content: flex-end; gap: 8px; }
/* Заглушка вида упражнения, которого больше нет (уроки, созданные до его удаления). */
.imgex-legacy { font-size: .88em; color: var(--warn); border: 1px dashed var(--border-2);
  border-radius: var(--radius-sm); padding: 10px 12px; }

/* ── Беседа по картинке (IMAGE с полем talk, views/exercise-edit.js talkPanel) ──
   Вопросы к картинке, слова-опоры и начала фраз под ней. Фон — --surface-2 без рамок
   (правило «без рамок у поверхностей»). Подписей вида вопроса («Опиши», «Совет») нет —
   владелец продукта убрал их как шум. */
.talk { margin-top: 14px; display: flex; flex-direction: column; gap: 12px; }
.talk-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.talk-step { padding: 12px 16px; background: var(--surface-2); border-radius: var(--radius-sm);
  line-height: 1.45; overflow-wrap: anywhere; color: var(--text); }
.talk-sup { padding: 0 2px; }
.talk-sup-title { font-size: .74em; color: var(--muted); text-transform: uppercase;
  letter-spacing: .6px; margin-bottom: 6px; }
.talk-words { display: flex; flex-wrap: wrap; gap: 6px; }
.talk-word { padding: 5px 11px; border-radius: 99px; background: var(--surface-2);
  font-size: .9em; color: var(--text-dim); }
.talk-starters { margin: 0; padding-left: 18px; color: var(--text-dim); line-height: 1.6; }
/* Редактор беседы: номер · вопрос одной растущей строкой · корзина */
.talk-edit { margin-top: 6px; }
.talk-edit-rows { display: flex; flex-direction: column; gap: 8px; margin-bottom: 8px; }
.talk-edit-row { display: grid; grid-template-columns: 22px 1fr auto; gap: 8px; align-items: start; }
.talk-edit-n { color: var(--muted); font-size: .85em; padding-top: 12px; text-align: center; }
.talk-q { min-height: 0; resize: none; overflow: hidden; line-height: 1.45; }

/* ── «Для преподавателя» — пометки к любому упражнению ─────────────
   Показ (.tnote) — сворачиваемый блок под упражнением, только для не-учеников (ученику
   сервер поле не отдаёт). Редактор (.tnote-edit) — такой же сворачиваемый блок в конце
   полей упражнения, раскрыт, если заметка уже есть. */
.tnote { margin-top: 14px; background: var(--surface-2); border-radius: var(--radius-sm);
  padding: 10px 14px; font-size: .88em;
  /* Служебный текст учителя — заметно тусклее вопросов упражнения (иерархия оттенков). */
  color: color-mix(in srgb, var(--text-dim) 78%, transparent); }
.tnote summary, .tnote-edit summary { cursor: pointer; font-weight: 500; color: var(--text); }
.tnote-body { margin-top: 8px; line-height: 1.5; white-space: pre-wrap; }
.tnote-edit { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
/* ⚠️ summary без display:flex — иначе пропадает треугольник раскрытия, и не видно, что
   блок открывается. */
.tnote-edit summary { font-size: .9em; }
.tnote-hint { margin-left: 8px; color: var(--muted); font-weight: 400; font-size: .9em; }
.tnote-edit .tnote-input { margin-top: 10px; min-height: 72px; }
}
.imgex-result:empty { display: none; }
.imgex-verdict { font-weight: 500; }
.imgex-verdict.correct { color: var(--correct); }
.imgex-verdict.wrong { color: var(--wrong); }
.imgex-fb { color: var(--text-dim); font-size: .92em; margin-top: 4px; white-space: pre-wrap; }
.imgex-scores { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.imgex-score { font-size: .8em; padding: 3px 9px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border-2); color: var(--text-dim); }
.imgex-fix { font-size: .88em; color: var(--text-dim); margin-top: 3px; }
.imgex-fix s { color: var(--wrong); }
.imgex-fix b { color: var(--correct); font-weight: 500; }
/* ── Конструктор упражнения с картинкой ──────────────────────────
   Общие детали: пустая плитка — пунктир + иконка + подсказка кликнуть; заполненная —
   картинка в рамке `--radius-sm`; крестик удаления висит КРУЖКОМ поверх правого верхнего
   угла (как `.crm-att-del`), а не отдельной кнопкой в общем ряду — одно действие, один
   контрол. «Верный ответ» — своя кнопка-переключатель, а не нативный radio: он не красится
   темой и на вид не отличим от чекбокса (см. правило проекта «цвет только через var()»). */
.imgex-thumb-empty { width: 100%; height: 100%; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 6px; border: 1px dashed var(--border-2);
  border-radius: var(--radius-sm); color: var(--muted); font-size: .78em; text-align: center;
  padding: 8px; box-sizing: border-box; }
.imgex-thumb-empty .ico svg { width: 20px; height: 20px; opacity: .7; }

.imgex-thumb-del { position: absolute; top: -7px; right: -7px; width: 24px; height: 24px; padding: 0;
  border-radius: 50%; border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 1; }
.imgex-thumb-del svg { width: 12px; height: 12px; }
.imgex-thumb-del:hover { color: var(--danger); border-color: var(--danger); }

/* Одна общая картинка (image_text_choice/true_false) */
.imgex-shot-row { display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap; }
.imgex-shot-wrap { position: relative; flex: 0 0 auto; }
.imgex-shot { width: 160px; aspect-ratio: 4 / 3; border-radius: var(--radius-sm); overflow: hidden;
  background: var(--surface-2); }
.imgex-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Отметка «верный ответ»: пилюля под картинкой (сетка) или кружок в текстовой строке.
   Приглушена, пока не выбрана, — заливается цветом `--correct`, когда активна. */
.imgex-vmark, .imgex-mark {
  border: 1px solid var(--border-2); background: transparent; color: var(--text-dim);
  cursor: pointer; transition: border-color .12s, background-color .12s, color .12s;
}
.imgex-vmark:hover, .imgex-mark:hover { border-color: var(--accent-border); }
.imgex-vmark.active, .imgex-mark.active {
  border-color: var(--correct); color: var(--correct);
  background: color-mix(in srgb, var(--correct) 14%, transparent);
}
.imgex-vmark {
  width: 100%; margin-top: 8px; padding: 7px 8px; border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center; gap: 6px; font-size: .8em;
}
.imgex-mark {
  flex: 0 0 auto; width: 28px; height: 28px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}
.imgex-mark .ico svg { width: 13px; height: 13px; }

/* Плитка-«добавить» — тем же пунктиром, что и пустая картинка, во всю ширину. */
.imgex-add-btn { width: 100%; margin-top: 10px; padding: 9px; border-radius: var(--radius-sm);
  border: 1px dashed var(--border-2); background: transparent; color: var(--muted);
  display: flex; align-items: center; justify-content: center; gap: 6px; font-size: .84em;
  cursor: pointer; transition: border-color .12s, color .12s; }
.imgex-add-btn:hover { border-color: var(--accent-border); color: var(--accent-text); }
.imgex-add-btn .ico svg { width: 14px; height: 14px; }

/* Варианты-картинки (image_choice): сетка кликабельных плиток, рамка карточки повторяет
   отметку «верный ответ» — видно с одного взгляда, не читая пилюлю под каждой. */
.imgex-vgrid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
.imgex-vcard { border: 2px solid transparent; border-radius: var(--radius-sm); padding: 2px;
  transition: border-color .12s; }
.imgex-vcard.correct { border-color: var(--correct); }
.imgex-vthumb { aspect-ratio: 4 / 3; border-radius: calc(var(--radius-sm) - 2px); overflow: hidden;
  background: var(--surface-2); cursor: pointer; outline: 2px solid transparent; outline-offset: -2px;
  transition: outline-color .12s; }
.imgex-vthumb:hover { outline-color: var(--accent-border); }
.imgex-vthumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.imgex-vthumb .imgex-thumb-empty { border-radius: calc(var(--radius-sm) - 2px); }

/* Варианты-тексты (image_text_choice): строка «отметка · поле · удалить», без нативного
   radio и без прилипшего к слову кружка. */
.imgex-topt-list { display: flex; flex-direction: column; gap: 8px; }
.imgex-topt-row { display: flex; align-items: center; gap: 10px; }
.imgex-topt-row .input { flex: 1; }
.imgex-row-del { flex: 0 0 auto; width: 30px; height: 30px; padding: 0; border-radius: 8px;
  border: 1px solid transparent; background: none; color: var(--text-dim); cursor: pointer;
  display: flex; align-items: center; justify-content: center; }
.imgex-row-del svg { width: 14px; height: 14px; }
.imgex-row-del:hover { color: var(--danger); border-color: var(--border-2); }

@media (max-width: 760px) {
  .imgex-grid { gap: 8px; }
  .imgex-tf { gap: 8px; }
  .imgex-vgrid { grid-template-columns: 1fr 1fr; gap: 8px; }
}
@media (max-width: 420px) {
  .imgex-vgrid { grid-template-columns: 1fr; }
}

/* ── Упражнение «Письмо» (rich-text ответ, views/essay-exercise.js) ──────────────
   Задание учителя — обычный текст (.essay-prompt); ответ ученика — маленький
   contenteditable с панелью Ж/К/Ч/S/выделение. В живой комнате поле синхронизировано
   между сторонами (см. room.js case 'answer') — потому и не должно выглядеть как обычное
   поле ввода, а как общий блокнот. */
.essay { display: flex; flex-direction: column; gap: 10px; }
.essay-prompt { font-size: 1.02em; color: var(--text); line-height: 1.5; white-space: pre-wrap; }
.essay-box {
  min-height: 140px; padding: 12px 14px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid transparent; color: var(--text);
  font-size: .95em; line-height: 1.55; overflow-wrap: anywhere; cursor: text;
  transition: border-color .12s;
}
.essay-box:focus { outline: none; border-color: var(--accent-border); }
/* Плейсхолдер у пустого contenteditable — своя реализация, у тега нет placeholder. */
.essay-box:empty::before { content: attr(data-placeholder); color: var(--muted); }
.essay-count { align-self: flex-end; font-size: .74em; color: var(--muted); }
.essay-count.over { color: var(--warn); }
/* Сдано / чужой ответ на проверке — по-настоящему нередактируемо (contenteditable=false),
   а не только визуально: pointer-events:none у .hw-locked ловит мышь, но не клавиатуру. */
.essay-readonly .essay-box { cursor: default; background: var(--bg); }
.essay-readonly .essay-box:empty::before { content: 'Пусто'; }

/* ── Форматирование текста упражнений (views/text-format.js) ─────────────────────
   Панель одна на конструктор всех упражнений с текстом (слева Ж/К/Ч/S, выделитель, цвет
   текста; справа размер и шрифт) и на ответ «Письма» (только левая часть без цвета).
   Поле конструктора (.rt-edit) — contenteditable, по виду как .textarea; текст держит
   переносы (pre-wrap), как и рендер ученику. */
/* Цвета текста: ключи — TEXT_COLORS в text-format.js, значения — здесь. В светлой теме
   темнее: те же оттенки на белом иначе не читаются. */
:root {
  --rt-red: #f87171; --rt-orange: #fb923c; --rt-yellow: #facc15; --rt-green: #4ade80;
  --rt-blue: #60a5fa; --rt-purple: #c084fc; --rt-pink: #f472b6; --rt-grey: #9ca3af;
}
:root[data-theme="light"] {
  --rt-red: #dc2626; --rt-orange: #c2410c; --rt-yellow: #a16207; --rt-green: #15803d;
  --rt-blue: #1d4ed8; --rt-purple: #7e22ce; --rt-pink: #be185d; --rt-grey: #6b7280;
}
.rt-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px; }
.rt-tgroup { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.rt-tbtn { width: 32px; height: 32px; padding: 0; border-radius: var(--radius-sm);
  border: 1px solid var(--border-2); background: var(--surface-2); color: var(--text-dim);
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  font-family: var(--font); transition: border-color .12s, color .12s, background .12s; }
.rt-tbtn:hover { border-color: var(--accent-border); color: var(--text); }
/* Курсор стоит в жирном/курсиве/… — кнопка «нажата»: видно, что уже применено. */
.rt-tbtn.on { border-color: var(--accent-border); color: var(--text);
  background: color-mix(in srgb, var(--accent) 14%, transparent); }
.rt-tlabel { font-size: .9em; line-height: 1; }
.rt-t-bold { font-weight: 700; }
.rt-t-italic { font-style: italic; }
.rt-t-underline { text-decoration: underline; }
.rt-t-strikeThrough { text-decoration: line-through; }
.rt-tbtn .ico svg { width: 16px; height: 16px; }
/* Выделитель: маркер и под ним полоса цвета выделения (того же, что у <mark>). */
.rt-hl { position: relative; }
.rt-hl .ico { margin-top: -4px; }
.rt-hl-bar { position: absolute; left: 8px; right: 8px; bottom: 5px; height: 3px;
  border-radius: 2px; background: var(--rt-yellow); }
/* Цвет текста — квадрат текущего цвета у курсора (фон ставит панель). */
.rt-swatch { width: 14px; height: 14px; border-radius: 3px; background: var(--text); }
/* Размер: − N + одной плашкой. .off — край шкалы (10/20); кнопка остаётся, чтобы не
   прыгала раскладка, но не реагирует видом. */
.rt-size { display: inline-flex; align-items: center; height: 32px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-2); background: var(--surface-2); }
.rt-size-btn { width: 30px; height: 30px; padding: 0; border: 0; background: none; color: var(--text-dim);
  cursor: pointer; display: flex; align-items: center; justify-content: center; }
.rt-size-btn .ico svg { width: 15px; height: 15px; }
.rt-size-btn:hover { color: var(--text); }
.rt-size-btn.off { opacity: .35; cursor: default; }
.rt-size-val { min-width: 24px; text-align: center; font-size: .88em; color: var(--text);
  font-variant-numeric: tabular-nums; }
.rt-font { height: 32px; min-width: 150px; max-width: 200px; padding: 0 8px 0 11px; display: flex;
  align-items: center; justify-content: space-between; gap: 8px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-2); background: var(--surface-2); color: var(--text); cursor: pointer;
  font-family: var(--font); font-size: .86em; }
.rt-font:hover, .rt-font.open { border-color: var(--accent-border); }
.rt-font-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rt-font .ico { color: var(--text-dim); flex: 0 0 auto; }
.rt-font .ico svg { width: 15px; height: 15px; }
/* Выпадашка цвета/шрифта — под кнопкой, поверх поля. */
.rt-popwrap { position: relative; }
.rt-pop { position: absolute; top: calc(100% + 4px); left: 0; z-index: 40; padding: 6px;
  max-width: calc(100vw - 24px);
  background: var(--surface); border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  box-shadow: 0 10px 28px rgba(0,0,0,.35); }
.rt-pop-end { left: auto; right: 0; }
.rt-colors { display: grid; grid-template-columns: repeat(4, 26px); gap: 6px; padding: 2px; }
.rt-color { width: 26px; height: 26px; padding: 0; border-radius: 6px; border: 0; cursor: pointer; }
.rt-color:hover, .rt-color.on { outline: 2px solid var(--accent-border); outline-offset: 2px; }
.rt-pop-item { display: block; width: 100%; margin-top: 6px; padding: 7px 10px; text-align: left;
  border: 0; border-radius: 6px; background: none; color: var(--text-dim); cursor: pointer;
  font-family: var(--font); font-size: .86em; white-space: nowrap; }
.rt-pop-item:hover { background: var(--surface-2); color: var(--text); }
.rt-pop-item.on { color: var(--accent-text); }
.rt-fonts { display: flex; flex-direction: column; width: 190px; max-width: 100%; max-height: 340px; overflow-y: auto; }
.rt-fonts .rt-pop-item { margin-top: 0; font-size: 1.02em; color: var(--text); }
.rt-fonts .rt-pop-item.on { color: var(--accent-text); }
.rt-field { display: flex; flex-direction: column; gap: 6px; }
.rt-edit {
  position: relative; width: 100%; min-height: 72px; padding: 11px 14px;
  background: var(--bg); color: var(--text); border: 1px solid var(--border-2);
  border-radius: var(--radius-sm); font-size: .92em; font-family: var(--font); line-height: 1.55;
  white-space: pre-wrap; overflow-wrap: anywhere; cursor: text; transition: border-color .15s;
}
.rt-edit:focus { outline: none; border-color: var(--accent-border);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 9%, transparent); }
.modal .rt-edit { border-color: transparent; }
.modal .rt-edit:focus { border-color: var(--accent-border); }
.rt-edit-tall { min-height: 216px; }   /* текст теории — втрое выше обычного поля */
.rt-edit-line { min-height: 0; }       /* вопрос теста, задание к картинке: строка, растёт под текст */
/* Плейсхолдер — своя реализация (у contenteditable атрибута нет); поверх, а не строкой
   текста, чтобы курсор вставал в начало поля, а не после подсказки. */
.rt-edit.is-empty::before { content: attr(data-placeholder); color: var(--muted);
  position: absolute; left: 15px; right: 15px; top: 12px; pointer-events: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Выделение цветом — и в ответе «Письма», и в тексте упражнения у ученика, и в редакторе. */
.essay-box mark, .ex-render mark, .rt-edit mark {
  background: var(--rt-yellow); color: var(--bg); border-radius: 2px; padding: 0 1px; }

/* ── Подключение Telegram (профиль) ───────────────────────────────── */
.tg-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.tg-state { color: var(--text-dim); font-size: .9em; }
.tg-state.warn { color: var(--warn); }

/* ── Анкета ученика при первом входе (онбординг) ────────────────────
   Экран-сцена: слева из-за края выглядывает огромная 3D-планета (ОДНА на весь мастер —
   createPlanet в views/onboarding.js, рендер mountPlanet в views/globe.js), справа —
   варианты ответа кругами, которые физически лежат друг на друге (buildBubbles).
   Правила, которые тут нельзя нарушать:
   • слои сцены (планета и круги) — position: fixed, координаты физики — координаты окна.
     Поэтому у их предков НЕ БЫВАЕТ transform: шаг сменяется только прозрачностью, а
     заголовок въезжает сам по себе (он сосед слоя, не предок);
   • позицию круга ставит JS на КНОПКУ, размер — масштабом ДИСКА внутри неё;
   • цвета — от --accent и --on-accent: палитра школы красит и планету, и круги. */

/* ⚠️ На экране анкеты шапки нет ВООБЩЕ: пока анкета не заполнена, все её пункты всё
   равно возвращают сюда, а меню профиля рядом с квизом читается как «отсюда можно
   уйти». Экран должен быть один-единственный. */
body.onb-view .topbar { display: none; }
body.onb-view .app { max-width: none; padding-left: 0; padding-right: 0; }
/* ⚠️ Экран анкеты — РОВНО высота окна: вертикальные поля .app (36/80, на телефоне 22/64)
   плюс свои поля страницы давали лишние ~33px на телефоне — страница прокручивалась, а
   стрелка «далее» висела высоко над краем (и отнимала место у кругов). Поля убираем
   только у .app С АНКЕТОЙ внутри: body.onb-view носит и голый экран рабочих часов
   (#/work-hours/send), ему поля нужны. */
body.onb-view .app:has(> .onb-page) { padding-top: 0; padding-bottom: 0; }

.onb-page { position: relative; display: flex; flex-direction: column;
  min-height: 100vh; min-height: 100dvh;
  padding: max(28px, 6vh) max(20px, 4vw) max(12px, 2.5vh); }
/* Затемнение по краям: планета — единственный источник света на экране. */
.onb-veil { position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background: radial-gradient(70% 55% at 50% 55%, transparent 0%,
    color-mix(in srgb, var(--bg) 85%, transparent) 100%); }
/* Плёночное зерно на весь экран — очень слабое. Оно снимает «цифровую стерильность»
   ровных градиентов: без него фон выглядит как заливка в редакторе. */
.onb-veil::after { content: ''; position: absolute; inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.5' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 170px 170px; opacity: .07; mix-blend-mode: overlay; }
.onb-shell { position: relative; z-index: 1; width: 100%; max-width: 1080px; margin: 0 auto;
  flex: 1; display: flex; flex-direction: column; }

/* ── Верхняя строка: раздел и прогресс ───────────────────────────── */
.onb-top { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.onb-eyebrow { font-size: .7rem; letter-spacing: .24em; text-transform: uppercase;
  color: var(--accent-text); opacity: .8; }
.onb-rail { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 160px; }
.onb-count { font-size: .72rem; letter-spacing: .18em; color: var(--muted);
  font-variant-numeric: tabular-nums; }
.onb-track { position: relative; flex: 1; height: 1px;
  background: color-mix(in srgb, var(--border-2) 90%, transparent); overflow: hidden; }
/* Заполнение — scaleX (композитор), а не width: тянется без пересчёта раскладки. */
.onb-track-fill { position: absolute; inset: 0; transform-origin: left center; transform: scaleX(0);
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 20%, transparent), var(--accent));
  box-shadow: 0 0 14px color-mix(in srgb, var(--accent) 70%, transparent);
  transition: transform .6s cubic-bezier(.22, 1, .36, 1); }

/* ── Шаг: заголовок, сцена, подвал ───────────────────────────────── */
/* ⚠️ Шаг сменяется ТОЛЬКО прозрачностью: внутри него fixed-слой кругов, а transform у
   предка превратил бы fixed в absolute относительно шага — сцена поехала бы вместе с ним.
   Анимация перезапускается сменой имени: out-* ставит turn(), render() его снимает. */
.onb-stage { flex: 1; display: flex; flex-direction: column;
  animation: onb-fade-in .45s ease both; }
.onb-stage.out-fwd, .onb-stage.out-back { animation: onb-fade-out .24s ease-in forwards; }
@keyframes onb-fade-in { from { opacity: 0; } }
@keyframes onb-fade-out { to { opacity: 0; } }
/* Заголовок въезжает снизу — ему transform можно: он сосед слоя кругов, а не предок.
   Только на СМЕНЕ шага (класс enter), а не на каждой перерисовке подвала. */
.onb-head.enter { animation: onb-head-in .55s cubic-bezier(.22, 1, .36, 1) both; }
@keyframes onb-head-in { from { opacity: 0; transform: translate3d(0, 16px, 0); } }

.onb-head { padding: 26px 0 0; max-width: 620px; }
.onb-greet { font-size: .78rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--text-dim); margin-bottom: 14px; }
/* Крупный заголовок в верхнем регистре — как в референсе: он держит весь экран. */
.onb-title { font-size: clamp(1.9rem, 4.6vw, 3.1rem); font-weight: 700; line-height: 1.05;
  letter-spacing: -.02em; text-transform: uppercase; margin: 0; }
.onb-hint { color: var(--text-dim); font-size: .92rem; line-height: 1.55; margin: 14px 0 0;
  max-width: 42ch; }
/* Стрелка «назад» — как в референсе: тонкая линия со скруглённым наконечником. */
.onb-back { position: relative; margin-top: 22px; width: 56px; height: 22px; padding: 0;
  background: none; border: 0; cursor: pointer; color: var(--text-dim);
  transition: color .2s ease, transform .2s ease; }
.onb-back span { position: absolute; left: 0; top: 50%; width: 100%; height: 1px;
  background: currentColor; }
.onb-back span::before { content: ''; position: absolute; left: 0; top: 50%; width: 9px; height: 1px;
  background: currentColor; transform-origin: left center; transform: rotate(-45deg); }
.onb-back span::after { content: ''; position: absolute; left: 0; top: 50%; width: 9px; height: 1px;
  background: currentColor; transform-origin: left center; transform: rotate(45deg); }
.onb-back:hover { color: var(--text); transform: translateX(-4px); }

/* Подсказка СПРАВА от заголовка (на телефоне — под ним, рядом со стрелкой «назад»). */
.onb-head.with-hint { max-width: 1000px; display: grid; align-items: center;
  grid-template-columns: minmax(0, max-content) minmax(220px, 430px);
  column-gap: clamp(22px, 3.2vw, 48px);
  grid-template-areas: "greet greet" "title hint" "back hint"; }
.onb-head.with-hint .onb-greet { grid-area: greet; }
.onb-head.with-hint .onb-title { grid-area: title; }
.onb-head.with-hint .onb-back { grid-area: back; justify-self: start; }
.onb-head-side { grid-area: hint; display: flex; flex-direction: column; gap: 10px; }
.onb-head-hint { margin: 0; font-size: clamp(1rem, 1.35vw, 1.25rem);
  line-height: 1.38; color: color-mix(in srgb, var(--text) 84%, transparent); max-width: 42ch; }
/* «Можно выбрать несколько», «можно пропустить» — служебная приписка под подсказкой. */
.onb-head-note { margin: 0; font-size: .7rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted); }

/* ── Сцена: планета и круги ──────────────────────────────────────── */
/* Место под круги между заголовком и подвалом. Сам по себе пустой — круги живут в
   fixed-слое поверх всей страницы. */
.onb-bubfield { position: relative; flex: 1; min-height: 240px; pointer-events: none; }
.onb-bub-layer { position: fixed; inset: 0; z-index: 2; pointer-events: none; overflow: hidden; }
/* Планета — ОДНА на весь мастер: слой лежит у .onb-page (до шага), поэтому не мигает
   и не выезжает заново на каждом вопросе. Над затемнением (оно раньше в разметке), под
   текстом шага (у оболочки z-index 1). */
.onb-planet-layer { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.onb-planet { position: absolute; left: 0; top: 0; }
.onb-planet-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block;
  opacity: 0; transition: opacity .9s ease; }
.onb-planet.has-3d .onb-planet-canvas { opacity: 1; }
/* Запасной диск — ТОЛЬКО когда 3D не будет (нет WebGL или все попытки сорвались, класс
   no-3d). ⚠️ Пока модель грузится, его нет: нарисованный шар выглядел готовой картинкой,
   и казалось, что «Земля не прогрузилась» (жалоба владельца продукта). На это время —
   прелоадер ниже. Свет — справа-сверху: на экране видна именно правая часть шара. */
.onb-planet-disk { position: absolute; border-radius: 50%; opacity: 0; transition: opacity .9s ease;
  background: radial-gradient(circle at 76% 26%,
    color-mix(in srgb, var(--accent) 78%, #fff) 0%,
    color-mix(in srgb, var(--accent) 82%, #000) 16%,
    color-mix(in srgb, var(--accent) 50%, #000) 42%,
    color-mix(in srgb, var(--accent) 22%, #000) 68%, #050505 100%); }
.onb-planet.no-3d .onb-planet-disk { opacity: 1; }
/* Рассеянное свечение ЗА планетой — ореол атмосферы. Круг крупнее диска (размер ставит
   JS, PLANET_GLOW = 1.16 — край планеты приходится на 86% радиуса ореола), сплошной внутри
   и тающий наружу.
   ⚠️ Сплошная середина — не расточительство: у низкополигональной модели край неровный
   и чуть внутри круга, о который бьются пузыри, и без подложки по краю планеты шла
   чёрная щель — «обводка», которую видно на скриншоте первой версии. */
.onb-planet-glow { position: absolute; border-radius: 50%; pointer-events: none;
  background: radial-gradient(closest-side,
    color-mix(in srgb, var(--accent) 55%, #000) 0%,
    color-mix(in srgb, var(--accent) 55%, #000) 86%,
    color-mix(in srgb, var(--accent) 30%, transparent) 89%,
    color-mix(in srgb, var(--accent) 11%, transparent) 94%,
    transparent 100%);
  filter: blur(14px); opacity: 0; transition: opacity 1.1s ease; }
/* Свечение — вместе с Землёй: сплошная середина ореола без планеты сверху выглядела бы
   тем самым «шаром». */
.onb-planet.has-3d .onb-planet-glow, .onb-planet.no-3d .onb-planet-glow { opacity: 1; }

/* ── Прелоадер планеты: контур и бегущий по нему свет ──────────────
   Размер и место — как у диска (ставит JS). Свет ходит туда-обратно по ВИДИМОЙ части
   контура (планета почти вся за краем экрана), угол ведёт JS через Web Animations —
   CSS-анимации гасит «Уменьшение движения», а прелоадер без движения ничего не сообщает. */
.onb-planet-load { position: absolute; border-radius: 50%; pointer-events: none;
  transition: opacity .6s ease; }
.onb-planet.has-3d .onb-planet-load, .onb-planet.no-3d .onb-planet-load { opacity: 0; }
/* Дорожка — пунктир контура (как прежние орбитальные кольца) и еле заметная подсветка по
   краю изнутри: контур читается формой шара, а не случайной линией. */
.onb-planet-load::before { content: ''; position: absolute; inset: 0; border-radius: 50%;
  border: 1px dashed color-mix(in srgb, var(--accent) 40%, transparent);
  background: radial-gradient(closest-side, transparent 93%,
    color-mix(in srgb, var(--accent) 9%, transparent) 100%); }
/* Светлое пятно на контуре: conic-градиент с пиком ровно на 0° (from −19°, ±19°) —
   поэтому угол поворота = положение пятна. Кольцо вырезается маской с мягкими краями —
   это и есть свечение, без filter: фильтр пришлось бы перерисовывать на каждом кадре на
   круге в тысячу пикселей. ⚠️ Полуширина 19° совпадает с ARC_HALF в onboarding.js. */
.onb-planet-load-arc { position: absolute; inset: -6px; border-radius: 50%;
  background: conic-gradient(from -19deg, transparent 0deg,
    color-mix(in srgb, var(--accent) 90%, #fff) 19deg, transparent 38deg, transparent 360deg);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 16px), #000 calc(100% - 7px),
    #000 calc(100% - 5px), transparent 100%);
  mask: radial-gradient(farthest-side, transparent calc(100% - 16px), #000 calc(100% - 7px),
    #000 calc(100% - 5px), transparent 100%); }

.onb-bub { position: absolute; left: 0; top: 0; margin: 0; padding: 0; border: 0;
  background: none; font: inherit; color: inherit; display: grid; place-items: center;
  pointer-events: none; will-change: transform; outline: none;
  -webkit-tap-highlight-color: transparent; -webkit-user-select: none; user-select: none; }
.onb-bub.on { z-index: 2; }
.onb-bub.drag { z-index: 3; }
/* Кликается только диск: у кнопки pointer-events: none — иначе прозрачные углы
   квадрата перехватывали бы нажатия у соседних кругов. */
.onb-bub-disc { position: absolute; inset: 0; border-radius: 50%; pointer-events: auto;
  cursor: pointer; touch-action: none; transform-origin: 50% 50%;
  background: color-mix(in srgb, var(--bg) 90%, var(--accent));
  /* Рамка 2px: в покое диск сжат масштабом до ~0.76, на экране выходит полтора пикселя. */
  border: 2px solid color-mix(in srgb, var(--accent) 82%, transparent);
  transition: background-color .16s ease, border-color .16s ease, box-shadow .3s ease; }
.onb-bub.drag .onb-bub-disc { cursor: grabbing; }
.onb-bub-body { position: relative; pointer-events: none; display: flex; flex-direction: column;
  align-items: center; gap: .12em; text-align: center; font-size: var(--bub-font, 15px);
  line-height: 1.16; font-weight: 500; letter-spacing: .005em;
  color: color-mix(in srgb, var(--text) 86%, transparent); transition: color .16s ease; }
/* ⚠️ Размеры значка и кода совпадают с BUB_PROFILES в onboarding.js — по ним считается
   радиус круга (на телефоне свои, см. @media ниже). */
.onb-bub-ico { font-size: 1.3em; line-height: 1.1; }
/* Код языка или уровня («EN», «中», «A1») — крупно и акцентом, как прежние медальоны. */
.onb-bub-code { font-size: 1.7em; line-height: 1.05; font-weight: 700; letter-spacing: .01em;
  color: color-mix(in srgb, var(--accent) 60%, #fff); transition: color .16s ease; }
.onb-bub-line { display: block; white-space: nowrap; }
/* Выбранный: сплошная заливка акцентом и контрастный к ней текст (--on-accent считается
   от палитры школы — у красного акцента это белый, как на макете). */
.onb-bub.on .onb-bub-disc { background: var(--accent); border-color: var(--accent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 60%, #fff) inset,
    0 18px 50px -16px color-mix(in srgb, var(--accent) 90%, transparent),
    0 0 36px -8px color-mix(in srgb, var(--accent) 70%, transparent); }
.onb-bub.on .onb-bub-body { color: var(--on-accent); font-weight: 600; }
.onb-bub.on .onb-bub-code { color: var(--on-accent); }
@media (hover: hover) {
  .onb-bub:not(.on) .onb-bub-disc:hover { border-color: var(--accent);
    background: color-mix(in srgb, var(--bg) 78%, var(--accent)); }
  .onb-bub:not(.on) .onb-bub-disc:hover + .onb-bub-body { color: var(--text); }
}
.onb-bub:focus-visible .onb-bub-disc { outline: 2px solid color-mix(in srgb, #fff 85%, transparent);
  outline-offset: 3px; }
/* Кольцо-отклик при выборе: масштаб и прозрачность ведёт JS (Web Animations). */
.onb-bub-ring { position: absolute; inset: 0; border-radius: 50%; pointer-events: none;
  border: 2px solid color-mix(in srgb, var(--accent) 80%, #fff); }

/* ── Свободный ответ ─────────────────────────────────────────────── */
/* Поле справа от планеты: левый отступ ставит JS (сколько занимает планета на высоте
   поля), иначе на ПК поле ложилось бы на её диск. */
.onb-field { position: relative; flex: 1; display: flex; align-items: center;
  justify-content: center; padding: 26px 0; }
/* ⚠️ Поле ПЛОТНОЕ, а не стеклянное: планета, просвечивающая сквозь строки, делает
   собственный текст ученика нечитаемым. */
.onb-field .onb-text { position: relative; z-index: 1;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); }
.onb-text { width: min(100%, 620px); min-height: 190px; font-size: 1rem; border-radius: 20px;
  padding: 20px; background: color-mix(in srgb, var(--surface) 55%, transparent);
  border: 1px solid color-mix(in srgb, var(--border-2) 80%, transparent);
  backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
  transition: border-color .2s ease, box-shadow .25s ease; }
.onb-text:focus { border-color: color-mix(in srgb, var(--accent) 60%, transparent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 14%, transparent); }

/* ── Удобное время: сетка «день × час» (последний шаг квиза) ────────
   Та же таблица, что «Мои окна для учителей» на главной, — ответ пишется в окна ученика.
   ⚠️ Подложка ПЛОТНАЯ, как у поля «о себе»: на телефоне сетка лежит поверх серпа планеты
   (отступ под него отнял бы треть ширины), и Земля сквозь ячейки мешала бы их читать. */
.onb-slots-field { padding: 18px 0; align-items: flex-start; }
.onb-slots-anchor { height: 0; }
/* Широкий экран: заголовок с подсказкой слева (под ним планета), сетка справа во всю
   высоту шага. Шестнадцать строк часов под заголовком в три строки на ноутбуке не
   помещались — страница уезжала вниз вместе со стрелкой «Завершить». */
@media (min-width: 1001px) {
  .onb-stage.slots-step { display: grid; align-content: start;
    grid-template-columns: minmax(0, 1fr) minmax(0, 600px);
    grid-template-rows: auto minmax(0, 1fr) auto; column-gap: clamp(28px, 4vw, 64px); }
  .onb-stage.slots-step > .onb-head { grid-column: 1; grid-row: 1; }
  /* Отступ якоря — под ореол планеты: он на 16% шире диска и иначе ложился бы на
     приписку «можно пропустить» и стрелку «назад» в конце заголовка. */
  .onb-stage.slots-step > .onb-slots-anchor { grid-column: 1; grid-row: 2; margin-top: 56px; }
  .onb-stage.slots-step > .onb-slots-field { grid-column: 2; grid-row: 1 / 3; padding: 26px 0 0; }
  .onb-stage.slots-step > .onb-foot { grid-column: 1 / -1; grid-row: 3; }
  /* Подсказка — под заголовком, а не рядом: справа от него стоит сама сетка. */
  .onb-stage.slots-step .onb-head.with-hint { display: flex; flex-direction: column;
    align-items: flex-start; max-width: none; }
  .onb-stage.slots-step .onb-head-side { margin-top: 20px; }
  .onb-stage.slots-step .onb-title { font-size: clamp(1.9rem, 3.4vw, 2.8rem); }
}
.slots { width: min(100%, 620px); display: flex; flex-direction: column; gap: 12px;
  -webkit-user-select: none; user-select: none; }
.onb-slots-field .slots { position: relative; z-index: 1; padding: 16px 18px 14px; border-radius: 22px;
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: blur(22px); -webkit-backdrop-filter: blur(22px); }
.slots-grid { display: flex; flex-direction: column; gap: 3px; }
/* ⚠️ touch-action: none — только там, где отмечают протягиванием (квиз): иначе палец,
   ведущий по ячейкам, прокручивал бы страницу. В модалке профиля сетка обычная. */
.slots-grid.paint { touch-action: none; }
.slots-row { display: grid; grid-template-columns: 50px repeat(7, minmax(0, 1fr)); gap: 3px; }
.slots-corner { display: block; }
.slots-day, .slots-hour { padding: 0; border: 0; background: none; font: inherit; cursor: pointer;
  color: var(--text-dim); border-radius: 6px; transition: color .15s ease, background-color .15s ease; }
.slots-day { font-size: .76rem; font-weight: 600; letter-spacing: .04em; padding: 2px 0 5px; }
.slots-hour { font-size: .72rem; text-align: left; color: var(--muted); font-variant-numeric: tabular-nums; }
@media (hover: hover) { .slots-day:hover, .slots-hour:hover { color: var(--text); } }
/* Высота ячейки — от высоты экрана: 16 строк часов обязаны помещаться между заголовком
   и стрелкой «далее» и на ноутбуке 1280×720, и на телефоне. */
/* Высоту строки подгоняет JS под место до низа окна (--slot-h, buildSlotsStep): у
   вопросов разная высота шапки, и считать её по vh нельзя. */
.slots-cell { display: block; height: var(--slot-h, 22px); border-radius: 6px; cursor: pointer;
  background: color-mix(in srgb, var(--text) 7%, transparent);
  transition: background-color .12s ease; }
/* ⚠️ Отмеченный час — СЕРЫЙ (`--win-student`), а не акцент (решение владельца продукта):
   это окно ученика, и тем же серым оно потом стоит в расписании класса и на главной.
   Свечения нет — серому оно не нужно. Акцент остался у быстрых наборов («Утро», «Будни»). */
.slots-cell.on { background: var(--win-student); }
@media (hover: hover) { .slots-cell:not(.on):hover { background: color-mix(in srgb, var(--text) 16%, transparent); } }
.slots-foot { display: flex; align-items: center; gap: 12px; min-height: 22px; }
.slots-count { font-size: .82rem; color: var(--text-dim); }
.slots-count.none { color: var(--muted); }   /* ⚠️ не .empty — это глобальная заглушка пустого экрана с полями 70px */
.slots-clear { margin-left: auto; padding: 0; border: 0; background: none; font: inherit;
  font-size: .8rem; color: var(--accent-text); cursor: pointer; }
.slots-clear[hidden] { display: none; }

/* ── Подвал шага ─────────────────────────────────────────────────── */
.onb-foot { display: flex; align-items: center; justify-content: flex-end; gap: 16px;
  padding: 2vh 0 4vh; }
/* ⚠️ Главное действие — длинная тонкая стрелка без подписи и без плашки: залитая
   кнопка во всю ширину перетягивала на себя весь экран и спорила со сценой. */
.onb-next.btn.btn-primary { width: auto; min-width: 0; height: auto; padding: 10px 2px;
  background: none; border: 0; box-shadow: none; color: var(--text-dim);
  display: inline-flex; align-items: center; gap: 12px;
  transition: color .25s ease, transform .25s cubic-bezier(.22, 1, .36, 1); }
/* ⚠️ Приглушение висит на САМОЙ СТРЕЛКЕ, а не на кнопке: на кнопке уже есть базовое
   правило из трёх классов (.onb-next.btn.btn-primary), и «загорание» с равной
   специфичностью до него не достучалось. У стрелки конкурентов нет. */
.onb-next-arrow { display: block; width: 88px; opacity: .45;
  transition: opacity .25s ease, transform .25s cubic-bezier(.22, 1, .36, 1); }
.onb-next-arrow svg { display: block; width: 88px; height: 20px; }
.onb-next-note { font-size: .8rem; }
.onb-next.ready { color: var(--accent-text); }
.onb-next.ready .onb-next-arrow { opacity: 1; }
.onb-next.ready:hover .onb-next-arrow { transform: translateX(6px); }
.onb-next:disabled { opacity: .4; }

/* ── Финальный экран ─────────────────────────────────────────────── */
.onb-done { flex: 1; display: flex; flex-direction: column; align-items: center;
  justify-content: center; text-align: center; gap: 6px; }
.onb-check { width: 96px; height: 96px; position: relative; margin-bottom: 18px; }
.onb-check::before { content: ''; position: absolute; inset: -30px; border-radius: 50%;
  background: radial-gradient(circle, color-mix(in srgb, var(--accent) 34%, transparent), transparent 68%);
  animation: onb-bloom 1.1s ease-out both; }
.onb-check-svg { position: relative; width: 96px; height: 96px; stroke: var(--accent);
  stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.onb-check-ring { stroke-dasharray: 145; stroke-dashoffset: 145;
  animation: onb-draw .7s cubic-bezier(.22, 1, .36, 1) forwards; }
.onb-check-tick { stroke-dasharray: 40; stroke-dashoffset: 40; stroke-width: 3.4;
  animation: onb-draw .4s cubic-bezier(.22, 1, .36, 1) .5s forwards; }
@keyframes onb-draw { to { stroke-dashoffset: 0; } }
@keyframes onb-bloom { from { opacity: 0; transform: scale(.6); } 60% { opacity: 1; } to { opacity: .55; } }

/* ── Мобильные ───────────────────────────────────────────────────── */
@media (max-width: 760px) {
  .onb-page { padding: max(18px, 3.5vh) 16px max(8px, 1.5vh); }
  .onb-head { padding-top: 18px; }
  .onb-title { font-size: clamp(1.6rem, 7.4vw, 2.2rem); }
  .onb-hint { font-size: .88rem; margin-top: 10px; }
  .onb-back { margin-top: 16px; }
  /* Подсказка уходит под заголовок — рядом со стрелкой «назад», как на макете. */
  .onb-head.with-hint { grid-template-columns: auto minmax(0, 1fr); column-gap: 18px;
    align-items: start; grid-template-areas: "greet greet" "title title" "back hint"; }
  .onb-head.with-hint.no-back { grid-template-areas: "greet greet" "title title" "hint hint"; }
  .onb-head.with-hint .onb-back { margin-top: 14px; }
  .onb-head-side { margin-top: 10px; gap: 6px; }
  .onb-head-hint { font-size: .86rem; line-height: 1.36; }
  .onb-head-note { font-size: .64rem; }
  .onb-bubfield { min-height: 200px; }
  /* ⚠️ Совпадает с BUB_PROFILES.mob в onboarding.js — по ним считается радиус круга. */
  .onb-bub-ico { font-size: 1.15em; }
  .onb-bub-code { font-size: 1.55em; }
  .onb-field { padding: 16px 0; }
  .onb-text { min-height: 150px; }
  .onb-slots-field { padding: 10px 0; }
  .onb-slots-field .slots { padding: 12px 10px 10px; border-radius: 18px; gap: 10px; }
  .slots-row { grid-template-columns: 40px repeat(7, minmax(0, 1fr)); gap: 3px; }
  .slots-cell { border-radius: 5px; }
}

/* Системная настройка «меньше движения»: декоративные CSS-анимации выключены. Физика
   кругов и планета идут из JS и при ней только смягчаются (см. calm в onboarding.js). */
@media (prefers-reduced-motion: reduce) {
  .onb-stage, .onb-head.enter, .onb-check::before,
  .onb-check-ring, .onb-check-tick { animation: none !important; }
  .onb-check-ring, .onb-check-tick { stroke-dashoffset: 0; }
}

/* Светлая тема: затемнение по краям мягче — на белом оно выглядит грязным пятном.
   (Сам экран анкеты тему форсирует тёмной — это на случай, если форс снимут.) */
[data-theme="light"] .onb-veil {
  background: radial-gradient(70% 55% at 50% 55%, transparent 0%,
    color-mix(in srgb, var(--bg) 70%, transparent) 100%); }

/* Варианты списком — только в модалке правки анкеты из профиля. */
.onb-opts { display: flex; flex-wrap: wrap; gap: 8px; }
.onb-opt { display: flex; align-items: center; gap: 9px; font: inherit; font-size: .95em;
  cursor: pointer; text-align: left; padding: 11px 16px; border-radius: 999px; color: var(--text);
  background: var(--surface); border: 1px solid var(--border-2);
  transition: border-color .15s ease, background .15s ease, color .15s ease; }
.onb-opt-mark { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto;
  background: color-mix(in srgb, var(--text-dim) 45%, transparent); transition: background .15s ease; }
.onb-opt:hover { border-color: var(--accent-border); }
/* ⚠️ НЕ `background: var(--selected)`: токен наследует --accent, то есть цвет ТЕКСТА,
   и сплошная заливка им давала зелёное на зелёном. */
.onb-opt.active { background: color-mix(in srgb, var(--selected) 14%, transparent);
  border-color: var(--selected); color: var(--selected); font-weight: 500; }
.onb-opt.active .onb-opt-mark { background: var(--selected); }

/* Анкета в настройках школы: карточка вопроса */
.onbq-list { display: flex; flex-direction: column; gap: 12px; margin-top: 12px; }
.onbq { padding: 14px; }
.onbq.off { opacity: .6; }
.onbq-head { display: flex; align-items: center; gap: 10px; }
.onbq-num { width: 22px; height: 22px; flex: 0 0 auto; border-radius: 50%; font-size: .78em;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2); color: var(--muted); }
.onbq-title { flex: 1; min-width: 120px; font-weight: 500; }
.onbq-acts { display: flex; gap: 4px; flex: 0 0 auto; }
.onbq-del svg { color: var(--danger); }
.onbq-meta { display: flex; align-items: center; gap: 8px; margin: 10px 0; flex-wrap: wrap; }
.onbq-type { font-size: .78em; padding: 3px 10px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border-2); color: var(--text-dim); }
.onbq-type-sel { max-width: 220px; }
.onbq-key { font-size: .75em; color: var(--muted); }
.onbq-opts { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.onbq-opt { display: flex; align-items: center; gap: 8px; }
.onbq-opt .input { flex: 1; }
.onbq-opts > .btn { align-self: flex-start; }
/* Варианты из справочника — только показываем. Ограничиваем высоту: справочник хобби
   на 27 позиций иначе растянул бы карточку вопроса на весь экран. */
.onbq-locked { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px;
  max-height: 132px; overflow-y: auto; }
.onbq-switches { margin-top: 12px; border-top: 1px solid var(--border); padding-top: 4px; }
.onbq-add { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }

@media (max-width: 760px) {
  .onbq-head { flex-wrap: wrap; }
  .onbq-title { order: 3; flex: 1 1 100%; }
}

/* ─────────────────────────────────────────────────────────────────
   ОТБОР ПЕРСОНАЛА
   Экран кандидата (#/staff), анкета преподавателя и её мини-лендинг
   (#/card/:id), таблица потоков в настройках.
   ───────────────────────────────────────────────────────────────── */

/* Пунктов меню на экране отбора нет — они всё равно возвращают сюда. Шапка с названием
   школы при этом остаётся (в отличие от .onb-view). */
body.staff-view #nav { display: none; }
/* Пока отбор не пройден — и меню профиля справа нет (решение владельца продукта): оно
   появляется, когда кандидат принят. Выход — кнопкой внизу страницы (.staff-exit). */
body.staff-locked #who, body.staff-locked .who-menu, body.staff-locked .card-nudge { display: none; }
.staff-exit { display: flex; justify-content: center; margin-top: 28px; }

.staff-page { max-width: 860px; margin: 0 auto; padding-bottom: 60px; }
.staff-head { text-align: center; margin: 26px 0 22px; }
.staff-kicker { font-size: .78em; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted); }
.staff-title { font-size: 1.9em; font-weight: 700; margin: 8px 0 0; line-height: 1.2; }
.staff-sub { color: var(--text-dim); margin-top: 6px; }

/* Шаги отбора: тест → обучение → анкета. Пройденное — акцентом с галочкой. */
.staff-steps { display: flex; gap: 10px; margin: 0 0 20px; flex-wrap: wrap;
  justify-content: center; }
.staff-step { display: flex; align-items: center; gap: 9px; padding: 9px 15px;
  border-radius: 999px; border: 1px solid var(--border-2); background: var(--surface);
  color: var(--text-dim); font-size: .9em; }
.staff-step-n { width: 22px; height: 22px; flex: 0 0 auto; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; font-size: .8em;
  background: var(--surface-2); color: var(--muted); }
.staff-step.done { color: var(--text); border-color: var(--accent-border); }
.staff-step.done .staff-step-n { background: var(--accent); color: var(--on-accent); }
.staff-step.current { color: var(--text); border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--surface)); }
.staff-step.current .staff-step-n { background: var(--accent); color: var(--on-accent); }

.staff-intro { margin-bottom: 16px; }
.staff-stage-title { font-size: 1.15em; font-weight: 600; margin-bottom: 6px; }
.staff-rules { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.staff-rule { font-size: .82em; padding: 5px 12px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border-2); color: var(--text-dim); }

.staff-slides { display: flex; flex-direction: column; gap: 16px; }
.staff-foot { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;
  margin-top: 22px; }
.btn.btn-lg { padding: 13px 30px; font-size: 1.02em; }

.staff-result { text-align: center; padding: 34px 22px; }
.staff-result-ico { font-size: 3em; line-height: 1; }
.staff-result-title { font-size: 1.35em; font-weight: 700; margin: 12px 0 6px; }
.staff-score { font-size: 2.2em; font-weight: 800; font-variant-numeric: tabular-nums;
  color: var(--accent-text); }
/* Пояснения на терминальных экранах идут подряд — без отступа они слипаются в абзац. */
.staff-result .page-sub { margin: 8px auto 0; max-width: 52ch; line-height: 1.55; }

/* ── Экспресс-тест на знание языка (views/staff-express.js) ──────────
   Смысл теста — скорость: крупный вопрос, крупные варианты, таймер всегда на виду.
   ⚠️ Полосы двигает JS (Web Animations / transform), а не CSS-переход: «Уменьшение
   движения» на iPhone гасит CSS-анимации, а таймер обязан идти у всех. */
.xt-intro-ico { width: 44px; height: 44px; border-radius: 12px; display: flex;
  align-items: center; justify-content: center; margin-bottom: 12px;
  background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent-text); }
.xt-intro-ico svg { width: 22px; height: 22px; }
.xt-lead { margin: 8px 0 0; line-height: 1.6; color: var(--text-dim); max-width: 68ch; }
.xt-rules { list-style: none; padding: 0; margin: 18px 0 0; display: flex;
  flex-direction: column; gap: 10px; }
.xt-rule { display: flex; gap: 10px; align-items: flex-start; line-height: 1.5; }
.xt-rule-ico { flex: 0 0 auto; display: flex; margin-top: 3px; color: var(--accent-text); }
.xt-rule-ico svg { width: 16px; height: 16px; }
.xt-last { margin: 16px 0 0; }
.xt-quiet { text-align: center; margin: 16px 0 0; }

/* Вопрос нельзя выделить: копипаст в переводчик — самый быстрый способ подсмотреть. */
.xt { user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
.xt-countdown { text-align: center; padding: 54px 20px; }
.xt-count-cap { color: var(--muted); letter-spacing: .14em; text-transform: uppercase;
  font-size: .8em; }
.xt-count { font-size: 4.5em; font-weight: 800; line-height: 1.1; margin-top: 10px;
  color: var(--accent-text); font-variant-numeric: tabular-nums; }

.xt-top { display: flex; align-items: center; gap: 10px; }
.xt-counter { color: var(--text-dim); font-size: .9em; font-variant-numeric: tabular-nums; }
.xt-level { font-size: .74em; font-weight: 700; padding: 3px 9px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border-2); color: var(--text-dim); }
.xt-clock { margin-left: auto; font-weight: 700; font-size: 1.05em; color: var(--text);
  font-variant-numeric: tabular-nums; }
.xt-clock.low { color: var(--danger); }
.xt-progress { height: 3px; border-radius: 3px; margin-top: 12px; overflow: hidden;
  background: var(--surface-2); }
.xt-progress-fill { height: 100%; background: var(--muted); transform-origin: left;
  transform: scaleX(0); }
.xt-timer { height: 8px; border-radius: 8px; margin-top: 8px; overflow: hidden;
  background: var(--surface-2); }
.xt-timer-fill { height: 100%; background: var(--progress); transform-origin: left; }
.xt-timer-fill.low { background: var(--danger); }
.xt-prompt { font-size: 1.45em; font-weight: 600; line-height: 1.45; margin: 26px 0 22px;
  min-height: 2.9em; overflow-wrap: anywhere; }
.xt-gap { display: inline-block; width: 3.2em; height: .95em; vertical-align: -.1em;
  margin: 0 .15em; border-bottom: 2px solid var(--accent-text); }
/* Варианты — выбираемые элементы: рамка у них несёт состояние «выбрано» (docs/ui-kit.md). */
.xt-opts { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.xt-opt { display: flex; align-items: center; gap: 12px; min-height: 56px; padding: 12px 14px;
  text-align: left; border-radius: var(--radius-sm); cursor: pointer; font: inherit;
  font-size: 1.02em; background: var(--surface-2); border: 1px solid var(--border-2);
  color: var(--text); }
.xt-opt:hover:not(:disabled) { border-color: var(--accent-border); }
.xt-opt:disabled { cursor: default; }
.xt-opt.picked { border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, var(--surface-2)); }
.xt-opt-k { flex: 0 0 auto; width: 24px; height: 24px; border-radius: 7px; display: flex;
  align-items: center; justify-content: center; font-size: .75em; font-weight: 700;
  color: var(--muted); background: var(--surface); }
.xt-opt-t { min-width: 0; overflow-wrap: anywhere; }
.xt-foot { display: flex; align-items: center; gap: 12px; margin-top: 16px; min-height: 34px; }
.xt-note { color: var(--warn); font-size: .85em; min-width: 0; }
/* Пометка «уточняющий вопрос» — справка, а не предупреждение: жёлтый оставлен уходу со страницы. */
.xt-note.info { color: var(--muted); }
.xt-skip { margin-left: auto; flex: 0 0 auto; white-space: nowrap; }

.xt-badge { width: 124px; height: 124px; border-radius: 50%; margin: 16px auto 8px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: var(--surface-2); border: 2px solid var(--border-2); }
.xt-badge.ok { border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--surface-2)); }
.xt-badge-lv { font-size: 2.4em; font-weight: 800; line-height: 1; color: var(--text); }
.xt-badge.ok .xt-badge-lv { color: var(--accent-text); }
.xt-badge-cap { font-size: .72em; color: var(--muted); margin-top: 6px; }
/* Разбивка по уровням — у кандидата в итоге и у администратора в окне попытки. */
.xt-levels, .xd-levels { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 14px; }
.xt-levels { justify-content: center; }
.xt-lv, .xd-lv { display: flex; flex-direction: column; align-items: center; gap: 2px;
  min-width: 52px; padding: 7px 8px; border-radius: 10px; font-size: .82em;
  background: var(--surface-2); border: 1px solid transparent; color: var(--muted); }
.xt-lv b, .xd-lv b { color: var(--text-dim); }
.xt-lv.ok, .xd-lv.ok { border-color: var(--accent-border); color: var(--accent-text); }
.xt-lv.ok b, .xd-lv.ok b { color: var(--accent-text); }

@media (max-width: 560px) {
  .xt-opts { grid-template-columns: 1fr; }
  .xt-prompt { font-size: 1.2em; margin: 20px 0 16px; }
  .xt-opt { min-height: 50px; }
}

/* ── Редактор анкеты преподавателя ────────────────────────────────
   Секции-карточки с подсказкой сбоку (макет владельца продукта). Форма ОДНА и для
   модалки в профиле, и для экрана отбора, поэтому ширину задаёт контейнер, а не она
   сама. ⚠️ Секция отделяется ФОНОМ, а не рамкой — общее правило дизайн-системы. */
/* Окно шире обычного (620px): в две колонки «контрол + подсказка» иначе не влезть.
   ⚠️ Не 1080, как у кокпита профиля, — анкету заполняют сверху вниз, и слишком широкое
   окно растягивает строку подсказки до нечитаемой длины. */
.modal:has(> .tc-wrap) { max-width: 880px; }
.tc-wrap { display: flex; flex-direction: column; }
.tc-form { display: flex; flex-direction: column; gap: 14px; }
.tc-sec { padding: 18px 20px; border-radius: var(--radius); background: var(--surface-2); }
.tc-sec-title { font-weight: 600; margin-bottom: 12px; }
/* Контрол слева, подсказка справа. minmax(0,…) обязателен: без него длинное слово в
   подсказке растягивает колонку и ломает сетку. */
.tc-sec-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: 18px 24px; align-items: start; }
/* Секция с действием у правого края (макет: превью — подсказка — «Загрузить»). */
.tc-sec-grid.has-aside { grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; }
/* Секции, где контрол широкий (текст, языки, файлы): подсказка идёт НАД ним. */
.tc-sec-stack { display: flex; flex-direction: column; gap: 12px; }
.tc-hint { display: flex; flex-direction: column; gap: 9px; color: var(--text-dim);
  font-size: .86em; line-height: 1.55; }
.tc-hint p { margin: 0; }
.tc-sub { font-size: .78em; color: var(--muted); margin-bottom: 7px; }
.tc-note { font-size: .82em; color: var(--muted); }

/* Фото: два превью — ровно так его увидят в анкете и в списке преподавателей. */
.tc-photo-prev { display: flex; gap: 16px; align-items: flex-end; }
.tc-photo-one { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.tc-photo { flex: 0 0 auto; border-radius: 50%;
  background: var(--surface) center/cover no-repeat;
  display: flex; align-items: center; justify-content: center; color: var(--muted); }
.tc-photo-big { width: 104px; height: 104px; font-size: 1.6em; }
.tc-photo-small { width: 46px; height: 46px; font-size: .9em; }
.tc-photo-cap { font-size: .72em; color: var(--muted); }
/* ⚠️ Кнопка ОДНА и переключается «Загрузить» ↔ «Убрать» (макет владельца продукта):
   двух кнопок в столбик на телефоне хватало на две лишние строки, а «Убрать» рядом с
   пустым кружком нажимать было незачем. */
.tc-photo-act { display: inline-flex; }
.tc-up { display: inline-flex; }

/* Загруженные документы — плитками, как на макете. */
.tc-files-row { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.tc-files-col { flex: 1; min-width: 200px; }
.tc-tiles { display: flex; flex-wrap: wrap; gap: 10px; }
.tc-tiles-empty { font-size: .82em; color: var(--muted); }
.tc-tile { position: relative; width: 62px; height: 62px; border-radius: 10px;
  background: var(--surface); display: flex; align-items: center; justify-content: center;
  overflow: hidden; }
.tc-tile-img { width: 100%; height: 100%; object-fit: cover; }
.tc-tile-ico { font-size: 1.3em; }
/* ⚠️ Крестик виден всегда, а не по наведению: на тач-экране :hover не наступает вовсе,
   и убрать ошибочно загруженный файл с телефона было бы нечем. */
.tc-tile-x { position: absolute; top: 3px; right: 3px; width: 18px; height: 18px;
  border: 0; border-radius: 50%; cursor: pointer; font-size: .62em; line-height: 1;
  color: #fff; background: rgba(0, 0, 0, .55); }
.tc-tile-x:hover { background: var(--danger); }

.tc-about { width: 100%; resize: vertical; }
.tc-count { text-align: right; font-size: .76em; color: var(--muted); margin-top: 6px;
  font-variant-numeric: tabular-nums; }
.tc-count.over { color: var(--warn); }

/* Языки: у каждого свои уровни и свои направления. */
.tc-langs { display: flex; flex-direction: column; gap: 12px; }
.tc-lang { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px 22px;
  padding: 14px 16px; border-radius: var(--radius-sm); background: var(--surface); }
.tc-lang-head { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
.tc-lang-head .select { flex: 1; min-width: 0; }
.tc-lang-del { flex: 0 0 auto; }
.tc-levels { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px 10px; }
.tc-lang-add { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 12px; }
.tc-lang-add:empty { display: none; }
/* Язык в блоке ещё не выбран: уровни и направления видны (понятно, что они будут), но
   приглушены и не нажимаются — `inert` в teacher-card.js teachingField. */
.tc-lang-hint { margin: -4px 0 12px; }
.tc-lang.off .tc-lang-opts { opacity: .45; pointer-events: none; }

/* Опыт преподавания — своя секция с тумблером «Не показывать опыт».
   ⚠️ Поле не ПРЯЧЕТСЯ при выключенном показе, а приглушается: число остаётся у школы, и
   исчезающее поле читалось бы как «его стёрли», а не «его не видит ученик». */
.tc-exp-row { display: flex; gap: 10px; align-items: center; margin-bottom: 12px; }
.tc-exp-row .tc-sub { margin: 0; }
.tc-exp-row.off { opacity: .45; }
.tc-exp { max-width: 110px; }

/* Плеер видео-приветствия во всю ширину секции: преподаватель должен увидеть, ЧТО
   покажут ученику, не уходя на чужой сайт и не сохраняя анкету ради проверки. */
.tc-video:not(:empty) { margin-top: 12px; }
.tc-video-player { width: 100%; aspect-ratio: 16 / 9; max-height: 420px; border: 0;
  border-radius: var(--radius-sm); background: #000; display: block; }
.tc-video-note { display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  padding: 12px 14px; border-radius: var(--radius-sm); background: var(--surface); }

/* Направления: выбранные чипами, «+» раскрывает список школы под ними. */
.tc-chips { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.tc-chip { display: inline-flex; align-items: center; gap: 7px; padding: 6px 12px;
  border-radius: 999px; font-size: .84em; color: var(--accent-text);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid var(--accent-border); }
.tc-chip-x { background: none; border: 0; padding: 0; cursor: pointer; font-size: .85em;
  color: inherit; opacity: .65; }
.tc-chip-x:hover { opacity: 1; }
/* «+» — SVG-иконка, отцентрированная флексом: текстовый глиф сидел по метрикам шрифта,
   ниже и левее центра кружка. */
.tc-chip-add { width: 28px; height: 28px; padding: 0; border-radius: 50%; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border-2); background: var(--surface-2); color: var(--text-dim); }
.tc-chip-add svg { width: 14px; height: 14px; display: block; }
.tc-chip-add:hover { border-color: var(--accent-border); color: var(--accent-text); }
.tc-pick { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 10px; }
.tc-pick-item { padding: 5px 11px; border-radius: 999px; cursor: pointer; font-size: .82em;
  font-family: var(--font); color: var(--text-dim);
  border: 1px solid var(--border-2); background: transparent; }
.tc-pick-item:hover { color: var(--accent-text); border-color: var(--accent-border); }

/* Чекбоксы (уровни, возрастные группы, хобби). ⚠️ Своя кнопка, а не <input checkbox>:
   у нативного в тёмной теме свой цвет от браузера, мимо палитры школы. */
.tc-checks { display: flex; flex-direction: column; gap: 4px; }
/* Хобби в две колонки — СВЕРХУ ВНИЗ по левой, потом по правой (как на макете).
   ⚠️ Именно `columns`, а не grid: сетка раскладывает построчно (первый слева, второй
   справа), а список так не читается. Grid с column-flow пришлось бы задавать точным
   числом строк, и добавление 28-го хобби ломало бы раскладку.
   ⚠️ `display: block` обязателен: соседнее правило .tc-checks делает контейнер флексом,
   а флекс-контейнер многоколоночную раскладку игнорирует — все 27 строк уходят в одну
   колонку (поймано в браузере). */
.tc-checks-2 { display: block; columns: 2; column-gap: 18px; }
.tc-checks-2 .tc-check { width: 100%; break-inside: avoid; }
.tc-check { display: flex; align-items: center; gap: 9px; padding: 4px 2px; cursor: pointer;
  border: 0; background: none; font: inherit; font-size: .88em; font-family: var(--font);
  color: var(--text-dim); text-align: left; }
.tc-check-box { width: 15px; height: 15px; flex: 0 0 auto; border-radius: 4px;
  border: 1px solid var(--border-2); background: var(--surface);
  position: relative; transition: background .12s, border-color .12s; }
.tc-check:hover { color: var(--text); }
.tc-check:hover .tc-check-box { border-color: var(--accent-border); }
.tc-check.on { color: var(--text); }
.tc-check.on .tc-check-box { background: var(--selected); border-color: var(--selected); }
/* Галочка — псевдоэлемент, а не символ в разметке: так она не попадает в выделение
   текста и не читается скринридером как буква. */
.tc-check.on .tc-check-box::after { content: ''; position: absolute; left: 4px; top: 1px;
  width: 4px; height: 8px; border: solid var(--on-accent); border-width: 0 2px 2px 0;
  transform: rotate(45deg); }
.tc-check-label { min-width: 0; }

.tc-add { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.tc-add .input { flex: 1; min-width: 160px; }

/* Настройки → Персонал → «Анкета преподавателя»: блок на каждый язык школы, у блока —
   свои направления (recruit-settings.js teacherCardSettings). */
.tcs-langs { display: flex; flex-direction: column; gap: 12px; }
.tcs-lang { display: flex; flex-direction: column; gap: 12px; padding: 14px 16px;
  border-radius: var(--radius-sm); background: var(--surface); }
.tcs-lang-head { display: flex; gap: 10px; align-items: center; }
.tcs-lang-name { font-weight: 600; font-size: 1.02em; min-width: 0; overflow-wrap: anywhere; }
.tcs-lang-del { margin-left: auto; flex: 0 0 auto; }
.tcs-lang-add { margin-top: 12px; }
/* Подвал редактора: Отмена · Предпросмотр · Сохранить. */
.tc-foot { display: flex; gap: 10px; justify-content: flex-end; flex-wrap: wrap;
  margin-top: 18px; }

@media (max-width: 760px) {
  .tc-sec { padding: 15px 14px; }
  .tc-sec-grid, .tc-sec-grid.has-aside, .tc-lang { grid-template-columns: minmax(0, 1fr); }
  /* Действие уезжает под подсказку и прижимается влево — в столбик оно уже не «сбоку». */
  .tc-sec-grid.has-aside { justify-items: start; }
  /* На узком экране подсказка идёт НАД контролом: сначала «зачем», потом «что делать». */
  .tc-sec-grid > .tc-hint { order: -1; }
  .tc-checks-2 { columns: 1; }
  .tc-photo-acts { margin-left: 0; }
  .tc-foot .btn { flex: 1 1 100%; }
}

/* ── Мини-лендинг преподавателя ── */
.tcard-page { max-width: 860px; margin: 0 auto; }
.tcard { display: flex; flex-direction: column; gap: 20px; }
/* ⚠️ Без рамки и с overflow:hidden — та же причина, что у .sched-toolbar (см. пометку
   там): тонкий шов у скруглённого угла на стыке фона и «прозрачной» рамки. */
.tcard-hero { display: flex; gap: 22px; align-items: center; padding: 22px;
  border-radius: 16px; overflow: hidden;
  background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 12%, var(--surface)),
    var(--surface)); }
.tcard-photo { width: 132px; height: 132px; flex: 0 0 auto; border-radius: 50%;
  background: var(--surface-2) center/cover no-repeat;
  border: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); }
.tcard-photo-empty { display: flex; align-items: center; justify-content: center;
  font-size: 2.6em; font-weight: 700; color: var(--accent-text); }
.tcard-hero-info { min-width: 0; }
.tcard-name { font-size: 1.7em; font-weight: 700; margin: 0; line-height: 1.15; }
.tcard-role { color: var(--text-dim); margin-top: 4px; }
.tcard-facts { color: var(--muted); font-size: .9em; margin-top: 8px; }
.tcard-chips { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 10px; }
.tcard-chip { font-size: .84em; padding: 5px 12px; border-radius: 999px;
  color: var(--accent-text); border: 1px solid var(--accent-border);
  background: color-mix(in srgb, var(--accent) 12%, transparent); }
.tcard-chip-soft { color: var(--text-dim); border-color: var(--border-2);
  background: var(--surface-2); }
.tcard-block { padding: 18px 20px; border-radius: 14px; background: var(--surface);
  border: 1px solid transparent; }
.tcard-block-title { font-size: .78em; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 10px; }
.tcard-text { margin: 0; line-height: 1.6; white-space: pre-wrap; }
.tcard-video { width: 100%; max-height: 460px; aspect-ratio: 16 / 9; border-radius: 12px;
  border: 0; background: #000; display: block; }
.tcard-docs { display: flex; flex-wrap: wrap; gap: 12px; }
/* Плитка ОДНОГО размера для картинки, превью PDF и значка: рамку держит сама ссылка,
   поэтому подмена значка на отрисованную страницу ничего не сдвигает. */
.tcard-doc { display: flex; align-items: center; justify-content: center;
  width: 132px; height: 96px; overflow: hidden; border-radius: 10px;
  background: var(--surface-2); border: 1px solid var(--border-2);
  text-decoration: none; color: var(--muted); }
.tcard-doc-img { width: 100%; height: 100%; object-fit: cover; }
/* Страница PDF — вертикальный лист: показываем его ВЕРХ (шапка с названием), а не середину. */
.tcard-doc-img.pdf { object-position: top; }
.tcard-doc-ico { display: flex; }
.tcard-doc-ico svg { width: 28px; height: 28px; }
.tcard-doc:hover { border-color: var(--border-3, var(--text-dim)); color: var(--text); }

/* ── Настройки: потоки отбора и кандидаты ── */
.rec-book { display: flex; align-items: center; gap: 12px; padding: 10px 12px;
  border-radius: 10px; background: var(--surface-2); border: 1px solid var(--border-2);
  margin-bottom: 8px; flex-wrap: wrap; }
.rec-book-t { font-weight: 500; }
.rec-book-n { color: var(--muted); font-size: .85em; flex: 1; }

.rec-table { display: flex; flex-direction: column; }
.rec-row { display: grid; gap: 12px; align-items: center; padding: 12px 4px;
  border-bottom: 1px solid var(--border);
  grid-template-columns: 1.4fr .8fr .7fr 1.3fr 1.3fr 1fr auto; }
.rec-head { font-size: .76em; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); padding-bottom: 8px; }
.rec-row.off { opacity: .55; }
.rec-name { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.rec-off-tag, .rec-cnt { font-size: .76em; color: var(--muted); }
.rec-stage { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rec-stage-t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: .9em; }
.rec-stage-e { font-size: .74em; color: var(--muted); }
.rec-empty { color: var(--muted); font-size: .85em; }
.rec-code { display: flex; align-items: center; gap: 4px; min-width: 0; }
.rec-code code { font-size: .86em; padding: 3px 8px; border-radius: 7px;
  background: var(--surface-2); border: 1px solid var(--border-2);
  overflow: hidden; text-overflow: ellipsis; }
.rec-code-edit { display: flex; gap: 8px; align-items: center; }
.rec-code-edit .input { flex: 1; }
.rec-acts { display: flex; gap: 6px; justify-content: flex-end; flex-wrap: wrap; }

/* ── Кандидаты: шкала этапов (макет владельца продукта) ─────────────
   Карточка кандидата: имя и почта | точки этапов на одной линии | итог отбора и «Скрыть».
   ⚠️ Рамка вокруг карточки — по макету владельца, вопреки общему «без рамок» (docs/ui-kit.md):
   карточки лежат стопкой на одном фоне, и разделяет их именно контур.
   Размеры — из макета (он в ~1,5× к CSS-пикселям). */
.rc-group { font-size: 1.45em; font-weight: 400; color: var(--muted); line-height: 1.25;
  margin: 26px 0 14px; }
.section-title + div > .rc-group:first-child { margin-top: 4px; }
.rc-list { display: flex; flex-direction: column; gap: 18px; container: rc / inline-size; }
.rc-card { display: grid; align-items: start; column-gap: 12px; padding: 18px 14px 20px 18px;
  grid-template-columns: minmax(124px, 1.2fr) minmax(0, 5.8fr) minmax(96px, .9fr);
  border: 1px solid color-mix(in srgb, var(--text) 16%, transparent); border-radius: var(--radius); }
.rc-who { min-width: 0; padding-top: 2px; }
.rc-name { font-size: 1.2em; font-weight: 600; line-height: 1.25; overflow-wrap: anywhere; }
/* Почта переносится после «@» (<wbr> в разметке), а не посреди слова. */
.rc-mail { font-size: .95em; color: var(--muted); margin-top: 22px; overflow-wrap: anywhere; }

/* Шкала: колонки этапов (`--cols` задаёт разметка — у «Анкеты» колонка шире), линия —
   отрезками между центрами соседних точек (у первого этапа она начинается от точки, у
   последнего — на точке кончается). ⚠️ Колонки — переменной, а не инлайн-свойством: иначе
   вертикальная шкала телефона (правило контейнера ниже) не смогла бы их перебить. */
.rc-flow { display: grid; grid-template-columns: var(--cols, repeat(4, minmax(0, 1fr))); min-width: 0; }
.rc-step { position: relative; display: flex; flex-direction: column; align-items: center;
  text-align: center; min-width: 0; padding: 0 2px; }
.rc-step::before { content: ''; position: absolute; top: 31px; left: 0; right: 0; height: 1.5px;
  background: var(--stage-todo); }
.rc-step:first-child::before { left: 50%; }
.rc-step:last-child::before { right: 50%; }
.rc-step:only-child::before { display: none; }
.rc-step-t { font-size: .86em; line-height: 20px; color: var(--text); white-space: nowrap;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
a.rc-step-link { text-decoration: none; }
a.rc-step-link:hover { color: var(--accent-text); text-decoration: underline dotted;
  text-underline-offset: 3px; }
.rc-dot { position: relative; z-index: 1; width: 11px; height: 11px; border-radius: 50%;
  margin: 6px 0 9px; background: var(--stage-todo); flex: none; }
.rc-done .rc-dot { background: var(--stage-done); }
.rc-fail .rc-dot { background: var(--stage-fail); }
.rc-now .rc-dot { background: var(--stage-now); }
/* Тело — во всю колонку (stretch): от её ширины считается отступ пунктов анкеты. */
.rc-step-body { display: flex; flex-direction: column; align-items: center; min-width: 0;
  align-self: stretch; }
.rc-line { font-size: .73em; line-height: 1.4; color: var(--text); overflow-wrap: anywhere; }
.rc-line b { font-weight: 600; }
/* Дата и итог не рвутся: узкая колонка переносит строку между подписью и значением. */
.rc-nw { white-space: nowrap; }
.rc-line.dim { color: var(--muted); }
.rc-line.live { color: var(--stage-now); }
/* Итог экспресс-теста — ссылка на разбор попыток. */
button.rc-link { background: none; border: 0; padding: 0; font-family: inherit; cursor: pointer;
  text-decoration: underline dotted color-mix(in srgb, currentColor 45%, transparent);
  text-underline-offset: 3px; }
button.rc-link:hover { color: var(--accent-text); }
.rc-reset { margin-top: 8px; padding: 4px 24px; border: 0; border-radius: 999px; cursor: pointer;
  background: var(--bg); color: var(--text); font: inherit; font-size: .68em; line-height: 1.3;
  transition: background .15s; }
.rc-reset:hover { background: color-mix(in srgb, var(--text) 12%, var(--bg)); }
/* Пункты анкеты: столбиком, их точки — под точкой этапа. Колонке, которой не хватает места
   справа от центра, список сдвигается влево: заходить он может только в зазор между
   колонками (12px), но не под итог справа. */
.rc-items { list-style: none; margin: 0; padding: 0 0 0 min(calc(50% - 4.5px), calc(100% - 80px));
  align-self: stretch; display: flex; flex-direction: column; text-align: left; }
.rc-item { display: flex; align-items: center; gap: 6px; font-size: .73em; line-height: 1.32;
  white-space: nowrap; }
.rc-idot { width: 9px; height: 9px; border-radius: 50%; flex: none; background: var(--stage-fail); }
.rc-item.now .rc-idot { background: var(--stage-now); }
.rc-item.ok .rc-idot { background: var(--stage-done); }

.rc-side { display: flex; flex-direction: column; align-items: center; text-align: center;
  min-width: 0; margin-top: -6px; }
.rc-hide { align-self: flex-end; background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-size: .76em; color: var(--muted); transition: color .15s; }
.rc-hide:hover { color: var(--text); }
.rc-verdict { margin-top: 36px; font-size: .92em; line-height: 1.3; color: var(--text); }
.rc-note { margin-top: 6px; font-size: .74em; line-height: 1.35; color: var(--muted);
  overflow-wrap: break-word; }
.rc-approve { margin-top: 12px; }
.rc-block { margin-top: 6px; }
.rc-card.rc-is-hidden { opacity: .7; }
.rc-hidden-toggle { margin-top: 22px; background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-size: .9em; color: var(--muted); }
.rc-hidden-toggle::after { content: ' ▾'; }
.rc-hidden-toggle[aria-expanded="true"]::after { content: ' ▴'; }
.rc-hidden-toggle:hover { color: var(--text); }

/* Узкая карточка (настройки с боковым меню, планшет): имя и итог — первой строкой, шкала —
   во всю ширину под ними. */
@container rc (max-width: 760px) {
  .rc-card { grid-template-columns: minmax(0, 1fr) auto; row-gap: 18px; }
  .rc-flow { grid-column: 1 / -1; grid-row: 2; }
  .rc-side { grid-column: 2; grid-row: 1; align-items: flex-end; text-align: right;
    margin-top: 0; max-width: 220px; }
  .rc-verdict { margin-top: 10px; }
  .rc-mail { margin-top: 6px; }
}
/* Телефон: шкала вертикальная — точка и линия слева, подписи справа от них. */
@container rc (max-width: 560px) {
  .rc-card { padding: 16px 14px 16px; }
  .rc-flow { grid-template-columns: 1fr; }
  .rc-step { display: grid; grid-template-columns: 11px minmax(0, 1fr); column-gap: 12px;
    align-items: start; text-align: left; padding: 0 0 14px; }
  .rc-step-t { grid-column: 2; grid-row: 1; white-space: normal; }
  .rc-dot { grid-column: 1; grid-row: 1; margin: 5px 0 0; }
  .rc-step-body { grid-column: 2; align-items: flex-start; }
  /* Линия идёт через весь этап (к точке следующего), у первого — от его точки, у последнего —
     только до неё. */
  .rc-step::before, .rc-step:first-child::before, .rc-step:last-child::before {
    top: 0; bottom: 0; left: 5px; right: auto; width: 1.5px; height: auto; }
  .rc-step:first-child::before { top: 10px; }
  .rc-step:last-child::before { bottom: auto; height: 10px; }
  .rc-items { padding-left: 0; margin-top: 2px; }
}

/* Окно потока: блок экспресс-теста. Недоступный включатель и уровень при выключенном
   тесте ПРИГЛУШАЮТСЯ, а не прячутся — исчезнувшее поле читалось бы как «настройку стёрли». */
.switch-row.disabled { opacity: .5; }
.rec-dim { opacity: .5; }
/* Сложность экспресс-теста: набор (Мягкая/Обычная/Строгая) и три настройки под ним — в ряд
   на широком окне, столбиком на телефоне. */
.rec-diff { margin-top: 14px; }
.rec-diff-label { display: block; font-weight: 600; margin-bottom: 8px; }
.rec-diff-presets { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.rec-diff-custom { font-size: .82em; color: var(--text-dim); }
.rec-diff-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 14px; }
.rec-diff-grid .select { width: 100%; }
.rec-diff-warn { margin: 4px 0 0; color: var(--warn); }
@media (max-width: 720px) { .rec-diff-grid { grid-template-columns: 1fr; } }

/* Окно «Экспресс-тест» кандидата: каждый вопрос с ответом и временем. Строки отделены
   фоном, а не рамкой (docs/ui-kit.md); верно/неверно — полоской слева. */
.xd-verdict { font-size: 1.15em; font-weight: 700; }
.xd-verdict.ok { color: var(--correct); }
.xd-verdict.bad { color: var(--wrong); }
.xd-facts { color: var(--text-dim); font-size: .88em; margin-top: 4px; line-height: 1.5; }
.xd-qs { display: flex; flex-direction: column; gap: 6px; margin-top: 16px; }
.xd-q { display: grid; grid-template-columns: 26px 30px 1fr auto; gap: 10px;
  align-items: start; padding: 9px 12px; border-radius: var(--radius-sm);
  background: var(--surface-2); box-shadow: inset 3px 0 0 var(--border-2); }
.xd-q.ok { box-shadow: inset 3px 0 0 var(--correct); }
.xd-q.bad { box-shadow: inset 3px 0 0 var(--wrong); }
.xd-q.wait { opacity: .55; }
.xd-q-n { color: var(--muted); font-size: .82em; font-variant-numeric: tabular-nums; }
.xd-q-lv { font-size: .74em; font-weight: 700; color: var(--text-dim); }
.xd-q-body { min-width: 0; }
.xd-q-t { overflow-wrap: anywhere; }
.xd-q-a { display: flex; gap: 4px 12px; flex-wrap: wrap; font-size: .84em; margin-top: 3px;
  color: var(--text-dim); }
.xd-q.bad .xd-q-mine { color: var(--wrong); }
.xd-q-right { color: var(--correct); }
.xd-q-flag { color: var(--warn); }
.xd-q-round { color: var(--muted); }
.xd-hint { color: var(--muted); font-size: .82em; line-height: 1.5; margin: 10px 0 0; }
.xd-warn { color: var(--warn); font-size: .88em; line-height: 1.5; margin-top: 4px; }
.xd-q-ms { font-size: .8em; color: var(--muted); white-space: nowrap;
  font-variant-numeric: tabular-nums; }
.xd-old { margin-top: 18px; }
.xd-old > summary { cursor: pointer; color: var(--text-dim); padding: 6px 0; }
.xd-old .xd { margin-top: 10px; }
@media (max-width: 560px) {
  .xd-q { grid-template-columns: 22px 1fr; }
  .xd-q-lv { grid-column: 2; grid-row: 1; }
  .xd-q-body, .xd-q-ms { grid-column: 2; }
}

/* На узком экране таблицы превращаются в карточки: семь колонок туда не влезают, а
   горизонтальный скролл в настройках читается как поломка вёрстки. */
@media (max-width: 900px) {
  .rec-head { display: none; }
  .rec-row { grid-template-columns: 1fr; gap: 6px; padding: 14px 4px; }
  .rec-acts { justify-content: flex-start; margin-top: 6px; }
  .tcard-hero { flex-direction: column; text-align: center; }
  .tcard-chips, .tcard-facts { justify-content: center; }
}
@media (max-width: 760px) {
  .staff-title { font-size: 1.5em; }
  .staff-steps { gap: 6px; }
  .staff-step { padding: 7px 11px; font-size: .82em; }
  .tc-photo-row { flex-direction: column; }
}

/* ── Награды: каталог и 3D-превью ────────────────────────────────
   Модель рисуется в WebGL-канвасе внутри .rw-stage; всё вокруг — обычная вёрстка,
   поэтому карточка выглядит одинаково и с 3D, и с заглушкой (нет WebGL, идёт
   генерация, модель не получилась). */
.rw-head { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; }
.rw-head .section-title { margin: 0; }

/* Выбор сервиса: цена стоит рядом с названием — выбирают в первую очередь по ней. */
.rw-prov { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.rw-prov-item { display: flex; flex-direction: column; gap: 6px; text-align: left;
  padding: 12px 14px; border-radius: 12px; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--border-2); color: var(--text); }
.rw-prov-item:hover { border-color: var(--accent-border); }
.rw-prov-item.active { border-color: var(--accent-border); color: var(--accent-text);
  background: color-mix(in srgb, var(--accent) 12%, transparent); }
.rw-prov-head { display: flex; flex-direction: column; gap: 2px; }
.rw-prov-name { font-weight: 600; }
.rw-prov-price { font-size: .82em; color: var(--text-dim); }
.rw-prov-hint { font-size: .8em; color: var(--muted); line-height: 1.45; }
.rw-prov-site { font-size: .78em; color: var(--muted); text-decoration: underline; }

.rw-grid { display: grid; gap: 14px; margin-top: 14px;
  grid-template-columns: repeat(auto-fill, minmax(184px, 1fr)); }
.rw-card { position: relative; display: flex; flex-direction: column; align-items: center;
  gap: 2px; padding: 12px 10px 14px; border-radius: 14px;
  background: var(--surface-2); border: 1px solid transparent; }
.rw-card:hover { border-color: var(--border-2); }
.rw-off { opacity: .55; }

/* Сцена: квадрат, в котором живёт только сама модель. Фона под ней НЕТ.
   ⚠️ Тут были подложка-«ромашка» (фестончатая фигура, медленно вращалась) и круглое
   свечение под моделью — обе УДАЛЕНЫ по решению владельца продукта. Возвращать не нужно,
   как и прежнюю тень-эллипс (`.rw-shadow`): она изображала опору, которой у парящей
   награды нет. Награда должна висеть в пустоте — украшения вокруг неё перетягивают
   внимание с самого предмета. */
.rw-stage { position: relative; width: 100%; aspect-ratio: 1 / 1; display: flex;
  align-items: center; justify-content: center; touch-action: pan-y; cursor: grab; }
.rw-stage:active { cursor: grabbing; }
.rw-stage-big { width: min(320px, 70vw); margin: 0 auto 6px; }

/* Пока движок и модель готовятся, в середине сцены мягко пульсирует пятнышко: у награды
   в это время пусто, и без сигнала это неотличимо от поломки. Класс ставит reward3d.js
   на время подготовки — отдельного спиннера не нужно.
   ⚠️ Это НЕ подложка: пятно живёт только во время загрузки и гаснет вместе с классом. */
.rw-stage.rw3d-busy::after {
  content: ''; position: absolute; left: 50%; top: 50%; width: 22%; aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%); border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--text-dim) 40%, transparent) 0%, transparent 70%);
  animation: rw-busy 1.4s ease-in-out infinite;
}
@keyframes rw-busy { 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) {
  .rw-stage.rw3d-busy::after { animation: none; }
}
.rw3d-canvas { position: relative; z-index: 1; width: 100% !important; height: 100% !important; }
/* Стоп-кадр: показывается вместо канваса, когда контекст WebGL отдан соседней награде
   (их у вкладки мало — см. reward3d.js). Без него плитка просто ПУСТЕЛА бы. */
.rw3d-still { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%;
  object-fit: contain; pointer-events: none; }

.rw-empty { display: flex; flex-direction: column; align-items: center; gap: 6px;
  color: var(--muted); }
.rw-empty-ico { font-size: 2.4em; opacity: .8; }
.rw-empty-txt { font-size: .8em; }

.rw-name { font-weight: 600; text-align: center; line-height: 1.25;
  overflow-wrap: anywhere; }
.rw-status { font-size: .78em; color: var(--muted); text-align: center; }
.rw-status-wait { color: var(--accent-text); }
.rw-status-bad { color: var(--danger); display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden; }

/* Действия появляются по наведению — на карточке размером с ладонь три постоянные
   кнопки перетягивают внимание с самой награды. На тач-экранах наведения нет,
   поэтому там они видны всегда (см. @media ниже).
   ⚠️ z-index ОБЯЗАТЕЛЕН и должен быть выше `.rw3d-canvas` (у него z-index: 1).
   Канвас накрывает верх карточки, где стоят кнопки, и он ПРОЗРАЧНЫЙ — кнопки было
   видно, но нажать их было нельзя: клик уходил в канвас, курсор показывал «схватить»
   (cursor: grab у .rw-stage), и вместо кнопки крутилась модель. .rw-stage не создаёт
   свой контекст наложения (z-index: auto), поэтому z-index канваса конкурирует с
   кнопками напрямую, и одного лишь порядка в DOM тут не хватает. */
.rw-acts { position: absolute; z-index: 2; top: 8px; right: 8px; display: flex; gap: 4px;
  opacity: 0; transition: opacity .15s ease; }
.rw-card:hover .rw-acts, .rw-card:focus-within .rw-acts { opacity: 1; }
.rw-act { padding: 4px 7px; min-height: 0; line-height: 1; }

.rw-view { display: flex; flex-direction: column; align-items: center; }
.rw-view-name { font-size: 1.2em; font-weight: 700; text-align: center; }
.rw-view-text { margin: 8px 0 4px; text-align: center; color: var(--text-dim);
  line-height: 1.5; max-width: 460px; }
.rw-view-dim { color: var(--muted); }

.rw-prompt { min-height: 84px; font-size: .9em; }
.rw-prompt-row { display: flex; gap: 8px; margin-top: 8px; }

@media (hover: none) {
  .rw-acts { opacity: 1; }
}
@media (max-width: 760px) {
  .rw-grid { grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 10px; }
  .rw-rail { gap: 10px; }
  .rw-rail > .rw-card { flex-basis: 148px; }
  .rw-prov { grid-template-columns: 1fr; }
}
/* Выбор способа создания награды (сгенерировать / загрузить файл) — обычные вкладки,
   но с отступом: под ними сразу идут поля формы. */
.rw-mode { margin-bottom: 14px; }
/* Превью выбранного .glb прямо в модалке: появляется только когда файл выбран, поэтому
   пустая рамка не занимает место в форме. */
.rw-pick-preview { display: none; width: 170px; height: 170px; aspect-ratio: auto;
  margin: 10px 0 0; border-radius: 12px; background: var(--surface-2);
  border: 1px solid transparent; }
.rw-pick-preview.on { display: flex; }
.rw-pick-note { margin: 8px 0 0; }

/* ── Разделы каталога: системные / пользовательские ───────────────
   Обычные вкладки, но внутри карточки и рядом с кнопкой «Новая награда»: это не
   навигация по настройкам, а два разных каталога — вкладки стоят над тем, что
   переключают. Нижнюю линию и отступ снимаем, её роль тут играет сама карточка. */
.rw-kinds { margin: 0; border-bottom: 0; flex-wrap: wrap; }
.rw-kinds .tab { padding: 6px 14px; font-size: .88em; }
.rw-kind-note { margin: 2px 0 0; }

/* Редактор медали: слева картинка и цвет, справа живой предпросмотр. Предпросмотр
   обязателен — медаль собирается из картинки и цвета, и по имени файла с квадратиком
   цвета «как оно будет выглядеть» не угадать. */
/* Окну награды нужна своя ширина: в стандартные 460px две колонки редактора медали
   (поля слева, предпросмотр справа) не помещаются — вкладки способа ломались на три
   строки каждая. Специфичность :has выше, чем у .modal-wide, порядок правил не важен. */
.modal:has(> .rw-edit-body) { max-width: 700px; }
.rw-medal-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 18px;
  align-items: start; }
.rw-medal-preview { width: 190px; height: 190px; aspect-ratio: auto; flex: 0 0 auto; }

/* Цвет оправы: готовые «металлы» + свой цвет. Кружки, а не список названий — цвет
   выбирают глазами. */
.rw-colors { display: flex; flex-wrap: wrap; gap: 8px; }
.rw-color { width: 28px; height: 28px; padding: 0; border-radius: 50%; cursor: pointer;
  border: 1px solid var(--border-2); transition: transform .12s ease, box-shadow .12s ease; }
.rw-color:hover { transform: scale(1.12); }
.rw-color.on { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent-border); }
/* Своя палитра: радуга, пока цвет не выбран вручную. Инлайновый background с выбранным
   цветом перекрывает её — тогда видно, какой именно оттенок задан. */
.rw-color-custom { background: conic-gradient(#e05252, #e0c452, #6fd06f, #52c8e0, #7a6ce0, #d05bb8, #e05252); }

@media (max-width: 760px) {
  .rw-medal-row { grid-template-columns: 1fr; }
  .rw-medal-preview { width: 100%; height: auto; aspect-ratio: 1 / 1; }
}

/* ── «Редактор наград» (своя награда: Материалы → Награды) ─────────
   Без подписей над полями — подсказки в плейсхолдерах; слева цвет, пояснение и выбор
   картинки, справа крупная медаль; «Создать» внизу справа, закрывает крестик.
   ⚠️ Специфичность `.rw-ed .rw-medal-preview` (0,2,0) выше общего `.rw-medal-preview`,
   поэтому мобильный размер задан здесь же, а не наследуется из блока выше. */
.modal:has(> .rw-ed) { max-width: 672px; }
.rw-ed .textarea { min-height: 136px; resize: none; }
.rw-ed .rwl-pack-row .select, .rw-ed .rwl-pack-row .btn { align-self: stretch; }
.rw-ed .rwl-pack-row .btn { padding-left: 18px; padding-right: 18px; }
.rw-ed-medal { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 18px;
  align-items: stretch; margin-top: 4px; }
.rw-ed-side { display: flex; flex-direction: column; min-width: 0; }
.rw-ed-side .field { margin-bottom: 0; }
.rw-ed-pick { margin-top: auto; padding-top: 16px; }
.rw-ed .rw-medal-preview { width: 220px; height: 220px; }
.rw-ed-actions { justify-content: flex-end; margin-top: 18px; }
.rw-ed-actions .btn { min-width: 150px; }
@media (max-width: 760px) {
  .rw-ed-medal { grid-template-columns: 1fr; }
  .rw-ed-medal > div:last-child { order: -1; justify-self: center; }
  .rw-ed .rw-medal-preview { width: 200px; height: 200px; }
  .rw-ed-actions .btn { width: 100%; }
}

/* ── Витрина ученика «Мои награды» и окно выдачи ──────────────────
   Один и тот же кубик (.rw-tile) в двух местах: в ленте над расписанием класса и в
   сетке выбора у учителя. Отличается только раскладка контейнера. */
.rw-shelf { margin-top: 26px; }
.rw-shelf-head { display: flex; align-items: baseline; gap: 8px; margin: 0 0 10px; }
.rw-shelf-count { font-size: .78em; font-weight: 500; color: var(--muted);
  font-variant-numeric: tabular-nums; }
.rw-shelf-empty { color: var(--muted); font-size: .9em; padding: 8px 0 4px; }

/* Лента: ровно 8 наград в ряд (4 на телефоне), остальные — горизонтальной прокруткой.
   ⚠️ Ширина плитки считается ОТ ШИРИНЫ ЛЕНТЫ, а не задаётся в пикселях: иначе на узком
   экране в «четыре в ряд» помещалось бы три с половиной, и ряд выглядел бы обрезанным. */
.rw-strip { display: flex; gap: 12px; overflow-x: auto; padding-bottom: 6px;
  scroll-snap-type: x proximity; overscroll-behavior-x: contain; }
.rw-strip > .rw-tile { flex: 0 0 calc((100% - 7 * 12px) / 8); scroll-snap-align: start; }
/* ⚠️ В ленте горизонтальный жест — это ПРОКРУТКА, а не вращение модели: лента для того
   и сделана, чтобы листать награды пальцем. Мышью модель по-прежнему крутится
   (touch-action касается только тач-жестов). */
.rw-strip .rw-stage { touch-action: pan-x pan-y; }

/* Сетка выбора: те же 8 (4) в ряд, но прокрутка вертикальная — наград в школе может
   быть много, а модалка не должна вырастать выше экрана. */
/* ⚠️ overflow-x: hidden обязателен: доли пикселя при делении ширины на восемь колонок
   дают 2–3px «переполнения», и внизу сетки появляется лишняя горизонтальная полоса
   прокрутки — она читается как сломанная вёрстка. */
.rw-pick { display: grid; grid-template-columns: repeat(8, 1fr); gap: 12px;
  max-height: min(54vh, 430px); overflow-y: auto; overflow-x: hidden; padding: 2px; }
.rw-pick-hint { margin: 0 0 12px; }
.rw-pick-msg { grid-column: 1 / -1; text-align: center; padding: 18px 0;
  color: var(--muted); font-size: .9em; line-height: 1.5; }
/* Восемь моделей в ряд в обычную модалку (620px) не помещаются — окну выдачи нужна
   своя ширина. Специфичность :has выше, чем у .modal-wide, поэтому порядок не важен. */
.modal:has(> .rw-pick-body) { max-width: 940px; }

.rw-tile { position: relative; display: flex; flex-direction: column; align-items: center;
  gap: 3px; min-width: 0; }
.rw-tile .rw-stage { width: 100%; }
/* Подпись — кнопка (нажимается и с клавиатуры), но выглядит обычным текстом. */
.rw-tile-name { background: none; border: 0; padding: 0; cursor: pointer; font: inherit;
  color: var(--text); font-size: .78em; font-weight: 600; text-align: center;
  line-height: 1.25; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere; }
.rw-tile-name:hover { color: var(--accent-text); }
.rw-tile-note { font-size: .72em; color: var(--muted); }
.rw-tile-off { opacity: .5; }
.rw-tile-off .rw-tile-name { cursor: default; color: var(--muted); }
.rw-tile.sel .rw-stage { border-radius: 14px;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  box-shadow: inset 0 0 0 2px var(--accent-border); }
.rw-tile.sel .rw-tile-name { color: var(--accent-text); }
/* ⚠️ z-index обязателен: канвас модели лежит на z-index 1 и накрыл бы метку
   (та же ловушка, что с кнопками карточки каталога — см. комментарий у .rw-acts). */
.rw-tile-badge { position: absolute; z-index: 2; top: 0; right: 0; pointer-events: none;
  padding: 2px 7px; border-radius: 99px; font-size: .62em; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase;
  background: var(--badge-accent); color: var(--on-accent); }

.rw-view-from { margin: 2px 0 0; text-align: center; font-size: .82em; color: var(--muted); }

/* Кнопка выдачи в поп-апе оценки урока — слева, «Позже» остаётся справа. */
.rate-actions { justify-content: flex-end; flex-wrap: wrap; }
.rate-award { margin-right: auto; }

/* Окно выдачи: источник (награды школы / мои) — те же сегменты, что наверху «Материалов»,
   под ним пакеты-чипы. Сегменты и чипы в одну строку, на узком экране — столбиком. */
.rw-pick-nav { display: flex; align-items: center; gap: 10px 14px; flex-wrap: wrap; margin: 0 0 12px; }
.rw-chips { display: flex; gap: 6px; flex-wrap: wrap; min-width: 0; }
.rw-chip { display: inline-flex; align-items: center; gap: 6px; max-width: 220px;
  padding: 6px 12px; border-radius: 99px; cursor: pointer; border: 1px solid var(--border-2);
  background: transparent; color: var(--text-dim); font-family: var(--font); font-size: .82em; }
.rw-chip-t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rw-chip:hover { color: var(--text); }
.rw-chip.active { border-color: var(--accent-border); color: var(--selected);
  background: color-mix(in srgb, var(--selected) 12%, transparent); }
.rw-chip.active .mat-n { background: color-mix(in srgb, var(--selected) 20%, transparent);
  color: var(--selected); }

/* ── «Материалы → Награды»: свои награды преподавателя по пакам ──
   Секция = пак: заголовок с числом и действиями, под ним та же сетка карточек, что в
   каталоге школы (.rw-grid/.rw-card), — чтобы медаль выглядела одинаково везде.
   ⚠️ Эту же раскладку (.rwl-section/.rwl-head/.rwl-empty/.rwl-pack-row и чипы .rw-chip) берут
   и ПАКИ ДОСОК («Материалы → Доски», modules/board/boards.js): правка здесь меняет оба экрана. */
.rwl-bar { display: flex; align-items: baseline; gap: 6px 14px; flex-wrap: wrap;
  margin: -4px 0 18px; font-size: .86em; }
.rwl-count { color: var(--text); font-variant-numeric: tabular-nums; }
.rwl-hint { color: var(--muted); }
.rwl-section + .rwl-section { margin-top: 28px; }
.rwl-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.rwl-title { font-size: 1.02em; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rwl-title-dim { color: var(--text-dim); font-weight: 500; }
.rwl-head-acts { display: flex; gap: 2px; margin-left: 4px; }
.rwl-note { margin: 4px 0 0; color: var(--muted); font-size: .82em; line-height: 1.5; }
.rwl-grid { margin-top: 12px; }
.rwl-empty { grid-column: 1 / -1; display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 16px 18px; border-radius: 14px; border: 1px dashed var(--border-2);
  color: var(--muted); font-size: .86em; }
/* Перетаскивание доски в другой пак (ui.js → enableCardMove, boards.js → boardsBody).
   Карточку берут за любое место; пока тащат, она идёт за указателем поверх остальных, а секция,
   в которую можно бросить, подсвечена. Пустая «Без пака» (.rwl-spare) видна только на время
   переноса — иначе доску из пака было бы некуда вынуть. */
.board-lib-card[data-move-id] { cursor: grab; user-select: none; -webkit-user-select: none;
  -webkit-touch-callout: none; }
.board-lib-card.move-card, .rw-card.move-card { position: fixed; margin: 0; box-sizing: border-box; z-index: 300;
  pointer-events: none; cursor: grabbing; box-shadow: var(--shadow); border-color: var(--accent-border); }
/* ── Доски: страница паков (boards.js) ──
   Обзор: «Паки досок:» карточками и ниже «Отдельные доски:»; пак открывается на своей странице.
   Везде по четыре в ряд, без каруселей. */
.bpk-h { margin: 0 0 14px; font-size: 1.25em; font-weight: 500; }
.bpk-h:not(:first-child) { margin-top: 34px; }
.bpk-grid, .bpk-boards { display: grid; gap: 16px; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.bpk-boards { margin-top: 0; }
@media (max-width: 1020px) { .bpk-grid, .bpk-boards { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 760px) { .bpk-grid, .bpk-boards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } }
@media (max-width: 470px) { .bpk-boards { grid-template-columns: minmax(0, 1fr); } }

/* Карточка пака: обложка на всю карточку, снизу тёмная панель с названием, описанием, числом. */
/* ⚠️ Рамки у карточки НЕТ и фон — цвет панели (--bg): 1px прозрачной рамки со светлым фоном
   давали белую кайму вокруг тёмной панели, а на дробной ширине картинка выглядывала с краёв.
   Контур при наведении — тенью, а не рамкой. */
.bpk-card { position: relative; aspect-ratio: 2 / 3; border-radius: 26px; overflow: hidden;
  background: var(--bg); isolation: isolate; }
.bpk-card--link { cursor: pointer; }
.bpk-card--link:hover { box-shadow: 0 0 0 1px var(--border-2); }
.bpk-card--link:focus-visible { outline: 2px solid var(--accent-border); outline-offset: 2px; }
/* ⚠️ Обложка НЕ доходит до нижних скруглённых углов (30px снизу — всегда под панелью): картинка и
   панель обрезаются одной дугой, и сглаженный край картинки просвечивал сквозь сглаженный край
   панели светлой каймой в углах. Где картинки нет — шва нет. */
.bpk-cover { position: absolute; top: 0; left: 0; width: 100%; height: calc(100% - 30px);
  object-fit: cover; display: block; }
.bpk-cover--none { display: flex; align-items: center; justify-content: center; color: var(--muted);
  background: linear-gradient(160deg, var(--surface-2), var(--surface)); }
.bpk-cover--none svg { width: 44px; height: 44px; opacity: .6; }
/* −1px по бокам и снизу: панель перекрывает картинку с запасом, шва на краю не остаётся. */
.bpk-info { position: absolute; left: -1px; right: -1px; bottom: -1px; z-index: 1; padding: 16px 19px 15px;
  background: var(--bg); border-radius: 26px 26px 0 0; display: flex; flex-direction: column; gap: 8px; }
.bpk-name { text-align: center; font-size: 1.15em; font-weight: 500; line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bpk-ph { color: var(--muted); }
.bpk-desc { color: var(--text-dim); font-size: .84em; line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
  overflow-wrap: anywhere; }
.bpk-foot { display: flex; align-items: baseline; justify-content: flex-end; gap: 10px; font-size: .9em; }
.bpk-author { margin-right: auto; min-width: 0; color: var(--muted); font-size: .88em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bpk-count { white-space: nowrap; }
.bpk-acts { position: absolute; top: 10px; right: 10px; z-index: 2; display: flex; gap: 4px;
  opacity: 0; transition: opacity .15s; }
.bpk-acts .btn { background: color-mix(in srgb, var(--bg) 78%, transparent); }
.bpk-card:hover .bpk-acts, .bpk-card:focus-within .bpk-acts { opacity: 1; }
@media (hover: none) { .bpk-acts { opacity: 1; } }
/* Карточка, над которой держат доску: бросок кладёт доску в этот пак. */
.bpk-card.move-over { outline: 2px dashed var(--accent-border); outline-offset: 3px; }
/* Измерительная карточка: окно пака берёт у неё настоящую ширину карточки страницы. */
.bpk-probe { visibility: hidden; height: 0; overflow: hidden; pointer-events: none; margin: 0 !important; }

/* Плитка «+» в паке (pack-cards.js addTile): полупрозрачная, тонкая рамка, тонкий плюс. Высота —
   как у соседних карточек (ряд сетки растягивает), одна в пустом паке — min-height. */
.bpk-add { display: flex; align-items: center; justify-content: center; min-height: 290px;
  border-radius: 14px; border: 1px solid var(--border-2); cursor: pointer; color: var(--muted);
  background: color-mix(in srgb, var(--surface) 25%, transparent); padding: 0; }
/* Одна в пустом паке — пропорции карточки раздела (доска: квадратное превью + подписи; награда:
   квадратная сцена + подпись). Рядом с карточками высоту всё равно задаёт ряд сетки. */
.bpk-boards > .bpk-add { min-height: 0; aspect-ratio: 5 / 8; }
.bpk-rw > .bpk-add { min-height: 0; aspect-ratio: 4 / 5; }
.bpk-add svg { width: 42px; height: 42px; stroke-width: 1; }
.bpk-add:hover { color: var(--text); border-color: var(--accent-border);
  background: color-mix(in srgb, var(--surface) 45%, transparent); }

/* Страница пака */
.bpk-bar { display: flex; align-items: center; gap: 10px; min-width: 0; flex-wrap: wrap; }
.bpk-back { display: inline-flex; align-items: center; gap: 4px; padding-left: 8px; }
.bpk-back svg { width: 16px; height: 16px; }
.bpk-bar-title { font-size: 1.25em; font-weight: 500; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.bpk-lead { margin: 10px 0 0; color: var(--text-dim); font-size: .9em; line-height: 1.5; max-width: 70ch; }
.bpk-bar + .bpk-boards, .bpk-lead + .bpk-boards { margin-top: 18px; }
/* «Убрать из пака»: появляется только пока тащат доску. */
.bpk-out { display: none; margin-top: 14px; padding: 14px 18px; text-align: center; font-size: .88em;
  color: var(--muted); border: 1px dashed var(--border-2); border-radius: 14px; }
.bpk.move-active .bpk-out { display: block; }
.bpk-out.move-over { border-color: var(--accent-border); color: var(--text);
  background: color-mix(in srgb, var(--accent) 6%, transparent); }
.bpk-out + .bpk-boards { margin-top: 18px; }

/* Окно «Новая доска»: название, пак списком и в одну строку — видимость и «Создать». */
.modal:has(> .bnew) { max-width: 500px; }
.bnew { display: flex; flex-direction: column; gap: 12px; }
.bnew-pack--empty { color: var(--muted); }
.bnew-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 6px; }
.bnew-row .seg-btn { font-size: .86em; white-space: nowrap; }
.bnew-go { margin-left: auto; min-width: 120px; padding-top: 12px; padding-bottom: 12px; }

/* Окно «Новый пак»: слева поля, справа живое превью карточки. */
.modal:has(> .bpk-ed) { max-width: 880px; }
.bpk-ed { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 28px; align-items: start; }
.bpk-ed-form { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.bpk-ed-desc { min-height: 130px; resize: none; }
.bpk-ed-n { text-align: right; font-size: .82em; color: var(--muted); font-variant-numeric: tabular-nums;
  margin: 0 4px 8px; }
.bpk-ed-coverrow { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.bpk-ed-cover { width: 100%; padding: 22px 16px; border-radius: 18px; border: 0; cursor: pointer;
  background: var(--surface-2); color: var(--text); font: inherit; font-weight: 500; }
.bpk-ed-cover:hover { background: var(--border); }
.bpk-ed-cover:disabled { opacity: .6; cursor: default; }
.bpk-ed-cover-x { align-self: center; background: none; border: 0; color: var(--muted); cursor: pointer;
  font: inherit; font-size: .84em; padding: 2px 6px; }
.bpk-ed-cover-x:hover { color: var(--danger); }
.bpk-ed-vis { margin: 2px 0 12px; align-self: flex-start; }
.bpk-ed-vis .seg-btn { font-size: .86em; white-space: nowrap; }
.bpk-ed-submit { width: 100%; justify-content: center; padding-top: 14px; padding-bottom: 14px; }
/* Ширину задаёт JS: превью обязано быть РОВНО такой же карточкой, как на странице «Доски». */
.bpk-ed-side { min-width: 0; }
.bpk-ed-ph { text-align: center; margin-top: 8px; color: var(--muted); font-size: .9em; }
@media (max-width: 760px) {
  .bpk-ed { grid-template-columns: 1fr; gap: 18px; }
  .bpk-ed-side { order: -1; margin: 0 auto; }
}

/* Награды пака/школы/преподавателя — тот же ряд-карусель, что у досок. Пять в ряд, как в сетке. */
.rw-rail { display: flex; gap: 14px; overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity; padding: 2px 0 8px; scrollbar-width: thin; }
.rw-rail > .rw-card { flex: 0 0 max(184px, calc((100% - 4 * 14px) / 5)); scroll-snap-align: start;
  min-width: 0; box-sizing: border-box; }
.rw-card[data-move-id] { cursor: grab; user-select: none; -webkit-user-select: none;
  -webkit-touch-callout: none; }
.rwl-rail-nav { display: flex; gap: 4px; margin-left: auto; }
.rwl-rail-nav .btn { padding: 4px 8px; line-height: 1; }
.rwl-rail-nav .btn:disabled { opacity: .35; cursor: default; }
.rwl-rail-nav svg { width: 16px; height: 16px; display: block; }
.rwl-spare { display: none; }
.rwl.move-active .rwl-spare { display: block; }
.rwl-section.move-over { border-radius: 14px; background: color-mix(in srgb, var(--accent) 5%, transparent);
  box-shadow: 0 0 0 10px color-mix(in srgb, var(--accent) 5%, transparent);
  outline: 1.5px dashed var(--accent-border); outline-offset: 10px; }
.rwl-section.move-over .rwl-empty { border-color: var(--accent-border); color: var(--text-dim); }
/* Подпись карточки — кнопка (открывает медаль), но выглядит как .rw-name. */
.rwl-name { background: none; border: 0; padding: 0 4px; cursor: pointer; font: inherit;
  font-weight: 600; color: var(--text); max-width: 100%; }
.rwl-name:hover { color: var(--accent-text); }
.rwl-pack-row { display: flex; gap: 8px; align-items: center; }
.rwl-pack-row .select { flex: 1 1 auto; min-width: 0; }
.rwl-pack-row .btn { flex: 0 0 auto; white-space: nowrap; }
.rwl-pack-rename { padding: 8px 10px; line-height: 1; }
.rwl-pack-rename svg { width: 16px; height: 16px; display: block; }
/* Счётчик под текстом достижения: тихий, пока укладываемся; красный — у старого текста длиннее лимита. */
.rw-desc-count { margin-top: 6px; text-align: right; font-size: .76em; color: var(--muted);
  font-variant-numeric: tabular-nums; }
.rw-desc-count.over { color: var(--danger); }

@media (max-width: 760px) {
  .rw-strip { gap: 8px; }
  .rw-strip > .rw-tile { flex-basis: calc((100% - 3 * 8px) / 4); }
  .rw-pick { grid-template-columns: repeat(4, 1fr); gap: 8px; max-height: 46vh; }
  .rw-tile-name { font-size: .72em; }
  .rate-award { margin-right: 0; }
  /* ⚠️ nowrap обязателен: у МНОГОСТРОЧНОГО колоночного flex ширина строки — это ширина
     самого широкого элемента, и растянутые чипы (одна прокручиваемая строка) раздували окно
     выдачи за край экрана. У однострочного ширина строки — ширина контейнера. */
  .rw-pick-nav { flex-direction: column; align-items: stretch; flex-wrap: nowrap; }
  .rw-src { width: 100%; }
  .rw-src .mat-scope-btn { flex: 1 1 0; justify-content: center; padding: 9px 8px; font-size: .8em; }
  .rw-chips { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 2px; }
  .rw-chip { flex: 0 0 auto; }
  .rwl-bar { margin-top: 0; }
}

/* ── Алмазы ────────────────────────────────────────────────────────
   Учитель дарит, ученик копит и меняет на урок (frontend/js/views/diamonds.js).
   Цвет — из палитры школы (--badge-accent), как у баланса уроков: это два счёта
   одного и того же, и разными цветами они читались бы как разные сущности. */
.dm-ico { display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; flex: 0 0 auto; color: var(--badge-accent); }
.dm-ico svg, .dm-ico img { width: 100%; height: 100%; }
/* Своя картинка школы может быть любых пропорций — вписываем, а не растягиваем. */
img.dm-ico { object-fit: contain; }
.dm-ico-lg { width: 26px; height: 26px; }
.dm-ico-xl { width: 44px; height: 44px; }
.dm-count { display: inline-flex; align-items: center; gap: 5px;
  font-variant-numeric: tabular-nums; color: var(--badge-accent); }

/* Кошелёк на главной ученика — ПРАВАЯ ПОЛОВИНА общей плашки с балансом уроков
   (см. .sched-bal). Своей рамки и заливки у него нет: пилюля внутри пилюли выглядела
   бы вложенной кнопкой, а два одинаковых овала подряд — повтором. */
.dm-box { display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  position: relative; padding: 2px 10px 2px 11px; margin-left: 10px;
  border-radius: 99px; font-family: inherit; font-size: 1rem;
  background: transparent; border: 0; transition: background .14s, transform .1s; }
/* Разделитель — ::before, а не border-left: у рамки нет отступа, и черта прилипла бы
   к значку алмаза; здесь у неё воздух с обеих сторон. */
.dm-box::before { content: ''; position: absolute; left: -5px; top: 1px; bottom: 1px;
  width: 1px; background: var(--border-2); }
.dm-box:hover { background: color-mix(in srgb, var(--badge-accent) 18%, transparent); }
.dm-box:active { transform: scale(.97); }
.dm-box-num { font-weight: 700; color: var(--badge-accent); font-variant-numeric: tabular-nums; }
/* «Потратить» загорается ровно тогда, когда обмен возможен — ученику не нужно помнить
   курс и сравнивать числа самому.
   ⚠️ Это ЕДИНСТВЕННЫЙ признак накопленного: подсветка всего сегмента заливкой была и
   снята по решению владельца продукта («убери красную таблетку, в которой находятся
   алмазы») — на неакцентной плашке она читалась как предупреждение. Поэтому метка
   видна и на мобиле: скрывать её, как раньше, теперь нечем заменить. */
.dm-box-flag { font-size: .62em; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; padding: 2px 6px; border-radius: 99px;
  background: var(--badge-accent); color: var(--on-accent); }

/* Окно подарка */
.dm-gift-who { margin: 0 0 14px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.dm-amount { display: flex; align-items: center; justify-content: center; gap: 16px; margin: 4px 0 10px; }
.dm-amount-val { display: inline-flex; align-items: center; gap: 8px; min-width: 92px; justify-content: center; }
.dm-amount-num { font-size: 2em; font-weight: 700; color: var(--badge-accent);
  font-variant-numeric: tabular-nums; line-height: 1; }
.dm-step { width: 38px; height: 38px; padding: 0; border-radius: 50%; font-size: 1.3em; line-height: 1; }
.dm-gift-total { margin: 0; text-align: center; }
.dm-msg { line-height: 1.5; margin: 0 0 14px; }

/* Окно кошелька ученика */
.dm-wallet-top { display: flex; align-items: center; gap: 14px; margin-bottom: 12px; }
.dm-wallet-num { font-size: 2.1em; font-weight: 700; line-height: 1;
  color: var(--badge-accent); font-variant-numeric: tabular-nums; }
.dm-bar { height: 8px; border-radius: 99px; background: var(--border-2); overflow: hidden; }
.dm-bar-fill { height: 100%; border-radius: 99px; background: var(--progress);
  transition: width .3s ease; }
.dm-wallet-note { margin: 12px 0 0; line-height: 1.5; }

/* Настройки: свой значок алмаза */
.dm-img-row { display: flex; align-items: center; gap: 16px; margin-top: 10px; flex-wrap: wrap; }
.dm-img-preview { display: flex; align-items: center; justify-content: center;
  width: 64px; height: 64px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-2); background: var(--surface-2); }
.dm-img-acts { display: flex; gap: 8px; flex-wrap: wrap; }

/* В поп-апе оценки урока кнопок может быть две (награда и алмазы) — влево уходит
   только первая, иначе свободное место делится между ними и «Позже» уезжает в центр. */
.rate-award ~ .rate-award { margin-right: 0; }

@media (max-width: 760px) {
  .sched-bal { padding: 2px 2px; }
  .dm-box { gap: 5px; padding: 2px 6px 2px 7px; margin-left: 7px; }
  .dm-box::before { left: -4px; }
  /* ⚠️ Метка «потратить» на мобиле ОСТАЁТСЯ (раньше пряталась): подсветки сегмента
     больше нет, и без метки состояние «накопилось» не выражено ничем. Ужимаем её,
     а не скрываем; если строка всё же не влезет, часы уедут на вторую строку
     (`.sched-page` — flex-перенос) — это лучше, чем потерянный сигнал. */
  .dm-box-flag { font-size: .58em; padding: 2px 5px; }
}

/* ══ Чат: галочки, даты, «печатает…», попап о сообщении ══════════════
   Общие детали ОБОИХ чатов платформы — комнаты класса и мессенджера (js/chatui.js).
   Держим их в одном месте по той же причине, что и сам модуль: галочка «прочитано»
   не может выглядеть в двух чатах по-разному. */

/* ── Галочки доставки (Telegram-style) ───────────────────────────── */
.msg-ticks { flex: 0 0 auto; display: inline-flex; align-items: center; line-height: 0; opacity: .75; }
.msg-ticks svg { width: 13px; height: 13px; display: block; }
.msg-ticks.is-sending { opacity: .55; }
/* «Прочитано» — акцентом: это единственное состояние, которое человек ищет глазами. */
.msg-ticks.is-read { color: var(--accent-text); opacity: 1; }
/* «Не доставлено» — цвет ошибки и НЕ приглушено: его нельзя пропустить. */
.msg-ticks.is-failed { color: var(--danger); opacity: 1; }
.msg-ticks.retry { cursor: pointer; }
.msg-ticks.retry:hover { transform: scale(1.12); }
/* В своём пузыре (заливка акцентом) галочки берут цвет текста пузыря — иначе
   «прочитано» акцентом на акценте пропадало бы. Ошибка остаётся красной всегда. */
.bubble-row.mine .msg-ticks, .room-msg.mine .msg-ticks { color: var(--on-chat-mine); }
.bubble-row.mine .msg-ticks.is-failed, .room-msg.mine .msg-ticks.is-failed { color: var(--danger); }

/* ── Ссылки и карточка превью (сообщения администратора) ────────── */
/* Цвета — от currentColor пузыря и --accent-text: пузырь «своих» в светлой теме
   перекрашен color-mix'ом (см. блок светлой темы), и литеральный цвет там пропадал бы. */
.msg-link { color: var(--accent-text); text-decoration: underline; text-underline-offset: 2px;
  overflow-wrap: anywhere; }
.bubble-row.mine .msg-link, .room-msg.mine .msg-link { color: inherit; }
.msg-lp { display: block; max-width: 320px; margin-top: 6px; padding: 5px 10px 8px;
  border-left: 3px solid var(--accent); border-radius: 4px 8px 8px 4px;
  background: color-mix(in srgb, currentColor 7%, transparent);
  color: inherit; text-decoration: none; white-space: normal; overflow: hidden; }
.msg-lp:hover { background: color-mix(in srgb, currentColor 11%, transparent); }
.bubble-row.mine .msg-lp, .room-msg.mine .msg-lp { border-left-color: currentColor; }
.msg-lp-site { font-size: .76em; font-weight: 600; color: var(--accent-text); }
.bubble-row.mine .msg-lp-site, .room-msg.mine .msg-lp-site { color: inherit; opacity: .8; }
.msg-lp-title, .msg-lp-desc { display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
.msg-lp-title { font-weight: 600; line-height: 1.3; margin-top: 1px; -webkit-line-clamp: 2; }
.msg-lp-desc { font-size: .88em; line-height: 1.35; margin-top: 2px; opacity: .8; -webkit-line-clamp: 3; }
/* Высота картинки задана заранее: догрузившись, она не сдвигает ленту. */
.msg-lp-img { display: block; width: 100%; height: 130px; object-fit: cover; margin-top: 6px;
  border-radius: 6px; background: color-mix(in srgb, currentColor 8%, transparent); }

/* ── Разделитель дней в ленте ────────────────────────────────────── */
.msg-day { display: flex; justify-content: center; margin: 6px 0 2px; }
.msg-day-lbl { font-size: .68em; color: var(--muted); letter-spacing: .3px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 99px; padding: 2px 10px; }
/* ⚠️ Лента комнаты прижимает содержимое к низу через `> :first-child { margin-top: auto }`.
   Первым элементом теперь может оказаться разделитель дня — правило должно достаться и ему,
   иначе при паре сообщений они прилипли бы к верху ленты. Селектор уже по :first-child,
   поэтому специально ничего не нужно; здесь только напоминание для будущих правок. */

/* ── Мессенджер: время + галочки одной строкой ───────────────────── */
.bubble-meta { display: flex; align-items: center; justify-content: flex-end; gap: 4px; margin-top: 3px; }
.bubble-meta .bubble-time { display: inline; margin-top: 0; }
.bubble-row.pending .bubble { opacity: .75; }   /* пока сервер не подтвердил — чуть приглушённо */

/* ── «Собеседник печатает…» ──────────────────────────────────────── */
/* Мессенджер: статус ПОДМЕНЯЕТ подпись роли под именем (обе строки в одном потоке —
   шапка не прыгает по высоте, когда статус появляется и исчезает). */
.chat-h-typing { display: none; font-size: .72em; color: var(--accent-text); align-items: center; gap: 5px; }
.chat-h-typing.on { display: flex; }
.chat-h-typing.on ~ .chat-h-role { display: none; }
/* Комната: тонкая строка над лентой, внутри карточки чата. */
.room-chat-typing { flex: 0 0 auto; display: none; align-items: center; justify-content: center; gap: 6px;
  padding: 4px 40px 3px; font-size: .7em; color: var(--accent-text);
  border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--accent) 6%, transparent); }
.room-chat-typing.on { display: flex; }
/* Три «дышащие» точки. Анимируется только opacity — композитор, не грузит слабые устройства. */
.chat-typing-dots, .room-chat-typing-dots { display: inline-flex; gap: 3px; align-items: center; }
.chat-typing-dots i, .room-chat-typing-dots i {
  width: 4px; height: 4px; border-radius: 50%; background: currentColor; opacity: .35;
  animation: typing-dot 1.2s ease-in-out infinite;
}
.chat-typing-dots i:nth-child(2), .room-chat-typing-dots i:nth-child(2) { animation-delay: .18s; }
.chat-typing-dots i:nth-child(3), .room-chat-typing-dots i:nth-child(3) { animation-delay: .36s; }
@keyframes typing-dot { 0%, 60%, 100% { opacity: .3; } 30% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .chat-typing-dots i, .room-chat-typing-dots i { animation: none; opacity: .7; }
}

/* ── Попап о новом сообщении ─────────────────────────────────────── */
/* Свой слой в body (не #modal-root — его чистит роутер, см. chatui.js).
   z-index выше чата на весь экран (200) и виджета звонка (120): попап всплывает как раз
   тогда, когда чат свёрнут, но поверх остального интерфейса он быть обязан. */
.chat-pop-root { position: fixed; right: 18px; bottom: 18px; z-index: 260;
  display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
  pointer-events: none;   /* сам слой кликам не мешает — их ловят только карточки */
  max-width: min(340px, calc(100vw - 36px)); }
.chat-pop {
  pointer-events: auto; width: 100%; display: flex; align-items: flex-start; gap: 8px;
  padding: 9px 10px 9px 12px; border-radius: 12px;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  border: 1px solid var(--border-2); box-shadow: var(--shadow);
  backdrop-filter: blur(10px) saturate(150%); -webkit-backdrop-filter: blur(10px) saturate(150%);
  /* Стартовое состояние — уехал вправо и прозрачен; класс .in включает переход (см. chatui.js). */
  opacity: 0; transform: translateX(14px); transition: opacity .22s ease, transform .22s ease;
}
.chat-pop.in { opacity: 1; transform: none; }
.chat-pop.out { opacity: 0; transform: translateX(14px); }
.chat-pop.clickable { cursor: pointer; }
.chat-pop.clickable:hover { border-color: var(--accent-border); }
.chat-pop-main { flex: 1 1 auto; min-width: 0; }
.chat-pop-name { font-size: .8em; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Мелкий текст в две строки максимум: попап — приглашение открыть чат, а не сам чат. */
.chat-pop-text { margin-top: 2px; font-size: .74em; line-height: 1.35; color: var(--text-dim);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.chat-pop-x { flex: 0 0 auto; background: none; border: none; color: var(--muted); cursor: pointer;
  font-size: .8em; padding: 2px 4px; line-height: 1; border-radius: 6px; }
.chat-pop-x:hover { color: var(--text); background: var(--surface-2); }
/* Уведомление глобального опроса входящих (app.js → pollInbox): висит, пока не закроют, —
   поэтому крупнее короткого попапа чата и с аватаром отправителя, как в Telegram. */
.chat-pop.inbox { width: 340px; max-width: 100%; align-items: center; gap: 10px; padding: 10px 10px 10px 12px; }
.chat-pop.inbox .chat-pop-name { font-size: .86em; }
.chat-pop.inbox .chat-pop-text { font-size: .8em; margin-top: 3px; }
.chat-pop.inbox .chat-pop-x { align-self: flex-start; font-size: .86em; padding: 3px 5px; }
.avatar.chat-pop-av { width: 38px; height: 38px; font-size: .9em; flex: 0 0 auto; }
/* В виртуальном классе уведомления не показываем (там свой чат); висящие с других страниц
   прячем, а не закрываем — выйдя из класса, человек их увидит. */
body.room-view .chat-pop.inbox { display: none; }
/* В мессенджере правый нижний угол занят строкой ввода и кнопкой «Отправить» — поднимаем
   уведомления над ней, иначе карточка перекрывает кнопку. */
body.msg-view .chat-pop-root { bottom: 84px; }
@media (max-width: 760px) {
  /* На телефоне чат-дока прижата к низу экрана — попап уводим наверх, иначе он ложится
     прямо на строку ввода и закрывает её. */
  .chat-pop-root, body.msg-view .chat-pop-root { right: 10px; left: 10px; bottom: auto; top: 10px; max-width: none; }
  .chat-pop { padding: 8px 9px 8px 11px; }
}

/* ── Комната: цитата ответа, пересылка, действия над сообщением ──── */
.room-msg-meta { display: flex; align-items: center; justify-content: flex-end; gap: 4px; margin-top: 3px; }
.room-msg-time { font-size: .82em; color: var(--muted); }
.room-msg.mine .room-msg-time { color: color-mix(in srgb, var(--on-chat-mine) 62%, transparent); }
.room-msg-fwd { display: flex; align-items: center; gap: 4px; font-size: .84em; color: var(--accent-text); margin-bottom: 3px; }
.room-msg-fwd svg { width: 11px; height: 11px; flex: 0 0 auto; }
.room-msg-quote { display: flex; flex-direction: column; gap: 1px; padding: 3px 8px; margin-bottom: 4px;
  max-width: 100%; cursor: pointer; border-left: 2px solid var(--accent); border-radius: 0 6px 6px 0;
  background: color-mix(in srgb, var(--accent) 9%, transparent); }
.room-msg-quote-name { font-size: .82em; color: var(--accent-text); font-weight: 600; }
.room-msg-quote-text { font-size: .88em; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.room-msg.mine .room-msg-quote-text { color: color-mix(in srgb, var(--on-chat-mine) 78%, transparent); }
/* Три точки у пузыря — ЕДИНСТВЕННАЯ точка входа в действия над сообщением (было две
   кнопки-стрелки «ответить/переслать» прямо в ленте; убраны по макету владельца продукта).
   ⚠️ Видны ВСЕГДА, а не только при наведении: на тач-устройствах :hover не наступает, а
   другого способа открыть меню нет — долгое нажатие занято выделением текста и переводом
   слова по тапу. Приглушены, чтобы не спорить с самим сообщением. */
.room-msg-acts { display: inline-flex; flex: 0 0 auto; align-self: center; order: 2; }
.room-msg.mine .room-msg-acts { order: -1; }
.room-msg-menu { background: none; border: none; color: var(--muted); cursor: pointer; padding: 3px;
  border-radius: 6px; display: grid; place-items: center; opacity: .5; transition: opacity .12s, color .12s, background .12s; }
.room-msg:hover .room-msg-menu { opacity: 1; }
.room-msg-menu:hover { color: var(--accent-text); background: var(--surface-2); }
.room-msg-menu svg { display: block; }
@media (hover: none) { .room-msg-menu { opacity: .7; } }

/* Меню сообщения. Лежит в body (чат бывает узкой колонкой и плавающей карточкой — из
   обеих меню вылезало бы за край), поэтому позиционируется fixed из JS (placeMsgMenu).
   z-index выше полноэкранного чата (200), как у попапа перевода слова. */
.msg-menu {
  position: fixed; z-index: 220; min-width: 176px; padding: 5px;
  display: flex; flex-direction: column; gap: 1px;
  background: var(--surface-2); border: 1px solid var(--border-2);
  border-radius: 12px; box-shadow: var(--shadow); animation: modal-in .14s ease;
}
.msg-menu-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px;
  background: none; border: none; border-radius: 8px; cursor: pointer;
  color: var(--text); font-size: .84em; text-align: left; }
.msg-menu-item:hover { background: var(--surface); color: var(--accent-text); }
/* Удаление — единственный необратимый пункт меню, поэтому единственный красный.
   ⚠️ Правило стоит ПОСЛЕ :hover: специфичность у них одинаковая (0,2,0), и выше по
   файлу наведение перекрашивало бы его обратно в акцент. */
.msg-menu-item.danger, .msg-menu-item.danger:hover { color: var(--danger); }
.msg-menu-item.danger .msg-menu-ico, .msg-menu-item.danger:hover .msg-menu-ico { color: inherit; }
.msg-menu-ico { flex: 0 0 auto; display: grid; place-items: center; width: 18px; color: var(--muted); }
.msg-menu-item:hover .msg-menu-ico { color: inherit; }
.msg-menu-ico svg { display: block; }

/* Перевод всей реплики — под текстом, отделён линией: это не часть сообщения. */
.room-msg-tr { margin-top: 5px; padding-top: 5px; font-size: .95em; line-height: 1.4;
  border-top: 1px dashed var(--border-2); color: var(--text-dim); }
.room-msg.mine .room-msg-tr { border-top-color: color-mix(in srgb, var(--on-chat-mine) 30%, transparent);
  color: color-mix(in srgb, var(--on-chat-mine) 82%, transparent); }
.room-msg-tr.loading { opacity: .7; font-style: italic; }

/* Закреплённое: значок в строке времени + тонкая рамка у пузыря (полоса наверху
   показывает только одно из закреплённых — по ленте должно быть видно все). */
.room-msg-pin { display: inline-flex; color: var(--accent-text); }
.room-msg-pin svg { width: 11px; height: 11px; display: block; }
.room-msg.mine .room-msg-pin { color: color-mix(in srgb, var(--on-chat-mine) 78%, transparent); }
.room-msg-body.pinned { border-color: var(--accent-border); }
.room-msg.mine .room-msg-body.pinned { box-shadow: 0 0 0 1px color-mix(in srgb, var(--on-chat-mine) 32%, transparent); }

/* Полоса закреплённых над лентой: одна строка (колонка чата узкая), клик листает
   закреплённые по кругу и прокручивает к сообщению. */
.room-chat-pinned { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; cursor: pointer;
  padding: 6px 10px; border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--accent) 7%, var(--surface)); }
.room-chat-pinned.hidden { display: none; }
.room-chat-pinned:hover { background: color-mix(in srgb, var(--accent) 12%, var(--surface)); }
.room-pin-ico { flex: 0 0 auto; display: inline-flex; color: var(--accent-text); }
.room-pin-main { flex: 1 1 auto; min-width: 0; }
.room-pin-name { font-size: .68em; font-weight: 600; color: var(--accent-text); }
.room-pin-text { font-size: .74em; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.room-pin-x { flex: 0 0 auto; background: none; border: none; color: var(--muted); cursor: pointer;
  font-size: .78em; line-height: 1; padding: 3px 4px; border-radius: 6px; }
.room-pin-x:hover { color: var(--text); background: var(--surface-2); }

/* Режим выделения: галочка у пузыря, весь ряд — переключатель. */
.room-msg-check { flex: 0 0 auto; align-self: center; width: 18px; height: 18px; margin: 0 6px;
  border: 1.5px solid var(--border-2); border-radius: 50%; display: grid; place-items: center;
  color: var(--on-accent); }
.room-msg-check.on { background: var(--accent); border-color: var(--accent); }
.room-msg-check svg { width: 11px; height: 11px; display: block; }
.room-chat-feed.selecting .room-msg { cursor: pointer; border-radius: 8px; }
.room-chat-feed.selecting .room-msg.picked { background: color-mix(in srgb, var(--accent) 10%, transparent); }
/* ⚠️ Пока идёт выбор, слова НЕ кликабельны: иначе тап по слову открывал бы попап перевода
   вместо того, чтобы отметить сообщение (обработчик перевода делегирован на ленту и
   ищет .tw — с pointer-events:none целью станет сам пузырь). */
.room-chat-feed.selecting .tw { pointer-events: none; }
.room-chat-feed.selecting .room-msg-body { user-select: none; }

/* Панель выделения — ЗАМЕНЯЕТ строку ввода (в узкой колонке две панели не помещаются).
   ⚠️ Имя `-selbar`, а не `-sel`: `.room-chat-sel` — это селектор ученика выше по файлу. */
.room-chat-selbar { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; padding: 8px 10px;
  border-top: 1px solid var(--border); }
.room-chat-selbar.hidden { display: none; }
.room-sel-n { flex: 1 1 auto; min-width: 0; font-size: .78em; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.room-sel-act { flex: 0 0 auto; width: 32px; height: 32px; border-radius: 8px; cursor: pointer;
  display: grid; place-items: center; color: var(--text); background: var(--surface-2); border: 1px solid var(--border-2); }
.room-sel-act:hover:not([disabled]) { color: var(--accent-text); border-color: var(--accent-border); }
.room-sel-act[disabled] { opacity: .45; cursor: default; }
.room-sel-act.danger:hover:not([disabled]) { color: var(--danger); border-color: var(--danger); }
.room-sel-act svg { display: block; }
.room-sel-x { flex: 0 0 auto; width: 32px; height: 32px; border-radius: 8px; cursor: pointer;
  background: none; border: none; color: var(--muted); font-size: .85em; }
.room-sel-x:hover { color: var(--text); background: var(--surface-2); }
@media (max-width: 760px) { .room-sel-act, .room-sel-x { width: 36px; height: 36px; } }
/* Полоса ответа над строкой ввода (аналог .reply-bar мессенджера, но компактнее — колонка узкая) */
.room-chat-reply { flex: 0 0 auto; display: flex; align-items: center; gap: 7px; padding: 6px 10px;
  border-top: 1px solid var(--border); background: var(--surface); }
.room-chat-reply.hidden { display: none; }
.room-reply-ico { flex: 0 0 auto; color: var(--accent-text); display: inline-flex; }
.room-reply-main { flex: 1 1 auto; min-width: 0; border-left: 2px solid var(--accent); padding-left: 7px; }
.room-reply-name { font-size: .7em; font-weight: 600; color: var(--accent-text); }
.room-reply-text { font-size: .74em; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.room-reply-x { flex: 0 0 auto; background: none; border: none; color: var(--muted); cursor: pointer;
  font-size: .78em; padding: 3px 5px; border-radius: 6px; }
.room-reply-x:hover { color: var(--danger); }

/* ── Обои чата (фон переписки) ─────────────────────────────────────
   Картинки школы загружает администратор, а выбирают их СОБЕСЕДНИКИ — обои ОДНИ НА ЧАТ:
   кто выбрал последним, того и видят оба (см. js/chatbg.js).
   ⚠️ Фон вешается НА САМ ПРОКРУЧИВАЕМЫЙ БЛОК ленты, а не отдельным слоем внутри него:
   у прокручиваемого блока фон по умолчанию прижат к рамке и не едет вместе с сообщениями —
   именно этого и ждут от обоев. Слой внутри пришлось бы удерживать на месте на каждый скролл.
   ⚠️ Затемнение — ПЕРВЫМ слоем фона (градиент из одного цвета поверх картинки), а не
   отдельным элементом: лишний элемент перехватывал бы клики по сообщениям и требовал бы
   своего z-index в двух разных чатах. --chat-bg-dim — единственное место, где живут «20%». */
:root { --chat-bg-dim: .2; }
.chat-bg-on {
  background-image: linear-gradient(rgba(0, 0, 0, var(--chat-bg-dim)), rgba(0, 0, 0, var(--chat-bg-dim))),
                    var(--chat-bg-img, none);
  background-size: cover; background-position: center; background-repeat: no-repeat;
}
/* ⚠️ Пузыри, разделитель дней (.msg-day-lbl) и полоса закреплённых читаются на фото и без
   правок: у всех непрозрачная заливка от --surface*. Специально «поднимать» их не нужно. */

/* Кнопка «Фон» в комнате — ТЕКСТОВАЯ (не иконка: картинка на кнопке выбора картинки
   читалась двусмысленно), поэтому вместо круга — «пилюля» с подписью. Тот же «стеклянный»
   вид, что у сворачивания (класс .room-chat-min навешивается вместе, см. chatBgButton).
   ⚠️ `right` считается от ЛЕВОГО края кнопки «свернуть» (32px/34px + зазор 6px) — она
   растёт влево от этой точки, а не вправо, так что ширина текста её не сдвигает. */
/* ⚠️ font-family явно: .room-chat-min — голая кнопка без текста (только иконки/«›»), поэтому
   базовое правило шрифт не задаёт; здесь кнопка ВПЕРВЫЕ несёт подпись — без этого браузер
   отрисовал бы её своим дефолтным Arial вместо Inter (проверено, реальный разнобой). */
.room-chat-bg { right: 53px; width: auto; height: 30px; padding: 0 12px; border-radius: 15px;
  font-family: var(--font); font-size: .72em; font-weight: 600; white-space: nowrap; letter-spacing: .1px; }
/* ⚠️ В режиме доски кнопки «Фон» НЕТ ВООБЩЕ (решение владельца продукта) — плавающий чат
   и так тесный (см. .room-chat ниже — ширина/высота урезаны под холст доски), а обои
   переписки правятся из мессенджера/обычного вида чата. Правило было позиционирующим
   (top/right под другую раскладку) — теперь просто прячет кнопку, а не подгоняет её. */
body.room-board-fs .room-chat-bg { display: none; }
/* Строка селектора ученика делит верх чата с этими плавающими кнопками — резервируем им
   место, чтобы имя в <select> не залезало под «Фон»/«свернуть». Значение подобрано под
   короткую подпись — проверено замером в браузере (см. блок «Обои чата» в CLAUDE.md). */
.room-chat-selrow { padding-right: 118px; }

/* Выбор фона: небольшие превью. Живёт в body (fixed) — чат бывает узкой колонкой и
   плавающей карточкой, изнутри окно вылезало бы за край. */
.chatbg-pop {
  position: fixed; z-index: 220; width: 268px; max-width: calc(100vw - 16px); padding: 10px;
  background: var(--surface-2); border: 1px solid var(--border-2);
  border-radius: 12px; box-shadow: var(--shadow); animation: modal-in .14s ease;
}
.chatbg-title { font-size: .78em; font-weight: 600; color: var(--text-dim); margin-bottom: 3px; }
/* Подпись под заголовком: обои общие на чат, и кто выбрал текущие. Без неё «фон сменился
   сам, я ничего не делал» читалось бы как поломка (см. chatbg.js openChatBgPicker). */
.chatbg-note { font-size: .66em; line-height: 1.35; color: var(--muted); margin-bottom: 8px; }
.chatbg-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px;
  max-height: 300px; overflow: auto; }
.chatbg-tile { position: relative; aspect-ratio: 3 / 4; border-radius: 8px; cursor: pointer;
  border: 1.5px solid var(--border-2); background-size: cover; background-position: center;
  padding: 0; overflow: hidden; transition: border-color .15s, transform .15s;
  font-family: var(--font); }   /* плитка — <button>: без этого её подписи ушли бы в Arial */
.chatbg-tile:hover { border-color: var(--accent-border); transform: translateY(-1px); }
.chatbg-tile.on { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent); }
/* Картинку загрузил СОБЕСЕДНИК: видно, что стоит в чате, но взять её себе нельзя —
   сервер примет ссылку только из каталога школы либо из папки самого выбирающего. */
.chatbg-tile[disabled] { cursor: default; }
.chatbg-tile[disabled]:hover { border-color: var(--accent); transform: none; }
/* «Без фона» — плитка с подписью вместо картинки (первая в списке, всегда доступна). */
.chatbg-tile.none { display: grid; place-items: center; background: var(--surface);
  color: var(--muted); font-size: .68em; text-align: center; padding: 4px; }
/* Метка «по умолчанию» — на плитке школьного фона, который так назначен. */
.chatbg-tile-badge { position: absolute; left: 4px; bottom: 4px; right: 4px; padding: 2px 6px;
  border-radius: 6px; font-size: .62em; text-align: center; color: #fff;
  background: color-mix(in srgb, var(--accent) 75%, #000 15%); }
/* «Свой фон» — плитка-загрузчик (пунктирная рамка, пока своей картинки нет) либо превью
   уже загруженной своей картинки (сплошная рамка, как у остальных плиток). */
.chatbg-tile.own { display: grid; place-items: center; background: var(--surface);
  color: var(--muted); font-size: .68em; text-align: center; padding: 4px; border-style: dashed; }
.chatbg-tile.own.on { border-style: solid; color: #fff; }
.chatbg-tile-own-lbl { padding: 2px 6px; border-radius: 6px; }
.chatbg-tile.own.on .chatbg-tile-own-lbl {
  background: color-mix(in srgb, #000 55%, transparent); }

/* Админ: список загруженных фонов («Настройки → Школа → Внешний вид»). Клик по картинке —
   назначить «по умолчанию» (не крестик — он только удаляет, см. theme-settings.js). */
.chatbg-adm-acts { display: flex; gap: 8px; align-items: center; }
.chatbg-admin { display: grid; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 10px; }
.chatbg-adm-tile { position: relative; aspect-ratio: 4 / 3; border-radius: 10px; cursor: pointer;
  border: 1.5px solid var(--border-2); background-size: cover; background-position: center;
  transition: border-color .15s; }
.chatbg-adm-tile:hover { border-color: var(--accent-border); }
.chatbg-adm-tile.default { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent); }
.chatbg-adm-badge { position: absolute; left: 5px; bottom: 5px; right: 5px; padding: 2px 6px;
  border-radius: 6px; font-size: .66em; text-align: center; color: #fff;
  background: color-mix(in srgb, var(--accent) 75%, #000 15%); }
.chatbg-adm-del { position: absolute; top: 5px; right: 5px; width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center; cursor: pointer; font-size: .72em; line-height: 1;
  color: #fff; background: rgba(0, 0, 0, .55); border: 1px solid rgba(255, 255, 255, .25); }
.chatbg-adm-del:hover { background: var(--danger); border-color: transparent; }

/* ─────────────────────────────────────────────────────────────────
   ВИТРИНА ПРЕПОДАВАТЕЛЕЙ (#/teachers) — подбор учителя ученику.
   Карточки анкет + поп-ап профиля с расписанием (views/teachers.js).
   ───────────────────────────────────────────────────────────────── */
/* Карточка по макету владельца продукта — КРУПНАЯ: прежние четыре в ряд были мелкими,
   текст «о себе» и совпадения читались с трудом (жалоба «тяжело читать»). Колонка не уже
   400px — на ширине платформы (1180) это две карточки в ряд, на телефоне — одна. */
.tg-grid { display: grid; gap: 24px 30px;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 400px), 1fr)); }

/* ── Карточка: лицевая часть + язычок «открыть» ──
   Карточка целиком — <button> (кликается вся площадь, работает с клавиатуры). Своего
   фона и рамки у неё нет: видны только лицо и выглядывающий из-под него язычок.
   ⚠️ Лицо сдвинуто от правого края на ширину язычка (--tg-tab), а сам язычок шире на
   радиус скругления: его левая часть уходит ПОД скруглённый угол лица, и в этом углу
   виден перелив — ровно как на макете. */
.tg-card { --tg-tab: 44px; --tg-r: 26px; --tg-face: var(--surface);
  position: relative; display: flex; align-items: stretch; width: 100%;
  padding: 0; margin: 0; border: 0; background: none; cursor: pointer; text-align: left;
  font-family: var(--font); color: var(--text); -webkit-tap-highlight-color: transparent; }
.tg-card:focus-visible { outline: none; }
.tg-card:focus-visible .tg-face { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Язычок: перелив тем же шейдером и той же палитрой, что «Войти в класс» (js/liquid.js,
   общий контекст на всю витрину). Фон ниже — фолбэк без WebGL: те же цвета, медленно
   текущие по высоте. */
.tg-tab { position: absolute; top: 0; bottom: 0; right: 0; z-index: 0; overflow: hidden;
  width: calc(var(--tg-tab) + var(--tg-r) + 8px); border-radius: var(--tg-r);
  background: linear-gradient(180deg, #a01a54, #d92d6b 30%, #e8703f 58%, #2bb3a8 82%, #a01a54) 0 0 / 100% 300%;
  animation: tg-tab-flow 14s linear infinite;
  transition: transform .28s cubic-bezier(.22, 1, .36, 1), filter .2s ease; }
.tg-tab.has-liquid { animation: none; }
@keyframes tg-tab-flow { to { background-position: 0 100%; } }
/* Подпись — снизу вверх, как на макете. Белая с мягкой тенью, как на «Войти в класс»:
   на тёмной фуксии перелива чёрный текст макета терял бы контраст (≈2.8:1). */
.tg-tab-label { position: absolute; top: 0; bottom: 0; right: 0; z-index: 1; width: var(--tg-tab);
  display: flex; align-items: center; justify-content: center;
  writing-mode: vertical-rl; transform: rotate(180deg);
  font-weight: 800; font-size: 1.08em; letter-spacing: .14em; color: #fff;
  text-shadow: 0 1px 5px rgba(0, 0, 0, .45), 0 0 1px rgba(0, 0, 0, .35); }
/* Наведение и нажатие — язычок «оттягивается» вперёд (макет): при наведении слегка, при
   нажатии сильнее, и только потом открывается анкета (PULL_MS в teachers.js). */
.tg-card:hover .tg-tab, .tg-card:focus-visible .tg-tab { transform: translateX(8px);
  filter: brightness(1.06) saturate(1.06); }
.tg-card:active .tg-tab, .tg-card.pulling .tg-tab { transform: translateX(14px); }

.tg-face { position: relative; z-index: 1; flex: 1 1 auto; min-width: 0;
  margin-right: var(--tg-tab); display: flex; flex-direction: column;
  padding: 30px 30px 18px; border-radius: var(--tg-r); background: var(--tg-face);
  transition: background-color .2s ease; }
.tg-card:hover { --tg-face: color-mix(in srgb, var(--surface) 94%, var(--text)); }

/* Шапка: имя, язык и опыт слева, портрет справа — по высоте ровно на эти три строки,
   а правый край портрета совпадает с правым краем текста и «Попаданий» (сетка макета). */
.tg-head { display: flex; align-items: flex-start; gap: 16px; }
.tg-head-main { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 8px;
  padding-top: 2px; }
.tg-name { font-weight: 700; font-size: 1.65em; line-height: 1.12; letter-spacing: -.01em;
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; }
.tg-lang { font-size: 1.12em; letter-spacing: .04em; color: var(--accent-text); }
.tg-exp { font-size: 1.05em; color: var(--muted); }
/* Шильдики совпадений — две маленькие пилюли под «Опытом» в левой колонке шапки (макет
   владельца продукта). Это те же два совпадения, что строки «Попаданий» ниже, только
   видные с первого взгляда. Не влезли в колонку рядом с портретом — переносятся друг под
   друга (flex-wrap), а не вылезают на портрет. */
.tg-badges { display: flex; flex-wrap: wrap; gap: 6px 10px; margin-top: 6px; }
.tg-badge { padding: 2px 12px; border-radius: 999px; font-size: .78em; font-weight: 600;
  line-height: 1.5; letter-spacing: .01em; white-space: nowrap; color: #fff;
  background: var(--match-pink); }
/* «Окна» — тёплый конец слева, к середине переходит в фуксию интересов: пара читается как одно семейство. */
.tg-badge.time { background: linear-gradient(90deg, var(--match-warm), var(--match-pink) 55%); }
.tg-ava-wrap { position: relative; flex: 0 0 auto; }
.tg-ava { display: block; width: 92px; height: 92px; border-radius: 50%;
  background: var(--surface-2) center/cover no-repeat; }
.tg-photo-empty { display: grid; place-items: center; font-size: 2em; font-weight: 700;
  color: var(--accent-text); }
.tg-video-mark { position: absolute; right: 0; bottom: 0; width: 26px; height: 26px;
  border-radius: 50%; display: grid; place-items: center; font-size: .66em; color: #fff;
  background: color-mix(in srgb, #000 62%, transparent);
  border: 1px solid rgba(255, 255, 255, .3); backdrop-filter: blur(6px); }

/* «О себе» — первые строки анкеты. Отдельного поля «кратко» нет, поэтому режем по строкам. */
.tg-about { display: -webkit-box; margin-top: 24px; color: var(--text-dim);
  font-size: 1.07em; line-height: 1.5; letter-spacing: .01em;
  -webkit-line-clamp: 6; line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden; }

/* «Попадания» — рамка с подписью на верхней линии (как fieldset на макете). ⚠️ Это
   осознанное исключение из «поверхностей без рамок»: здесь линия — сама графика блока,
   а не край карточки. Подпись вырезает линию фоном лица (--tg-face), поэтому и при
   наведении, когда лицо чуть светлеет, разрыв остаётся чистым.
   margin-top: auto — у карточек одного ряда блок встаёт на одну линию. */
.tg-hits { position: relative; display: flex; flex-direction: column; gap: 16px;
  margin-top: auto; padding: 30px 22px 24px; border-radius: 24px;
  border: 1px solid color-mix(in srgb, var(--text) 16%, transparent); }
/* Минимальный зазор над рамкой держит нижнее поле предыдущего блока: auto-отступ рамки
   забирает только ЛИШНЕЕ место, и вместе они дают «не ближе 28px и внизу карточки». */
.tg-about, .tg-head:has(+ .tg-hits) { margin-bottom: 28px; }
.tg-hits-legend { position: absolute; top: 0; left: 50%; transform: translate(-50%, -52%);
  padding: 0 12px; background: var(--tg-face); transition: background-color .2s ease;
  font-size: .86em; letter-spacing: .16em; text-transform: uppercase; white-space: nowrap;
  color: var(--muted); }
.tg-hit { display: flex; align-items: flex-start; gap: 14px; font-size: 1.07em; line-height: 1.3;
  letter-spacing: .02em; }
/* Стрелка — зелёная индикация «совпало» (--grade-good, вне палитры школы: это сигнал,
   а не оформление — так же красятся оценки). */
.tg-hit-ico { flex: 0 0 auto; width: 18px; height: 18px; margin-top: 1px; color: var(--grade-good); }
.tg-hit-ico svg { display: block; width: 100%; height: 100%; }
.tg-hit-main { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.tg-hit-sub { font-size: .84em; color: var(--muted); letter-spacing: 0; }

/* Метка «у меня с ним запись» на карточке витрины (макет владельца продукта).
   ⚠️ Стоит В ПОТОКЕ над шапкой, а не absolute поверх неё: наложенная метка закрыла бы
   имя преподавателя, а у карточек ряда одинаковая высота — сдвиг содержимого у одной из
   них ломает ряд. Цвет — акцентный, но при «выберите другое время» предупреждающий: это
   разные новости, и одинаковыми им быть нельзя. */
.tg-booked { align-self: flex-start; margin-bottom: 14px; padding: 5px 14px;
  border-radius: 999px; font-size: .9em; font-weight: 700; letter-spacing: .01em;
  color: var(--on-accent); background: var(--badge-accent); }
.tg-card.booked { --tg-face: color-mix(in srgb, var(--surface) 96%, var(--accent)); }

/* «Просмотрено» — анкету уже открывали. В разметке всегда (соседи ряда одной высоты),
   видна только у просмотренных. */
.tg-seen { display: block; margin-top: 12px; text-align: center; font-size: .86em;
  letter-spacing: .04em; color: color-mix(in srgb, var(--text) 30%, transparent);
  visibility: hidden; }
.tg-card.seen .tg-seen { visibility: visible; }
/* Без «Попаданий» (ничего не совпало или смотрит не ученик) подпись сама встаёт вниз. */
.tg-about + .tg-seen, .tg-head + .tg-seen { margin-top: auto; }

@media (prefers-reduced-motion: reduce) { .tg-tab { animation-duration: 40s; } }

/* Вступление вместо заголовка «Преподаватели» — он дублировал пункт меню. */
.tg-intro { color: var(--text-dim); font-size: .95em; line-height: 1.5; margin-bottom: 16px;
  max-width: 640px; }
/* Постраничная выдача: кнопка «Показать ещё» + автоподгрузка при прокрутке к ней. */
.tg-foot { display: flex; justify-content: center; margin-top: 18px; }
.tg-more-btn { min-width: 200px; }

/* Поп-ап профиля: мини-лендинг + расписание одной колонкой. */
.modal:has(> .tg-modal-body) { max-width: 860px; }
.tg-modal-body { display: flex; flex-direction: column; gap: 14px; }
.tg-modal-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; padding-top: 4px; }
.tg-foot-note { color: var(--muted); font-size: .84em; }
.tg-foot-acts { display: flex; gap: 8px; margin-left: auto; }
/* Неактивная «Записаться» — не серое «ничто»: видно, что кнопка тут будет, но пока не
   работает (следующий этап — живая запись). */
.tg-book[disabled] { opacity: .55; cursor: not-allowed; }

/* ── Расписание преподавателя в поп-апе ── */
.tav-head-row { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  flex-wrap: wrap; margin-bottom: 10px; }
.tav-head-row .tcard-block-title { margin-bottom: 0; }
.tav-nav { display: flex; align-items: center; gap: 8px; }
.tav-label { font-size: .84em; color: var(--text-dim); white-space: nowrap; }
/* ⚠️ Сетка обязана ВЛЕЗАТЬ в экран телефона без горизонтальной прокрутки (решение
   владельца продукта): раньше у неё была минимальная ширина 460px и свой скролл, и
   воскресенье на телефоне уезжало за край. Колонки — minmax(0, 1fr): сжимаются сами. */
.tav-grid { overflow: hidden; }
.tav-row { display: grid; grid-template-columns: 46px repeat(7, minmax(0, 1fr)); gap: 3px; margin-bottom: 3px; }
.tav-time { font-size: .72em; color: var(--muted); display: flex; align-items: center;
  font-variant-numeric: tabular-nums; }
.tav-day { text-align: center; font-size: .74em; line-height: 1.25; padding-bottom: 4px;
  min-width: 0; overflow: hidden; white-space: nowrap; }
.tav-dow { display: block; color: var(--text-dim); font-weight: 600; }
.tav-date { display: block; color: var(--muted); font-size: .92em; }
/* Цвета клеток — ТЕ ЖЕ, что в расписании класса (решение владельца продукта; раньше
   «свободно» было цвета акцента, а совпадение — зелёной точкой, и на школе с красным
   акцентом это читалось как «красное = нельзя»): окно преподавателя — `--win-teacher`,
   окно самого ученика — `--win-student`, совпавшее — две половины `--win-both`.
   ⚠️ Занятость НЕ рисуется вовсе (решение владельца продукта): ученику важны только свободные
   окна. Прежняя штриховка «Занято» убрана — занятый час выглядит как любой нерабочий. */
.tav-cell { height: 20px; border-radius: 5px; min-width: 0;
  background: color-mix(in srgb, var(--text) 7%, transparent); }
.tav-cell.free, .tav-dot.free { background: var(--win-teacher); }
.tav-cell.mine, .tav-dot.mine { background: var(--win-student); }
.tav-cell.free.mine, .tav-dot.free.mine { background: var(--win-both); }
.tav-legend { display: flex; align-items: center; gap: 6px 14px; flex-wrap: wrap; margin-top: 10px;
  font-size: .78em; color: var(--muted); }
.tav-key { display: inline-flex; align-items: center; gap: 6px; }
.tav-dot { flex: none; width: 20px; height: 10px; border-radius: 4px; display: inline-block; }
@media (max-width: 760px) {
  .tav-row { grid-template-columns: 38px repeat(7, minmax(0, 1fr)); }
  .tav-day { font-size: .66em; }
  .tav-note { margin-left: 0; flex-basis: 100%; }
}
.tav-note { margin-left: auto; }

/* На телефоне — ОДНА карточка в ряд. ⚠️ Прежние две в ряд были причиной жалобы «текст
   тяжело читать»: в половине экрана 375px на строку «о себе» оставалось ~130px. */
@media (max-width: 760px) {
  .tg-grid { gap: 16px; }
  .tg-card { --tg-tab: 36px; --tg-r: 22px; }
  .tg-face { padding: 22px 20px 14px; }
  .tg-name { font-size: 1.45em; }
  .tg-lang { font-size: 1.04em; }
  .tg-exp { font-size: .98em; }
  .tg-ava { width: 74px; height: 74px; }
  .tg-about { margin-top: 18px; font-size: 1em; -webkit-line-clamp: 5; line-clamp: 5; }
  .tg-about, .tg-head:has(+ .tg-hits) { margin-bottom: 22px; }
  .tg-hits { padding: 24px 16px 18px; gap: 13px; border-radius: 20px; }
  .tg-hit { font-size: 1em; gap: 11px; }
  .tg-tab-label { font-size: 1em; }
  /* Вперёд язычок уходит скромнее: справа от карточки на телефоне всего 16px поля. */
  .tg-card:hover .tg-tab, .tg-card:focus-visible .tg-tab { transform: translateX(5px); }
  .tg-card:active .tg-tab, .tg-card.pulling .tg-tab { transform: translateX(9px); }
  .tg-intro { font-size: .88em; margin-bottom: 13px; }
  .tg-more-btn { width: 100%; }
  .tg-foot-acts { width: 100%; }
  .tg-foot-acts .btn { flex: 1; }
}

/* «Скопировать расписание» — рядом с заголовком, навигация по неделям остаётся справа. */
.tav-copy { margin-right: auto; }
.tav-copy svg { width: 15px; height: 15px; }
@media (max-width: 760px) { .tav-copy span { display: none; } }

/* Точка «сейчас на платформе» на портрете (подбор учителей у админа). Цвет — индикация
   (--grade-good), а не палитра школы. */
.tg-online-dot { position: absolute; left: 4px; bottom: 4px; width: 16px; height: 16px;
  border-radius: 50%; background: var(--grade-good); border: 3px solid var(--tg-face); }

/* ─────────────────────────────────────────────────────────────────
   ПОДБОР УЧИТЕЛЕЙ (#/teacher-pick, админ) — фильтры над карточками витрины.
   Карточки — те же .tg-*, здесь только панель фильтров (views/teacher-pick.js).
   ───────────────────────────────────────────────────────────────── */
.tp-filters { display: flex; flex-direction: column; gap: 16px; padding: 20px 22px;
  border-radius: var(--radius); background: var(--surface); margin-bottom: 14px; }
.tp-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 22px; }
.tp-field { min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.tp-label { font-size: .8em; color: var(--muted); letter-spacing: .02em; }
.tp-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.tp-chip { padding: 7px 14px; border-radius: 999px; cursor: pointer; font-family: var(--font);
  font-size: .88em; color: var(--text-dim); background: var(--surface-2);
  border: 1px solid transparent; transition: border-color .15s, color .15s, background-color .15s; }
.tp-chip:hover { color: var(--text); }
.tp-chip.on { color: var(--accent-text); border-color: var(--accent-border);
  background: color-mix(in srgb, var(--accent) 14%, transparent); }
.tp-hours { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.tp-bar { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  flex-wrap: wrap; margin-bottom: 14px; min-height: 36px; }
.tp-summary { color: var(--text-dim); font-size: .9em; }
.tp-none { padding: 40px 0; text-align: center; color: var(--muted); }
.tp-results { transition: opacity .15s; }
.tp-results.loading { opacity: .55; }
/* Поп-ап «Карточка учителя» (отправка анкеты в чат из мессенджера): те же фильтры и
   карточки, что на странице агрегатора, но клик выбирает анкету, а «Отправить» прижата
   к низу окна — на длинной выдаче она не уезжает за экран.
   ⚠️ Окно ШИРЕ обычного «широкого» (620px не хватает на два ряда карточек) и скроллит
   САМО: свой overflow у результатов рождал второй скролл-бар, мелькавший при перерисовке.
   ⚠️ Полосе отправки нужен свой слой (z-index): язычок карточки живёт в transition/webgl
   контексте и без него ложился ПОВЕРХ кнопки. */
.modal:has(> .tp-picker) { max-width: min(1000px, calc(100vw - 32px)); }
.tp-picker { display: flex; flex-direction: column; min-height: 0; }
.tp-pick-results { min-height: 200px; }
.tp-picker .tg-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 22px; }
.tp-sendbar { position: sticky; bottom: 0; z-index: 5; display: flex; align-items: center;
  gap: 12px; margin: 10px -28px -28px; padding: 10px 28px 18px;
  background: var(--surface); border-top: 1px solid var(--border); }
.tp-send-hint { flex: 1; min-width: 0; color: var(--text-dim); font-size: .9em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Выбранная для отправки карточка — кольцо по контуру (у плитки своя рамки нет). */
.tg-card.sel { outline: 2px solid var(--accent); outline-offset: 3px; }
@media (max-width: 760px) {
  .tp-filters { padding: 16px; }
  .tp-row { grid-template-columns: minmax(0, 1fr); }
  .tp-picker .tg-grid { grid-template-columns: minmax(0, 1fr); }
  .modal:has(> .tp-picker) { max-width: none; }
  /* На телефоне окно — нижний лист со своим padding и safe-area: полоса внутри него. */
  .tp-sendbar { margin: 10px 0 calc(-12px - env(safe-area-inset-bottom));
    padding: 10px 18px calc(10px + env(safe-area-inset-bottom)); }
}

/* ─────────────────────────────────────────────────────────────────
   ПРОБНЫЙ УРОК (демо): запись из витрины, экран #/demo, выбор времени.
   Механика — backend/app/demo.py, экраны — views/demo.js.
   ───────────────────────────────────────────────────────────────── */
/* Свободный час в расписании кликабелен только там, где идёт выбор времени. */
.tav-cell.pick { cursor: pointer; }
.tav-cell.pick:hover { filter: brightness(1.15); }
.tav-cell.past { opacity: .45; }
/* Выбранный час — цвет окна остаётся, выбор показывает кольцо ВНУТРИ клетки (наружное срезал бы край сетки): заливка акцентом
   стирала бы разницу между «окно преподавателя» и «совпавшее». */
.tav-cell.sel { box-shadow: inset 0 0 0 2px var(--text); }

/* ── Экран #/demo ── */
/* ⚠️ Карточки записи (.demo-card / .demo-head / .demo-face / .demo-when …) БОЛЬШЕ НЕТ:
   отдельный экран «Пробный урок» у ученика удалён (решение владельца продукта — пять
   точек контакта с одной записью). Осталось два вида: плашка над витриной анкет
   (.demo-bar ниже) и общее окно занятия (.lesson-pop). */
.demo-acts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 18px; }

/* ── Плашка записи над витриной анкет (макет владельца продукта) ──
   Статус слева на своей подложке, преподаватель и время посередине, «Открыть запись»
   справа. ⚠️ Плашка кликается ЦЕЛИКОМ (кнопка — подсказка, а не единственный вход):
   на телефоне попасть пальцем в небольшую кнопку у края труднее, чем в строку. */
.tg-bars:empty, .demo-bars:empty { display: none; }
.demo-bars { display: flex; flex-direction: column; gap: 10px; }
.tg-bars:not(:empty) { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }
.demo-bar { display: flex; align-items: center; gap: 14px; cursor: pointer;
  padding: 0 14px 0 0; border-radius: 14px; overflow: hidden;
  background: color-mix(in srgb, var(--accent) 9%, var(--surface));
  transition: background-color .18s ease; }
.demo-bar:hover { background: color-mix(in srgb, var(--accent) 15%, var(--surface)); }
/* Подпись статуса — на чуть более плотной подложке: на макете это отдельный «ярлык»
   слева, а не просто жирный текст. flex: none — статус не сжимается, сжимается строка
   с именем и временем. */
.demo-bar-st { flex: none; align-self: stretch; display: flex; align-items: center;
  padding: 13px 16px; font-weight: 600; font-size: .88em;
  background: color-mix(in srgb, var(--accent) 16%, transparent); }
.demo-bar-txt { min-width: 0; flex: 1 1 auto; font-size: .9em; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.demo-bar .demo-chip-time { color: var(--muted); font-size: .82em; }
.demo-bar-btn { flex: none; margin-left: auto; }
/* Записи, где ЖДУТ действия ученика (преподаватель попросил другое время), — предупреждением. */
.demo-bar.act { background: color-mix(in srgb, var(--warn) 12%, var(--surface)); }
.demo-bar.act:hover { background: color-mix(in srgb, var(--warn) 18%, var(--surface)); }
.demo-bar.act .demo-bar-st { background: color-mix(in srgb, var(--warn) 22%, transparent);
  color: var(--warn); }
@media (max-width: 620px) {
  /* На телефоне статус уходит своей строкой во всю ширину: «Преподаватель просит выбрать
     другое время» рядом с именем и временем не помещается ни при какой ширине. */
  .demo-bar { flex-wrap: wrap; padding: 0 14px 12px; gap: 8px 12px; }
  .demo-bar-st { width: calc(100% + 28px); margin: 0 -14px; padding: 10px 14px; }
  .demo-bar-btn { margin-left: auto; }
}
/* Обратная связь после урока — та же плашка, но акцент на действии, а не на ожидании. */
.demo-bar-fb .demo-bar-st { background: color-mix(in srgb, var(--lesson-done) 20%, transparent);
  color: var(--lesson-done-fg); }
.demo-fb-modal .demo-fb { margin-top: 0; }

/* Статус записи. Цвета — смыслом, а не палитрой темы: «ждём» ≠ «подтверждено» ≠ «отказ». */
.demo-chip { display: inline-block; padding: 3px 11px; border-radius: 999px; font-size: .78em;
  border: 1px solid transparent; white-space: nowrap; }
.demo-chip-ok { color: var(--accent-text); border-color: var(--accent-border);
  background: color-mix(in srgb, var(--accent) 14%, transparent); }
.demo-chip-wait { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, transparent);
  background: color-mix(in srgb, var(--warn) 12%, transparent); }
.demo-chip-bad { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, transparent);
  background: color-mix(in srgb, var(--danger) 12%, transparent); }
/* Плашка с обратным отсчётом: подпись слева, оставшееся время у правого края (макет
   владельца продукта). Время моноширинное — иначе плашка «дышала» бы на каждой секунде. */
.demo-chip.has-time { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; white-space: normal; }
.demo-chip-time { font-variant-numeric: tabular-nums; font-weight: 600; flex: 0 0 auto; }
/* В карточке записи плашка идёт во всю ширину — так у отсчёта появляется правый край.
   ⚠️ flex, а не align-self:stretch: тянуть надо ШИРИНУ колонки с текстом, а растянутая
   по высоте колонка сместила бы имя и дату к верхнему краю портрета. */
.demo-head-info { flex: 1 1 auto; }
.demo-head-info .demo-chip.has-time { display: flex; width: 100%; }

/* Очередь заявок преподавателя. */
.demo-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px;
  margin-bottom: 18px; }
.demo-stat { padding: 14px 16px; border-radius: 14px; background: var(--surface);
  border: 1px solid transparent; }
.demo-stat-val { font-size: 1.6em; font-weight: 700; line-height: 1.1; }
.demo-stat-val.bad { color: var(--danger); }
.demo-stat-lbl { color: var(--muted); font-size: .8em; margin-top: 4px; }
.demo-list { display: flex; flex-direction: column; gap: 10px; }
.demo-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 14px 16px; border-radius: 14px; background: var(--surface);
  border: 1px solid transparent; }
.demo-row-main { min-width: 0; flex: 1 1 200px; }
.demo-row-when { font-weight: 600; }
.demo-row-who { color: var(--text-dim); font-size: .88em; margin-top: 2px; }
.demo-row-acts { display: flex; gap: 8px; flex-wrap: wrap; margin-left: auto; }

/* Экран «Демо» преподавателя (макет владельца продукта): две плитки в ряд и коэффициент
   во всю ширину, ниже «Предстоящие» и «Прошедшие». Статус — цветным текстом, без плашки.
   ⚠️ Успех — --grade-good, а не акцент школы: на красном акценте он читался бы ошибкой. */
.tdemo-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.tdemo-stats .demo-stat { padding: 18px 20px; border-radius: 18px; }
.tdemo-stats .demo-stat.wide { grid-column: 1 / -1; }
.tdemo-stats .demo-stat-val { font-size: 2em; font-weight: 500; }
.tdemo-stats .demo-stat-lbl { font-size: .9em; margin-top: 8px; }
.tdemo-title { color: var(--muted); font-size: 1.05em; margin: 22px 0 12px; }
.tdemo-card { padding: 16px 18px; border-radius: 18px; background: var(--surface); }
.tdemo-when { color: var(--muted); font-size: .92em; }
.tdemo-who { margin-top: 4px; overflow-wrap: anywhere; }
.tdemo-note { margin-top: 14px; color: var(--text-dim); font-size: .92em; }
.tdemo-note.wait { margin-top: 6px; color: var(--warn); font-size: .85em; }
.tdemo-state { display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin-top: 8px; }
.tdemo-state .demo-chip-time { color: var(--warn); }
.tdemo-st { font-weight: 600; font-size: .95em; }
.tdemo-st.wait { color: var(--warn); }
.tdemo-st.ok { color: var(--grade-good); }
.tdemo-st.bad { color: var(--danger); }
.tdemo-card .demo-row-acts { margin: 14px 0 0; }
.tdemo-past { display: flex; align-items: flex-end; gap: 14px; }
.tdemo-past .tdemo-main { min-width: 0; flex: 1 1 auto; }
.tdemo-past .tdemo-st { flex: 0 0 auto; }
.demo-hist .tdemo-title { margin-top: 0; }

/* ── Карточка на главной ── */
.tdash-demo:empty { display: none; }
/* ⚠️ ГРИД, а не flex-строка. Плашка статуса бывает длинной («Преподаватель просит выбрать
   другое время») и не переносится, поэтому во flex она съедала всю ширину: заголовок
   ломался на «Пробный / урок», а дата растягивалась в столбик по слову. В гриде текст
   живёт в своей колонке minmax(0,1fr) и обрезается многоточием, а плашка — в своей. */
.demo-home { display: grid; grid-template-columns: auto minmax(0, 1fr) minmax(0, auto);
  align-items: center;
  gap: 6px 12px; cursor: pointer;
  margin-bottom: 14px; padding: 12px 16px; border-radius: 14px; background: var(--surface);
  border: 1px solid var(--accent-border); transition: border-color .15s, transform .15s; }
.demo-home:hover { transform: translateY(-1px); border-color: var(--accent); }
.demo-home-face { width: 42px; height: 42px; flex: 0 0 auto; border-radius: 50%;
  background: var(--surface-2) center/cover no-repeat; }
.demo-home-info { min-width: 0; }
.demo-home-title { font-weight: 600; font-size: .95em; white-space: nowrap; }
.demo-home-when { color: var(--text-dim); font-size: .84em; margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* У карточки без портрета (заявки преподавателю) первой колонки нет — текст занимает её. */
.demo-home-info:first-child { grid-column: 1 / 3; }
/* Длинный статус переносится, а не распирает карточку: третья колонка ужимается. */
.demo-home .demo-chip { white-space: normal; }

.demo-home-wrap:empty { display: none; }
/* ── Два действия под карточкой записи ── */
/* Сетки часов преподавателя здесь БОЛЬШЕ НЕТ (решение владельца продукта): расписание
   живёт в анкете преподавателя и открывается кнопкой «Поменять время занятия». Под
   карточкой остаётся ровно два действия — перенести или уйти к другому. */
.demo-home-acts { display: flex; gap: 8px; flex-wrap: wrap; margin: -6px 0 14px; }
.demo-home-acts .btn { flex: 1 1 220px; }

@media (max-width: 760px) {
  .demo-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .demo-row-acts { width: 100%; margin-left: 0; }
  .demo-row-acts .btn { flex: 1; }
  /* В узкой колонке две кнопки в ряд оставляют на подпись ~70px, и «Выбрать другого
     эксперта» ломается на три строки: на телефоне они идут друг под другом. Замерено на
     375px: в ряду кнопке достаётся 153px при нужных ~190px. */
  .demo-home-acts .btn { flex: 1 1 100%; }
  .demo-card .demo-acts .btn { flex: 1 1 100%; }
  /* На телефоне плашка статуса уходит на свою строку во всю ширину: рядом с текстом ей
     остаётся полсотни пикселей, и любой статус длиннее «Отменена» режется на слоги. */
  .demo-home { grid-template-columns: auto minmax(0, 1fr); }
  .demo-home .demo-chip { grid-column: 1 / -1; white-space: normal; }
  .demo-home .demo-chip:not(.has-time) { text-align: center; }
  .demo-card { padding: 16px; }
  .demo-when { font-size: 1.05em; }
}

/* Пробный урок в сетке расписания — розовый чип с буквой D (см. .lesson-chip.demo и .chip-mark
   выше; у обычных уроков там R или S). Прежняя подпись «демо» (.chip-demo) и пунктирная рамка
   убраны — в колонку сетки текст не влезал, а цвет и буква читаются даже в самой узкой ячейке. */

/* Обратная связь по пробному уроку: блок у ученика, форма-поп-ап у преподавателя,
   история демо в его очереди заявок (views/demo.js, этап 3). */
.demo-fb { margin-top: 16px; padding: 16px; border-radius: 14px; background: var(--surface-2);
  border: 1px solid transparent; }
.demo-fb-title { font-size: .78em; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 12px; }
.demo-fb-row + .demo-fb-row { margin-top: 12px; }
.demo-fb-lbl { font-size: .82em; color: var(--muted); }
.demo-fb-val { margin: 3px 0 0; line-height: 1.55; white-space: pre-wrap; }
.demo-fb-err { color: var(--danger); font-size: .88em; min-height: 1.2em; margin-top: 6px; }
/* Поле, которое просят заполнить: подсветка снимается при первом же вводе. */
.textarea.input-bad, .input.input-bad { border-color: var(--danger); }
.demo-fbform .field + .field { margin-top: 12px; }
.demo-row-fb { margin-top: 6px; color: var(--text-dim); font-size: .85em; font-style: italic; }
.demo-hist { margin-top: 26px; }
.demo-hist-title { font-size: .78em; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 12px; }

/* ── ИИ-анализ ученика: журнал инспектора переписки (views/insights.js) ──────────
   Живёт в профиле (модалка), поэтому всё компактнее обычных карточек. Цвета — только
   через токены палитры: строка уровня «тревога» обязана краснеть и в школе с зелёным
   акцентом, и в школе с красным. */
.ins-wrap { margin-top: 6px; }

/* Полоса выбора преподавателя — НАД вкладками разбора. У ученика бывает два-четыре
   преподавателя, и «Сводно» здесь значит «итог по всем», а имя — «только этот».
   ⚠️ Стоит выше вкладок намеренно: «про кого смотрим» — свойство всего разбора, и
   внутри вкладки выбор сбрасывался бы на каждом переходе Чат→Звонки. */
.ins-scope { margin-bottom: 10px; }
.ins-scope.hidden { display: none; }
.ins-scope-list { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 2px;
  scrollbar-width: none; -ms-overflow-style: none; }
.ins-scope-list::-webkit-scrollbar { display: none; }
.ins-scope-chip { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: 999px; cursor: pointer; white-space: nowrap;
  background: var(--surface-2); border: 1px solid var(--border-2);
  color: var(--text-dim); font-family: var(--font); font-size: .82em;
  transition: color .15s, border-color .15s, background .15s; }
.ins-scope-chip:hover { color: var(--text); }
.ins-scope-chip.active { color: var(--accent-text); border-color: var(--accent-border);
  background: color-mix(in srgb, var(--accent) 12%, var(--surface-2)); }
.ins-scope-name { font-weight: 500; }
/* Непросмотренные тревоги инспектора у этого преподавателя — по ним и видно, к кому идти. */
.ins-scope-badge { display: inline-grid; place-items: center; min-width: 16px; height: 16px;
  padding: 0 4px; border-radius: 8px; font-size: .78em; font-weight: 600;
  background: var(--danger); color: #fff; }
.ins-scope-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--warn); }
.ins-scope-dot.high { background: var(--danger); }
.ins-scope-note { margin: 6px 0 0; font-size: .78em; color: var(--muted); line-height: 1.45; }

/* Заголовок группы внутри раздела: в сводном режиме находки и занятия разложены по
   преподавателям, а не свалены одной лентой. Заголовок кликабелен — уводит в разбор
   только этого преподавателя. */
.ins-group-head { display: flex; align-items: baseline; gap: 8px; width: 100%;
  margin: 12px 0 6px; padding: 0; background: none; border: none; cursor: pointer;
  text-align: left; font-family: var(--font); }
.ins-group-head:first-child { margin-top: 0; }
.ins-group-name { font-size: .84em; font-weight: 600; color: var(--text); }
.ins-group-head:hover .ins-group-name { color: var(--accent-text); }
.ins-group-sub { font-size: .76em; color: var(--muted); }
.ins-tabs { margin-bottom: 12px; overflow-x: auto; flex-wrap: nowrap;
  scrollbar-width: none; -ms-overflow-style: none; }
.ins-tabs::-webkit-scrollbar { display: none; }
.ins-tabs .tab { display: inline-flex; align-items: center; gap: 6px;
  white-space: nowrap; flex: 0 0 auto; }
.ins-soon-dot { font-size: .62em; letter-spacing: .06em; text-transform: uppercase;
  padding: 1px 5px; border-radius: 5px; color: var(--muted);
  background: color-mix(in srgb, var(--muted) 16%, transparent); }

.ins-soon { padding: 22px 18px; text-align: center; border-radius: 14px;
  border: 1px dashed var(--border-2); color: var(--text-dim); }
.ins-soon-ico { width: 26px; height: 26px; margin: 0 auto 8px; color: var(--muted); }
.ins-soon-ico svg { width: 100%; height: 100%; }
.ins-soon p { margin: 0; line-height: 1.5; font-size: .9em; }

.ins-state { display: flex; gap: 12px; align-items: flex-start; flex-wrap: wrap;
  padding: 12px 14px; border-radius: 12px; background: var(--surface-2);
  border: 1px solid transparent; margin-bottom: 12px; }
.ins-state-txt { flex: 1 1 220px; min-width: 0; font-size: .85em; color: var(--text-dim);
  line-height: 1.5; }
.ins-state-acts { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.ins-err { font-size: .8em; color: var(--danger); }

.ins-filters { display: flex; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.ins-filter { display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  font-family: var(--font); font-size: .8em; padding: 5px 11px; border-radius: 999px;
  color: var(--text-dim); background: transparent; border: 1px solid var(--border-2); }
.ins-filter b { color: var(--text); }
.ins-filter.active { color: var(--accent-text); border-color: var(--accent-border);
  background: color-mix(in srgb, var(--accent) 12%, transparent); }
.ins-filter.active b { color: var(--accent-text); }
.ins-filter.empty { opacity: .5; }

.ins-list { display: flex; flex-direction: column; gap: 8px; }
/* Уровень читается полосой слева: строк в журнале много, и цветной текст в каждой
   превратил бы список в витраж.
   ⚠️ Строка — ГРИД «текст + действия», а не три колонки: журнал живёт в модалке профиля
   шириной 460px, и отдельная колонка под дату оставляла заголовку ~200px — «Жалоба на
   скучные уроки» ломалась на четыре строки. */
.ins-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 10px;
  align-items: start; padding: 11px 13px; border-radius: 12px; background: var(--surface);
  border: 1px solid transparent; border-left: 3px solid var(--border-2); }
.ins-row.lvl-alert { border-left-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 6%, var(--surface)); }
.ins-row.lvl-warn { border-left-color: var(--warn); }
.ins-row.reviewed { opacity: .55; }
.ins-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; }
.ins-date { font-size: .8em; color: var(--muted); white-space: nowrap; }
.ins-lvl { font-size: .66em; letter-spacing: .06em; text-transform: uppercase;
  padding: 1px 6px; border-radius: 5px; color: var(--muted);
  background: color-mix(in srgb, var(--muted) 15%, transparent); }
.ins-row.lvl-alert .ins-lvl { color: var(--danger);
  background: color-mix(in srgb, var(--danger) 16%, transparent); }
.ins-row.lvl-warn .ins-lvl { color: var(--warn);
  background: color-mix(in srgb, var(--warn) 18%, transparent); }
/* Заголовок уезжает на свою строку, когда рядом с датой ему тесно. */
.ins-title { flex: 1 1 200px; min-width: 0; font-weight: 600; line-height: 1.4; }
.ins-more { grid-column: 1 / -1; }
.ins-more:empty { display: none; }
.ins-detail { font-size: .87em; color: var(--text-dim); line-height: 1.5; }
.ins-quote { margin-top: 6px; font-size: .85em; font-style: italic; color: var(--text-dim);
  padding-left: 9px; border-left: 2px solid var(--border-2); }
.ins-who { margin-top: 6px; font-size: .78em; color: var(--muted); }
.ins-acts { display: flex; gap: 6px; align-items: center; flex-shrink: 0; }
.ins-acts .btn { padding: 5px 9px; }
.ins-review.done { color: var(--accent-text); border-color: var(--accent-border); }
.ins-drop svg { width: 14px; height: 14px; }
.ins-empty { margin: 0; padding: 18px; text-align: center; color: var(--muted);
  font-size: .88em; line-height: 1.5; }

/* «Смотреть»: кусок переписки с подсвеченным сообщением. Лента прокручивается ВНУТРИ
   себя, а не вместе с модалкой — иначе прокрутка к находке уводила бы шапку с
   описанием, ради которого администратор сюда и пришёл. */
.ins-view-head { padding: 11px 13px; border-radius: 12px; margin-bottom: 8px;
  background: var(--surface-2); border-left: 3px solid var(--border-2); }
.ins-view-head.lvl-alert { border-left-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 8%, var(--surface-2)); }
.ins-view-head.lvl-warn { border-left-color: var(--warn); }
.ins-view-sub { font-size: .82em; }
.ins-feed { max-height: min(52vh, 460px); overflow-y: auto; padding: 10px;
  border-radius: 12px; background: var(--surface-2); display: flex;
  flex-direction: column; gap: 8px; }
.ins-msg { max-width: 78%; padding: 8px 11px; border-radius: 12px; font-size: .88em;
  background: var(--surface); border: 1px solid transparent; }
/* Стороны разговора. `.student`/`.teacher` — окно находки инспектора, `.side-a`/`.side-b` —
   просмотр переписки в профиле (там слева владелец профиля, а он бывает и учителем). */
.ins-msg.student, .ins-msg.side-a { align-self: flex-start; }
.ins-msg.teacher, .ins-msg.side-b { align-self: flex-end; }
/* Найденное сообщение: ради него всё и открывали — рамка акцентом и мягкое свечение. */
.ins-msg.focus { border-color: var(--accent-border); box-shadow: 0 0 0 3px
  color-mix(in srgb, var(--accent) 22%, transparent); }
.ins-msg-head { display: flex; gap: 10px; justify-content: space-between;
  align-items: baseline; margin-bottom: 4px; }
.ins-msg-who { font-size: .82em; font-weight: 600; color: var(--accent-text); }
.ins-msg.teacher .ins-msg-who, .ins-msg.side-b .ins-msg-who { color: var(--text-dim); }
.ins-msg-time { font-size: .74em; color: var(--muted); white-space: nowrap; }
.ins-msg-body { line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
.ins-msg-file { display: inline-flex; align-items: center; gap: 6px; margin-top: 6px;
  font-size: .84em; color: var(--accent-text); }
.ins-msg-file svg { width: 14px; height: 14px; }
/* Удаление сообщения прямо из окна находки (только администратор — раздел админский).
   ⚠️ margin-right:auto на подписи автора: в шапке пузыря теперь ТРИ элемента, и без него
   space-between развёл бы их поровну — время повисло бы посреди строки. */
.ins-msg-who { margin-right: auto; }
.ins-msg-del { flex: 0 0 auto; margin-left: -4px; padding: 0 2px; background: none;
  border: none; cursor: pointer; color: var(--muted); opacity: .55; transition: opacity .12s, color .12s; }
.ins-msg-del:hover { opacity: 1; color: var(--danger); }
.ins-msg-del svg { width: 13px; height: 13px; display: block; }
/* Удалённое не исчезает из окна, а становится заглушкой: место, вокруг которого собран
   фрагмент, не должно схлопнуться под курсором. */
.ins-msg.deleted { opacity: .6; }
.ins-msg-gone { font-size: .86em; font-style: italic; color: var(--muted); }
.ins-view-acts { display: flex; gap: 8px; margin-top: 12px; }

/* ── Переписка пользователя в профиле (views/chatlog.js) ──────────────────────
   Лента и пузыри переиспользуют .ins-feed/.ins-msg — это тот же по смыслу экран
   «чужой разговор глазами администратора», свой набор классов был бы копией. */
.cl-chats .list { gap: 8px; }
/* ⚠️ Строка-<button>: ей нужны свой шрифт и выравнивание (голая кнопка наследует
   системный Arial и центрирование), а `nowrap` — чтобы правая колонка со счётчиком
   не уезжала на вторую строку и строки не разъезжались по высоте. */
.cl-row { width: 100%; padding: 10px 12px; gap: 10px 12px; flex-wrap: nowrap;
  text-align: left; font-family: var(--font); font-size: 1em; color: inherit; cursor: pointer; }
.cl-row .row-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cl-row-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cl-row-last { margin-top: 2px; font-size: .82em; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cl-row-meta { flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end;
  gap: 2px; font-size: .76em; color: var(--muted); white-space: nowrap; }
.cl-row-count { color: var(--text-dim); }
/* Панель над лентой: слева счётчик, справа действия. */
.cl-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
.cl-count { margin-right: auto; font-size: .82em; color: var(--muted); }
.cl-bar-acts { display: flex; gap: 8px; flex-wrap: wrap; }
.cl-wipe:hover { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, transparent); }
.cl-msg-tag { font-size: .78em; color: var(--muted); margin-bottom: 3px; }
/* Режим выделения: галочка в шапке пузыря, весь пузырь — переключатель. */
.ins-msg.picked { border-color: var(--accent-border);
  background: color-mix(in srgb, var(--accent) 12%, var(--surface)); }
.ins-feed.cl-selecting .ins-msg { cursor: pointer; }
.cl-check { flex: 0 0 auto; width: 15px; height: 15px; border-radius: 5px; display: grid;
  place-items: center; border: 1px solid var(--border-2); color: var(--on-accent); }
.cl-check.on { background: var(--accent); border-color: var(--accent); }

/* Метка в списке учеников: «у этого есть непросмотренная тревога». */
.ins-badge { display: inline-flex; align-items: center; gap: 3px; padding: 1px 6px;
  border-radius: 6px; font-size: .72em; color: var(--danger);
  background: color-mix(in srgb, var(--danger) 14%, transparent); }
.ins-badge-ico { display: inline-flex; width: 11px; height: 11px; }
.ins-badge-ico svg { width: 100%; height: 100%; }

@media (max-width: 760px) {
  /* На телефоне действия уходят под текст: три кнопки в одной строке с заголовком
     сжимают его до одного слова в строку. */
  .ins-row { grid-template-columns: minmax(0, 1fr); }
  /* Порядок задаём явно: в одной колонке кнопки встали бы МЕЖДУ заголовком и описанием
     (в разметке они идут вторыми — так они попадают в правый верхний угол на широком). */
  .ins-more { order: 2; }
  .ins-acts { order: 3; justify-content: flex-end; }
  .ins-msg { max-width: 92%; }
  .ins-feed { max-height: 46vh; }
}

/* ── Посещаемость: раздел ИИ-анализа (views/insights.js) ─────────────────────────
   Считается арифметикой, поэтому вид у него «приборный»: плитки, столбики, факты.
   Цвета — токенами палитры, чтобы «отменил» краснел в любой школьной теме. */
.att-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
  gap: 8px; margin-bottom: 14px; }
.att-tile { padding: 10px 11px; border-radius: 12px; background: var(--surface-2);
  border: 1px solid transparent; }
.att-tile-val { font-size: 1.35em; font-weight: 600; line-height: 1.1; }
.att-tile.bad .att-tile-val { color: var(--danger); }
.att-tile-cap { margin-top: 3px; font-size: .76em; color: var(--text-dim); }
.att-tile-sub { margin-top: 2px; font-size: .72em; color: var(--muted); }

.att-block { padding: 12px 13px; border-radius: 12px; background: var(--surface-2);
  border: 1px solid transparent; margin-bottom: 12px; }
.att-block-head { display: flex; justify-content: space-between; align-items: baseline;
  gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.att-block-cap { font-size: .76em; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); }
.att-trend { font-size: .86em; color: var(--text-dim); }
.att-trend-pct { font-weight: 600; }
.att-trend.down .att-trend-pct { color: var(--danger); }
.att-trend.up .att-trend-pct { color: var(--accent-text); }

/* Мини-график по неделям: колонка = неделя, сегменты снизу вверх — проведено, неявки,
   отменённое. Высота от самой полной недели: важна форма, а не абсолютная шкала. */
.att-weeks { display: flex; align-items: flex-end; gap: 3px; height: 76px; }
.att-week { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column;
  justify-content: flex-end; height: 100%; }
.att-bars { display: flex; flex-direction: column; justify-content: flex-end;
  height: 58px; gap: 1px; }
.att-bar { border-radius: 2px; min-height: 2px; }
.att-bar.done { background: var(--lesson-done); }
.att-bar.missed { background: var(--noshow); }
.att-bar.cancelled { background: color-mix(in srgb, var(--muted) 55%, transparent); }
.att-week-lbl { margin-top: 4px; font-size: .6em; color: var(--muted); text-align: center;
  white-space: nowrap; overflow: hidden; }
.att-legend { display: flex; gap: 12px; margin-top: 8px; flex-wrap: wrap; }
.att-lg { display: inline-flex; align-items: center; gap: 5px; font-size: .74em;
  color: var(--muted); }
.att-lg::before { content: ''; width: 9px; height: 9px; border-radius: 2px; }
.att-lg.done::before { background: var(--lesson-done); }
.att-lg.missed::before { background: var(--noshow); }
.att-lg.cancelled::before { background: color-mix(in srgb, var(--muted) 55%, transparent); }

.att-facts { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.att-fact { flex: 1 1 180px; padding: 9px 11px; border-radius: 10px; background: var(--surface-2);
  border: 1px solid transparent; font-size: .84em; }
.att-fact-cap { display: block; font-size: .82em; color: var(--muted); margin-bottom: 2px; }

/* Разбивка посещаемости по преподавателям — рядом с общим итогом, а не вместо него.
   Строка целиком кликабельна: уводит в разбор только этого преподавателя. */
.att-split { display: flex; flex-direction: column; gap: 4px; }
.att-split-row { display: grid; align-items: center; gap: 4px 10px; width: 100%;
  grid-template-columns: minmax(0, 1.6fr) auto auto auto auto auto;
  padding: 8px 10px; border-radius: 10px; cursor: pointer; text-align: left;
  background: var(--surface); border: 1px solid transparent;
  border-left: 3px solid var(--border-2);
  font-family: var(--font); font-size: .84em; color: var(--text-dim);
  transition: border-color .15s, background .15s; }
.att-split-row:hover { background: var(--surface-2); }
.att-split-row.lvl-high { border-left-color: var(--danger); }
.att-split-row.lvl-watch { border-left-color: var(--warn); }
.att-split-name { color: var(--text); font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.att-split-cell { white-space: nowrap; }
.att-split-cell b { color: var(--text); }
.att-split-risk { white-space: nowrap; font-size: .92em; color: var(--muted); }
.att-split-risk.lvl-high { color: var(--danger); }
.att-split-risk.lvl-watch { color: var(--warn); }
/* ⚠️ На узком экране шесть колонок не помещаются: имя занимает всю ширину, остальное
   идёт под ним обычной строкой — таблица превращается в карточку.
   ⚠️ Порог по ВЬЮПОРТУ здесь только страховка для телефона: таблица живёт в колонке
   профиля шириной ~440px и на широком экране, поэтому в один ряд она обязана помещаться
   сама по себе — за это отвечают короткие подписи вердикта (SPLIT_RISK), а не медиазапрос. */
@media (max-width: 620px) {
  .att-split-row { grid-template-columns: 1fr auto; }
  .att-split-name { grid-column: 1 / -1; }
}

/* Вердикт: уровень + причины с цифрами, из которых он выведен. Голое «высокий риск»
   администратор проверить не может, а значит и не поверит. */
.att-risk { padding: 12px 13px; border-radius: 12px; margin-bottom: 12px;
  border: 1px solid transparent; border-left: 3px solid var(--border-2);
  background: var(--surface); }
.att-risk.lvl-high { border-left-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 7%, var(--surface)); }
.att-risk.lvl-watch { border-left-color: var(--warn); }
.att-risk-lvl { font-weight: 600; }
.att-risk.lvl-high .att-risk-lvl { color: var(--danger); }
.att-risk.lvl-watch .att-risk-lvl { color: var(--warn); }
.att-risk-head { margin-bottom: 6px; }
.att-reason { font-size: .86em; color: var(--text-dim); line-height: 1.5; padding-left: 12px;
  position: relative; }
.att-reason + .att-reason { margin-top: 4px; }
.att-reason::before { content: '•'; position: absolute; left: 2px; color: var(--muted); }
.att-reason.lvl-high::before { color: var(--danger); }
.att-reason.lvl-watch::before { color: var(--warn); }
.att-reason.lvl-good::before { content: '↑'; color: var(--accent-text); }

.att-verdict { padding: 12px 13px; border-radius: 12px; margin-bottom: 12px;
  border: 1px dashed var(--border-2); }
.att-verdict-txt { margin: 8px 0; line-height: 1.55; font-size: .9em; }
.att-verdict-at { font-size: .74em; color: var(--muted); margin-bottom: 8px; }

.att-events { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.att-ev { display: flex; gap: 10px; font-size: .83em; align-items: baseline; }
.att-ev-date { flex: 0 0 78px; color: var(--muted); }
.att-ev-what { color: var(--text-dim); }
.att-ev.k-no_show .att-ev-what { color: var(--noshow); }
.att-ev.k-cleanup .att-ev-what { opacity: .7; font-style: italic; }

/* Метка риска в списке учеников — рядом с меткой инспектора переписки. */
.ins-badge.risk { color: var(--warn); background: color-mix(in srgb, var(--warn) 16%, transparent); }
.ins-badge.risk.high { color: var(--danger);
  background: color-mix(in srgb, var(--danger) 16%, transparent); }

/* Выбор инициатора при отмене занятия (views/schedule.js). */
.cw-opts { display: flex; flex-direction: column; gap: 8px; }
.cw-opt { display: block; width: 100%; text-align: left; cursor: pointer;
  font-family: var(--font); padding: 10px 12px; border-radius: 10px;
  background: var(--surface-2); border: 1px solid var(--border-2); color: var(--text); }
.cw-opt b { display: block; font-size: .92em; }
.cw-opt span { display: block; margin-top: 2px; font-size: .78em; color: var(--muted); }
.cw-opt.active { border-color: var(--accent-border); color: var(--accent-text);
  background: color-mix(in srgb, var(--accent) 12%, transparent); }
.cw-opt.active span { color: var(--accent-text); opacity: .85; }

@media (max-width: 760px) {
  .att-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .att-weeks { height: 64px; }
  .att-bars { height: 46px; }
  .att-week-lbl { font-size: .55em; }
  .att-ev-date { flex-basis: 66px; }
}

/* ── Разбор занятия: расшифровка звонка по ролям (views/insights.js) ─────────────
   ⚠️ Префикс `ct-`, а не `call-`: последний принадлежит модулю видеозвонка
   (views/call.js), и одинаковые имена у двух разных фич — прямой путь к правке,
   которая чинит одно и ломает другое. */
.ct-meta { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center;
  margin-top: 4px; font-size: .8em; color: var(--muted); }
.ct-flag { color: var(--warn); }
/* Кто вёл занятие: у ученика несколько преподавателей, и список уроков без имени
   не отвечает на вопрос «с кем это было». */
.ct-who { color: var(--text-dim); font-weight: 500; }
/* Кто сколько говорил: полоса «преподаватель / ученик». Показывается только когда
   записаны обе стороны — по одной дорожке доля речи всегда 100% и означала бы неправду. */
.ct-talk { display: inline-flex; align-items: center; gap: 6px; }
.ct-talk-bar { display: inline-block; width: 54px; height: 6px; border-radius: 3px;
  background: color-mix(in srgb, var(--accent) 22%, transparent); overflow: hidden; }
.ct-talk-fill { display: block; height: 100%; background: var(--accent); }

.ct-summary { padding: 11px 13px; border-radius: 12px; margin-bottom: 12px;
  background: var(--surface-2); border: 1px solid transparent; }
/* Запись звонка — один плеер «по репликам» на обе стороны (call-audio.js), под ним
   переключатели сторон. Плеер — тот же, что в упражнениях (.ex-aplayer, своя заливка и
   рамка), карточка вокруг не нужна. Точка у стороны — цвет её роли в ленте ниже. */
.ct-rec { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.ct-rec .ex-aplayer, .ct-tracks .ex-aplayer { margin: 0; }
/* Без пофразного времени — плеер на каждую запись: «подпись | плеер», строка растворена
   (display: contents), подписи общей колонкой — плееры одной ширины. */
.ct-tracks { display: grid; grid-template-columns: max-content minmax(0, 1fr); align-items: center;
  gap: 8px 12px; margin-bottom: 12px; }
.ct-track { display: contents; }
.ct-track-role { font-size: .78em; color: var(--muted); white-space: nowrap; }
.ct-sides { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; padding-left: 4px; }
.ct-side { display: inline-flex; align-items: center; gap: 6px; padding: 3px 11px 3px 9px;
  border-radius: 99px; border: 1px solid var(--border-2); background: none; color: var(--text);
  font-family: inherit; font-size: .78em; cursor: pointer; transition: color .15s, border-color .15s; }
.ct-side::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--accent-text); }
.ct-side.student::before { background: var(--text-dim); }
.ct-side:hover { border-color: var(--text-dim); }
.ct-side[aria-pressed="false"] { color: var(--muted); text-decoration: line-through; }
.ct-side[aria-pressed="false"]::before { background: var(--border-2); }
.ct-side-note, .ct-rec-note { font-size: .78em; color: var(--muted); font-style: italic; }
.ct-rec-note { margin: 0 0 12px; }

/* Диалог: роль слева колонкой — так видно, кто вёл занятие, не читая текст. */
.ct-feed { max-height: min(52vh, 460px); overflow-y: auto; padding: 10px 12px;
  border-radius: 12px; background: var(--surface-2); }
.ct-line { display: flex; gap: 10px; padding: 5px 0; font-size: .88em; line-height: 1.55; }
.ct-line + .ct-line { border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); }
.ct-who { flex: 0 0 104px; font-size: .82em; font-weight: 600; color: var(--accent-text); }
.ct-line.student .ct-who { color: var(--text-dim); }
.ct-said { flex: 1 1 0; min-width: 0; overflow-wrap: anywhere; }
/* Диктор или музыка из материалов урока — не речь участников (расшифровка диалогом). */
.ct-line.audio .ct-who, .ct-line.audio .ct-said { color: var(--text-dim); font-style: italic; }
/* Реплика, которой микрофон её стороны не слышал (разметка браузера, call_dialog.verify). */
.ct-unv { color: var(--warn); font-weight: 700; cursor: help; }
/* Событие платформы между репликами (call_trace.py): не речь — тише и мельче реплик, чтобы
   диалог читался как диалог, а событие было видно рядом с тем, что оно объясняет. */
.ct-line.ct-ev { font-size: .8em; padding: 3px 0; }
.ct-line.ct-ev .ct-who { font-weight: 500; color: var(--muted); }
.ct-line.ct-ev .ct-said { color: var(--text-dim); }
.ct-ev-ic { display: inline-flex; width: 13px; height: 13px; margin-right: 6px; vertical-align: -2px;
  color: var(--muted); }
.ct-ev-ic svg { width: 100%; height: 100%; }
.ct-feed.ct-hide-ev .ct-ev { display: none; }
.ct-ev-toggle { display: inline-flex; align-items: center; gap: 8px; margin: 0 0 8px;
  font-size: .85em; color: var(--text-dim); cursor: pointer; }
/* Время реплики от начала занятия. Моноширинный — иначе колонка «дышит» на каждой
   строке и глазу не за что зацепиться при поиске нужного момента. */
.ct-at { flex: 0 0 54px; font-size: .76em; font-variant-numeric: tabular-nums;
  color: var(--muted); padding-top: 1px; }
/* Время — «слушать отсюда» (insights.followFeed), звучащая реплика подсвечена. Подсветка
   выходит за края строки тенью, а не отступами — текст ленты не сдвигается. */
.ct-feed.ct-seekable .ct-at { cursor: pointer; }
.ct-feed.ct-seekable .ct-at:hover { color: var(--accent-text); text-decoration: underline; }
.ct-line.ct-now { --now: color-mix(in srgb, var(--accent) 12%, transparent);
  background: var(--now); box-shadow: -8px 0 0 var(--now), 8px 0 0 var(--now); }

@media (max-width: 760px) {
  .ct-line { flex-direction: column; gap: 2px; }
  .ct-who { flex-basis: auto; }
  .ct-feed { max-height: 46vh; }
  /* На телефоне строка идёт колонкой — время встаёт в один ряд с ролью, а не отдельной
     строкой: три строки на каждую реплику превратили бы ленту в простыню. */
  .ct-at { flex-basis: auto; order: -1; }
}

/* Диагностика распознавания в окне занятия: сырой ответ сервиса. Моноширинный и со
   скроллом — это JSON, а не текст для чтения глазами. */
.ct-probe { margin: 10px 0; }
.ct-probe > summary { cursor: pointer; font-size: .85em; color: var(--text-dim); }
.ct-probe-out:empty { display: none; }
.ct-probe-out { max-height: 260px; overflow: auto; margin: 0; padding: 10px;
  border-radius: 10px; background: var(--surface-2); border: 1px solid transparent;
  font-family: ui-monospace, Menlo, Consolas, monospace; font-size: .74em;
  line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }

/* Результат проверки распознавания речи (Настройки → Ученики → Разбор звонков). */
.page-sub.stt-ok { color: var(--accent-text); }
.page-sub.stt-bad { color: var(--danger); }
/* Расшифровка без таймингов: реплики не переплетены — предупреждаем прямо над текстом. */
.page-sub.ct-untimed { color: var(--warn); }

/* ═══ ДОМАШНЯЯ РАБОТА (кнопка-домик в комнате класса) ═══════════════
   Большой попап: слева список домашек по датам, справа — сама домашка. Задания —
   обычные упражнения платформы, поэтому своих стилей у них нет: всё, что внутри
   .hw-item, рисуют классы упражнений (.ex-render и соседи). */
.modal:has(> .hw-modal) { max-width: 1000px; }
/* --hw-ink — «чернила» окна (макет владельца): тёмные карточки списка и тёмные кнопки
   подвала. Производная от текста и поверхности, а не литерал: в светлой палитре школы
   выходит тёмно-серый с макета (#474747), в тёмной — светлая плашка на тёмном фоне. */
.hw-modal { --hw-ink: color-mix(in srgb, var(--text) 72%, var(--surface));
  --hw-on-ink: var(--surface);
  --hw-ink-dim: color-mix(in srgb, var(--surface) 58%, var(--hw-ink));
  display: grid; grid-template-columns: 270px minmax(0, 1fr); gap: 20px;
  height: min(80vh, 720px); }

/* Метка на домике: сколько домашек ждут (ученику) / сколько сданных не открыты (учителю).
   ⚠️ Кнопка домика — position: relative только ради этой метки; без «on» её нет в потоке
   вовсе, иначе пустой кружок висел бы на домике всегда. */
.room-hw-btn { position: relative; }
.room-hw-badge { display: none; }
.room-hw-badge.on { display: flex; position: absolute; top: 6px; right: 8px;
  min-width: 17px; height: 17px; padding: 0 4px; align-items: center; justify-content: center;
  border-radius: 9px; background: var(--danger); color: #fff;
  font-size: .62rem; font-weight: 700; line-height: 1; }
/* ⚠️ На телефоне домик всего 30px, и метка 17px в его углу закрывала значок целиком
   (жалоба владельца продукта). Здесь она мельче и вынесена за правый верхний угол кнопки. */
@media (max-width: 760px) {
  .room-hw-badge.on { top: -3px; right: -7px; min-width: 14px; height: 14px; padding: 0 3px;
    border-radius: 7px; font-size: .55rem; }
}

/* ── Левая колонка: список домашек ── */
.hw-side { display: flex; flex-direction: column; min-height: 0; gap: 14px;
  border-right: 1px solid var(--border-2); padding-right: 18px; }
/* Заголовок окна живёт здесь, а не над обеими колонками (modal('') в homework.js). */
.hw-side-title { flex: 0 0 auto; font-size: 1.3em; font-weight: 600; color: var(--text);
  line-height: 1.2; }
.hw-side-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex;
  flex-direction: column; gap: 8px; padding: 3px; margin: -3px; }
.hw-side-empty { color: var(--muted); font-size: .82em; line-height: 1.5; padding: 8px 2px; }
.hw-card { flex: 0 0 auto; display: flex; flex-direction: column; gap: 6px; cursor: pointer;
  padding: 11px 14px 10px; border-radius: 18px; background: var(--hw-ink);
  color: var(--hw-on-ink); outline: none; transition: box-shadow .15s, filter .15s; }
.hw-card:hover { filter: brightness(1.08); }
/* Открытая — кольцом цвета карточки через зазор: заливка у всех одна (макет). */
.hw-card.active { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--hw-ink); }
.hw-card:focus-visible { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent-border); }
.hw-card-top { display: flex; align-items: baseline; gap: 8px; }
.hw-card-date { flex: 1 1 auto; min-width: 0; font-weight: 500; font-size: .95em; }
.hw-card-count { flex: 0 0 auto; font-size: .72em; color: var(--hw-ink-dim); }
.hw-card-title { min-height: 1.4em; font-size: .9em; color: var(--hw-ink-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hw-card-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px;
  min-height: 22px; }
.hw-card-del { flex: 0 0 auto; width: 26px; height: 26px; margin: -3px -6px -3px 0;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  background: none; border: 0; border-radius: 8px; color: var(--hw-ink-dim); }
.hw-card-del:hover { color: var(--danger); }
.hw-card-del svg { width: 16px; height: 16px; display: block; }
.hw-card-add { flex: 0 0 auto; height: 40px; display: flex; align-items: center;
  justify-content: center; border: 0; border-radius: 20px; cursor: pointer;
  background: var(--hw-ink); color: var(--hw-ink-dim); transition: filter .15s, color .15s; }
.hw-card-add:hover { filter: brightness(1.08); color: var(--hw-on-ink); }
.hw-card-add svg { width: 20px; height: 20px; display: block; }

/* ── Правая колонка ── */
.hw-main { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
/* Шапка: название с карандашом слева, дата справа — на одной строке с «Домашние работы». */
.hw-head { display: flex; align-items: baseline; gap: 12px; padding-bottom: 12px; }
.hw-head-title { flex: 1 1 auto; min-width: 0; font-size: 1.2em; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.2; }
.hw-head-date { flex: 0 0 auto; margin-left: auto; font-size: .8em; color: var(--muted);
  white-space: nowrap; }
.hw-title { flex: 0 1 auto; min-width: 0; display: inline-flex; align-items: center; gap: 4px;
  cursor: text; }
/* Название у учителя — поле, которое выглядит как заголовок; ширина по тексту (size из
   homework.js или field-sizing). 16px — меньше iOS зумит страницу при фокусе. */
.hw-title-input { min-width: 0; max-width: 100%; field-sizing: content; font: inherit;
  font-size: max(16px, 1.2em); font-weight: 500; line-height: 1.2; color: var(--text);
  background: none; border: 0; border-bottom: 1px solid transparent; border-radius: 0;
  padding: 0; outline: none; text-overflow: ellipsis; }
.hw-title-input::placeholder { color: var(--text-dim); }
.hw-title-input:focus { border-bottom-color: var(--accent-border); }
.hw-title-pen { flex: 0 0 auto; color: var(--muted); }
.hw-title-pen svg { width: 15px; height: 15px; display: block; }
.hw-title:hover .hw-title-pen { color: var(--text); }
.hw-icon-btn { width: 30px; height: 30px; display: inline-flex; align-items: center;
  justify-content: center; border-radius: 8px; cursor: pointer; background: none;
  border: 1px solid transparent; color: var(--text-dim); }
.hw-icon-btn:hover { border-color: var(--border-2); color: var(--text); }
.hw-icon-btn.danger:hover { color: var(--danger); border-color: var(--danger); }
.hw-icon-btn svg { width: 15px; height: 15px; display: block; }

.hw-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 4px 4px 14px 0; }
.hw-foot { flex: 0 0 auto; padding-top: 12px; }
.hw-foot:empty { display: none; }
/* «＋» нового задания — под лентой, контуром (макет): текст задания — «Заметка» в том же
   окне выбора, поэтому кнопка одна. */
.hw-add-item { display: flex; align-items: center; justify-content: center; width: 100%;
  height: 40px; margin-top: 14px; border-radius: 20px; cursor: pointer; background: none;
  border: 2px solid var(--border-2); color: var(--muted); transition: border-color .15s, color .15s; }
.hw-add-item:hover { border-color: var(--accent-border); color: var(--text); }
.hw-add-item svg { width: 20px; height: 20px; display: block; }
.hw-empty-hint { margin: 4px 2px 0; line-height: 1.5; }
/* Подвал учителя — во всю ширину: кнопка действия или плашка состояния (не кнопка). */
.hw-foot-btn, .hw-foot-bar { display: flex; align-items: center; justify-content: center;
  width: 100%; min-height: 38px; padding: 8px 16px; border: 0; border-radius: 12px;
  font: inherit; font-size: .9em; font-weight: 500; text-align: center; }
.hw-foot-btn { cursor: pointer; transition: filter .15s; }
.hw-foot-btn:hover:not(:disabled):not(.is-static) { filter: brightness(1.08); }
.hw-foot-btn:disabled { opacity: .6; cursor: default; }
.hw-foot-btn.ink { background: var(--hw-ink); color: var(--hw-on-ink); }
.hw-foot-btn.ok { background: var(--hw-accept); color: var(--hw-accept-fg); }
.hw-foot-btn.is-static { cursor: default; }
.hw-foot-bar { font-size: .95em; }
.hw-foot-bar.sent { background: var(--hw-sent); color: var(--hw-sent-fg); }
.hw-foot-bar.back { background: var(--hw-back); color: var(--hw-back-fg); }
/* Прислана: поле комментария и две кнопки стопкой справа. */
.hw-review { display: flex; gap: 10px; align-items: stretch; }
.hw-foot-comment { flex: 1 1 auto; min-width: 0; min-height: 68px; resize: none;
  padding: 10px 12px; border: 1px solid transparent; border-radius: 12px; outline: none;
  background: color-mix(in srgb, var(--text) 15%, var(--surface)); color: var(--text);
  font: inherit; font-size: max(16px, .9em); line-height: 1.4; }
.hw-foot-comment::placeholder { color: var(--text-dim); }
.hw-foot-comment:focus { border-color: var(--accent-border); }
.hw-review-acts { flex: 0 0 180px; display: flex; flex-direction: column; gap: 8px; }
.hw-review-acts .hw-foot-btn { flex: 1 1 0; }
.hw-submit-row, .hw-done { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  justify-content: flex-end; }
.hw-submit-row .btn-primary { margin-left: auto; }
.hw-hint { font-size: .78em; color: var(--muted); }

.hw-items { display: flex; flex-direction: column; gap: 14px; }
.hw-item { background: var(--surface-2); border: 1px solid transparent;
  border-radius: var(--radius); padding: 12px 14px; }
.hw-item-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.hw-item-acts { margin-left: auto; display: flex; gap: 2px; }
.hw-from { font-size: .7em; color: var(--muted); }
/* Учителю в списке заданий (и ученику после сдачи) упражнения показываются, но не
   решаются: клики по ним ничего не меняют, прокрутка страницы остаётся. */
.hw-locked .ex-render { pointer-events: none; }

.hw-status { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 14px; margin-bottom: 14px; border-radius: var(--radius);
  background: var(--surface-2); border: 1px solid transparent; }
.hw-status.ok { border-color: var(--accent-border);
  background: color-mix(in srgb, var(--accent) 10%, var(--surface-2)); }
/* На проверке: работа у учителя — тот же акцент, что у «принято», но только контуром:
   решения ещё нет. */
.hw-status.check { border-color: var(--accent-border); }
/* Черновик: ученик домашку не видит — сигнал «не забудьте отправить». */
.hw-status.draft { border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
/* На доработке: работа у ученика, ему есть что исправлять — не «ок» и не «ещё не начал». */
.hw-status.back { border-color: color-mix(in srgb, var(--warn) 45%, transparent);
  background: color-mix(in srgb, var(--warn) 9%, var(--surface-2)); }
.hw-status-main { flex: 1 1 200px; min-width: 0; }
.hw-status-title { font-weight: 600; font-size: .92em; }
.hw-status-sub { font-size: .76em; color: var(--muted); margin-top: 2px; }

.hw-chip { flex: 0 0 auto; padding: 2px 10px; border-radius: 999px; font-size: .7em;
  font-weight: 600; line-height: 1.5; white-space: nowrap; justify-self: start; }
.hw-chip.ok { background: var(--hw-ok); color: var(--hw-ok-fg); }
.hw-chip.check { background: var(--hw-check); color: var(--hw-check-fg); }
.hw-chip.draft { background: var(--hw-draft); color: var(--hw-draft-fg); }
.hw-chip.sent { background: var(--hw-sent); color: var(--hw-sent-fg); }
.hw-chip.back { background: var(--hw-back); color: var(--hw-back-fg); }

/* Комментарий учителя к возврату — текст человека: переносы строк сохраняем. */
.hw-comment { margin: -4px 0 14px; padding: 10px 14px; border-radius: var(--radius);
  background: var(--surface-2); border-left: 3px solid var(--warn); }
.hw-comment-label { font-size: .72em; color: var(--muted); margin-bottom: 4px; }
.hw-comment-text { font-size: .88em; line-height: 1.5; white-space: pre-wrap;
  overflow-wrap: anywhere; }
.hw-comment-input { width: 100%; resize: vertical; min-height: 96px; font-size: 16px; }

/* Проверка учителя по заданию (views/hw-correction.js): правка текста «Письма» видна как
   «зачёркнуто / добавлено» поверх ответа ученика, рядом — комментарий к заданию. */
.hw-mark-slot { margin-top: 10px; display: flex; flex-direction: column; gap: 8px; }
.hw-mark { padding: 10px 12px; border-radius: var(--radius-sm); background: var(--bg);
  border-left: 3px solid var(--accent-border); display: flex; flex-direction: column; gap: 6px; }
.hw-item > .hw-mark { margin-top: 10px; }
.hw-mark-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.hw-mark-label { font-size: .72em; color: var(--muted); }
.hw-mark-tabs { display: inline-flex; gap: 2px; margin-left: auto; padding: 2px;
  border-radius: 999px; background: var(--surface-2); }
.hw-mark-tabs button { border: 0; background: transparent; color: var(--text-dim);
  font: inherit; font-size: .72em; padding: 3px 10px; border-radius: 999px; cursor: pointer; }
.hw-mark-tabs button.active { background: var(--surface); color: var(--text); }
.hw-diff { font-size: .92em; line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere; }
.hw-diff-del { color: var(--wrong); text-decoration: line-through;
  background: color-mix(in srgb, var(--wrong) 12%, transparent); border-radius: 3px; }
.hw-diff-ins { color: var(--correct); font-weight: 600;
  background: color-mix(in srgb, var(--correct) 14%, transparent); border-radius: 3px; }
.hw-diff-del + .hw-diff-ins { margin-left: 3px; }
.hw-diff + .hw-mark-label, .hw-hint + .hw-mark-label { margin-top: 6px; }
.hw-mark-acts { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.hw-mark-edit .essay-box { min-height: 120px; background: var(--surface-2); }
.hw-btn-ico svg { width: 14px; height: 14px; display: block; }
.hw-mark-acts .btn { display: inline-flex; align-items: center; gap: 6px; }
@media (max-width: 640px) {
  /* Поле меньше 16px iOS зумит при фокусе. */
  .hw-mark-edit .essay-box { font-size: 16px; }
}

.hw-empty, .hw-empty-items { display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 8px; height: 100%; text-align: center; color: var(--muted);
  font-size: .88em; line-height: 1.5; padding: 20px; }
.hw-empty-ico svg { width: 42px; height: 42px; opacity: .45; display: block; }
.hw-error { color: var(--danger); padding: 20px; }
.hw-note { font-size: .78em; color: var(--muted); line-height: 1.5; margin-bottom: 12px; }

/* ⚠️ Телефон: под САМУ домашку должно уходить почти всё место (жалоба владельца: окно
   прокрутки заданий было в треть экрана, остальное съедали шапка, дубль списка и подвал).
   • Лист — на всю высоту, домашка тянется флексом, а не фиксированными 78vh.
   • Список домашек — заголовок и под ним одна строка компактных карточек «дата + статус»
     (без числа заданий и названия), «＋» учителя — в конце той же строки; корзина — только
     у открытой карточки. Ученику с одной домашкой списка нет вовсе (.hw-side-solo): он
     повторял шапку.
   • Шапка — одна строка: название и дата рядом.
   • Подвал — поле комментария над двумя кнопками в строку. */
@media (max-width: 760px) {
  .modal:has(> .hw-modal) { display: flex; flex-direction: column; overflow: hidden;
    height: calc(100dvh - 24px); max-height: calc(100dvh - 24px);
    padding-bottom: calc(10px + env(safe-area-inset-bottom)); }
  .hw-modal { flex: 1 1 auto; min-height: 0; height: auto;
    grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); gap: 8px; }
  .hw-side { gap: 8px; border-right: 0; padding: 0 0 8px; border-bottom: 1px solid var(--border-2); }
  .hw-side.hw-side-solo { display: none; }
  /* Без списка домашка — единственный ребёнок сетки и попала бы в строку `auto`
     (по содержимому), оставив под кнопкой пустоту. */
  .hw-modal:has(> .hw-side-solo) { grid-template-rows: minmax(0, 1fr); }
  .hw-side-title { font-size: 1.05em; }
  .hw-side-list { flex-direction: row; align-items: center; gap: 6px;
    overflow-x: auto; overflow-y: hidden; padding: 3px; scrollbar-width: none; }
  .hw-side-list::-webkit-scrollbar { display: none; }
  .hw-card { flex-direction: row; align-items: center; gap: 6px; padding: 5px 6px 5px 12px;
    border-radius: 999px; }
  .hw-card-top { display: contents; }
  .hw-card-date { flex: 0 0 auto; font-size: .82em; white-space: nowrap; }
  .hw-card-count, .hw-card-title { display: none; }
  .hw-card-foot { display: contents; }
  .hw-card .hw-chip { padding: 2px 7px; font-size: .64em; }
  .hw-card-del { display: none; margin: 0; width: 22px; height: 22px; }
  .hw-card.active .hw-card-del { display: inline-flex; }
  .hw-card-add { width: 36px; height: 30px; border-radius: 999px; }
  .hw-card-add svg { width: 16px; height: 16px; }
  .hw-side-empty { white-space: nowrap; padding: 4px 2px; }

  .hw-head { padding-bottom: 8px; gap: 8px; }
  .hw-head-title { font-size: 1em; }
  .hw-title-input { font-size: 16px; }
  .hw-head-date { font-size: .74em; }
  .hw-icon-btn { width: 28px; height: 28px; }

  .hw-body { padding: 4px 0 10px; }
  .hw-items { gap: 10px; }
  .hw-item { padding: 10px 11px; }
  .hw-status { padding: 8px 12px; margin-bottom: 10px; gap: 8px; }
  .hw-foot { padding-top: 8px; }
  .hw-review { flex-direction: column; gap: 8px; }
  .hw-foot-comment { min-height: 54px; }
  .hw-review-acts { flex: 0 0 auto; flex-direction: row; }
  .hw-done { gap: 4px 10px; }
  .hw-done .hw-hint { font-size: .72em; }
}

/* ── Конвертер бумажных учебников (views/textbook.js) ───────────── */
/* ⚠️ БЕЗ своего max-width: раньше 940px сужали редактор юнита внутри уже
   ограниченного `.app` (1180px) — карточка со страницами и полосой миниатюр
   выглядела заметно уже шапки, хотя обе живут в одном контейнере. Теперь
   `.tb-page` просто занимает всю ширину `.app`, как и остальные страницы. */
.tb-page { width: 100%; }
.tb-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; margin-bottom: 18px;
  --tb-head-h: 40px; }
.tb-title { margin: 0; font-size: 1.35rem; }
.tb-head-main { display: flex; flex-direction: column; gap: 7px; flex: 1 1 auto; min-width: 0; }
/* Название учебника — поле ввода, замаскированное под заголовок: с текстом внутри
   в покое выглядит как обычный `.tb-title` (без рамки и фона), рамка проявляется
   только по наведению и фокусу — так видно, что оно кликабельно, но шапка не
   пестрит полями. Пустое поле (плейсхолдер «Введите название») — исключение:
   рамка видна ВСЕГДА, иначе на пустой шапке нечего было бы заметить и нажать.
   `:placeholder-shown` даёт это ровно по значению, без своего класса и JS. */
.tb-title-input { width: 100%; height: var(--tb-head-h); font: inherit; font-size: 1.1rem;
  font-weight: 300; color: var(--text); background: transparent; border: 1px solid transparent;
  border-radius: var(--radius-sm); padding: 0 9px; }
.tb-title-input:hover, .tb-title-input:placeholder-shown { border-color: var(--border-2); }
.tb-title-input:focus { outline: none; background: var(--bg); border-color: var(--accent-border);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 9%, transparent); }
.tb-head-meta { display: flex; gap: 8px; flex-wrap: wrap; }
.tb-head-meta .select { width: auto; padding: 5px 30px 5px 10px; font-size: .82em;
  background-position: right 9px center; background-size: 12px; }
.tb-head-acts { display: flex; gap: 8px; flex-shrink: 0; }
/* Кнопки рядом с полем названия — ровно его высоты (общая `--tb-head-h`), иконка
   корзины — квадрат той же стороны. Только в шапке учебника (сосед `.tb-head-main`):
   у экрана юнита рядом не поле, а обычный заголовок, там подгонять не к чему. */
.tb-head-main + .tb-head-acts .btn { height: var(--tb-head-h); }
.tb-head-main + .tb-head-acts .tb-icon-btn { width: var(--tb-head-h); padding: 0; }
.tb-back { background: none; border: 0; color: var(--muted); font: inherit; font-size: .8em;
  cursor: pointer; padding: 0 0 4px; }
.tb-back:hover { color: var(--text); }

.tb-units { display: flex; flex-direction: column; gap: 8px; }
.tb-unit { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: var(--surface); border: 1px solid transparent; border-radius: var(--radius);
  padding: 12px 16px; cursor: pointer; font: inherit; color: var(--text); }
.tb-unit:hover { background: var(--surface-2); }
.tb-unit-n { flex: 0 0 30px; height: 30px; border-radius: 50%; background: var(--surface-2);
  display: grid; place-items: center; font-size: .8em; color: var(--muted); }
.tb-unit-t { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.tb-unit-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tb-unit-st { font-size: .72em; color: var(--muted); }
.tb-unit-p { flex: 0 0 auto; font-size: .75em; color: var(--muted); }
/* Собранный юнит помечается цветом статуса — тем же, что «проведён» в расписании:
   одно и то же состояние на платформе обязано выглядеть одинаково. */
.tb-unit-ready .tb-unit-n { background: color-mix(in srgb, var(--lesson-done) 26%, transparent);
  color: var(--lesson-done); }
.tb-unit-error .tb-unit-n { background: color-mix(in srgb, var(--danger) 26%, transparent); color: var(--danger); }
.tb-add { background: var(--surface-2); border: 0; border-radius: var(--radius); padding: 11px;
  color: var(--muted); font: inherit; cursor: pointer; }
.tb-add:hover { color: var(--text); }

/* Слоты страниц: ряд ячеек «страница 1…5», клик по заполненной — убрать. */
/* ⚠️ auto-fill, а не repeat(5): школа может поднять предел до 15 страниц, и
   пятнадцать колонок в ряд превратили бы страницы в марки. Ряд переносится сам. */
.tb-slots { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 10px; margin-bottom: 16px; }
.tb-slot { position: relative; background: var(--surface); border-radius: var(--radius);
  aspect-ratio: 3 / 4; overflow: hidden; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 6px; }
.tb-slot-empty { background: var(--surface-2); color: var(--muted); }
.tb-slot-n { font-size: .72em; }
.tb-slot-plus { font-size: 1.6em; opacity: .5; }
.tb-slot-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  object-position: top center; }
.tb-slot-cap, .tb-slot-txt { position: relative; z-index: 1; font-size: .68em; padding: 1px 7px;
  border-radius: 6px; background: color-mix(in srgb, var(--bg) 78%, transparent); }
.tb-slot-cap { position: absolute; left: 6px; bottom: 6px; }
.tb-slot-txt { position: absolute; right: 6px; bottom: 6px; color: var(--lesson-done); }
.tb-slot-del { position: absolute; top: 6px; right: 6px; z-index: 2; width: 26px; height: 26px;
  display: grid; place-items: center; border: 0; border-radius: 50%; cursor: pointer;
  background: color-mix(in srgb, var(--bg) 72%, transparent); color: var(--text); }
.tb-slot-del svg { width: 14px; height: 14px; }
.tb-slot-del:hover { background: var(--danger); color: #fff; }

/* Источник страниц: выбор PDF, навигатор и превью. */
.tb-src-box { background: var(--surface); border-radius: var(--radius); padding: 18px; }
.tb-src-main { text-align: left; }
/* Шапка карточки: текст слева, кнопки-действия столбцом справа — вместо прежней
   центрированной колонки текст-над-кнопками. */
/* ⚠️ `flex-wrap: wrap` + `min-width` у текста — не украшение, а страховка от
   наползания: без них длинное описание при узкой-но-ещё-не-мобильной ширине
   (карточка теперь во всю ширину `.app`, а не заперта в 940px) ужималось бы до
   нескольких пикселей и залезало под колонку кнопок. Теперь при нехватке места
   кнопки сами переносятся на свою строку — как на телефоне, только без резкого
   скачка на границе @media. */
.tb-src-head { display: flex; align-items: flex-start; gap: 12px 20px; flex-wrap: wrap; }
.tb-src-text { flex: 1 1 320px; min-width: 260px; }
.tb-src-text b { display: block; font-size: 1.02em; margin-bottom: 5px; }
.tb-src-text p { margin: 0; font-size: .92em; font-weight: 300; }
/* ⚠️ Подсказка и предупреждение ЖИВУТ ВНУТРИ `.tb-src-text`, а не отдельной
   строкой во всю ширину карточки: полная ширина заметно шире текстовой колонки
   (справа от нее стоят кнопки), и длинная подсказка растягивалась в одну строку
   аж до правого края карточки — визуально «заезжая» под колонку кнопок, хотя
   технически была уже ниже них. Общая колонка — общая ширина для всех абзацев. */
.tb-src-text p + p { margin-top: 10px; }
/* `margin-left: auto` вместо `justify-content: space-between` на родителе: так
   кнопки прижаты к правому краю И когда делят строку с текстом, И когда сами
   переносятся на отдельную — `space-between` для строки из одного элемента
   прижал бы её к левому краю. */
.tb-src-acts { display: flex; flex-direction: column; gap: 8px; flex: 0 0 auto;
  margin-top: 0; margin-left: auto; }
.tb-src-acts .btn { white-space: nowrap; }
.tb-src-file { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.tb-flag { font-size: .7em; color: var(--muted); background: var(--surface-2);
  padding: 2px 8px; border-radius: 6px; }
.tb-flag.ok { color: var(--lesson-done); }
.tb-prev { display: flex; align-items: center; justify-content: center; gap: 10px; }
.tb-prev-canvas { max-width: 100%; max-height: 460px; border-radius: 8px; background: #fff; }
.tb-nav { flex: 0 0 auto; font-size: 1.1em; line-height: 1; }
.tb-src-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.tb-num { width: 78px; flex: 0 0 auto; }
.tb-src-sep { flex: 0 0 1px; height: 22px; background: var(--border-2); margin: 0 4px; }
.tb-src-status { min-height: 16px; margin-top: 8px; }

@media (max-width: 760px) {
  /* На телефоне пять ячеек в ряд превращаются в марки: страницы должны быть узнаваемы. */
  .tb-slots { grid-template-columns: repeat(3, 1fr); }
  .tb-head { flex-direction: column; }
  /* Столбцу справа не хватает ширины рядом с текстом — кнопки уходят под текст
     и растягиваются в ряд во всю ширину, как в остальных карточках экрана. */
  .tb-src-head { flex-direction: column; }
  .tb-src-acts { flex-direction: row; flex-wrap: wrap; width: 100%; margin-top: 12px;
    margin-left: 0; }
  /* Базис 0, а не auto: с auto «Загрузить картинку» (текст длиннее) неизбежно
     получала бы больше места, чем «Открыть PDF» — то же требование «кнопки
     одного размера», что и в столбце на десктопе, только в ряд. */
  .tb-src-acts > .btn { flex: 1 1 0; }
  .tb-prev-canvas { max-height: 320px; }
}
.tb-contents { margin-bottom: 16px; }
.tb-analyzing { display: flex; align-items: center; gap: 14px; }
.tb-spin { flex: 0 0 22px; width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid var(--border-2); border-top-color: var(--accent);
  animation: tb-spin 0.9s linear infinite; }
@keyframes tb-spin { to { transform: rotate(360deg); } }
/* Уважение к «Уменьшению движения»: вращение — единственная анимация тут, и
   у неё есть спокойная замена (просто кольцо), в отличие от перелива кнопки. */
@media (prefers-reduced-motion: reduce) { .tb-spin { animation-duration: 3s; } }

/* Полоса миниатюр для выбора страниц оглавления (views/textbook.js). */
.tb-thumbs { display: flex; gap: 8px; overflow-x: auto; padding: 4px 2px 8px;
  margin: 10px 0 4px; scrollbar-width: thin; }
.tb-thumb { flex: 0 0 auto; width: 84px; background: none; border: 0; padding: 0;
  cursor: pointer; font: inherit; color: var(--muted); }
.tb-thumb-img { display: block; width: 84px; height: 112px; border-radius: 6px;
  background: var(--surface-2) center/cover no-repeat; outline: 2px solid transparent;
  outline-offset: 2px; }
.tb-thumb-n { display: block; font-size: .7em; text-align: center; margin-top: 3px; }
.tb-thumb:hover .tb-thumb-img { outline-color: var(--border-2); }
/* Отмеченная страница обводится акцентом: галочку на миниатюре в 84px не разглядеть,
   а обводка читается и на пёстром скане. */
.tb-thumb.on .tb-thumb-img { outline-color: var(--accent); }
.tb-thumb.on .tb-thumb-n { color: var(--accent-text, var(--accent)); font-weight: 600; }

/* Сборка урока из страниц юнита (views/textbook.js). */
/* Верстак страниц: один и тот же блок на экране юнита и в окне импорта. */
.tb-work { display: block; }
/* Окно «Из учебника PDF» в конструкторе урока: то же, но в модалке. Своя ширина —
   ряд слотов и превью страницы в стандартные 620px не помещаются. */
.modal:has(> .tb-import) { max-width: 860px; }
.tb-import > .muted { margin-bottom: 14px; }

.tb-build { margin-bottom: 16px; }
.tb-build-box { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.tb-build-main { flex: 1 1 320px; min-width: 0; }
.tb-build-acts { display: flex; gap: 8px; flex-shrink: 0; }
.tb-go { padding: 13px 22px; font-size: .9em; }
.tb-bar { height: 6px; border-radius: 3px; background: var(--surface-2); overflow: hidden;
  margin: 8px 0 6px; }
.tb-bar > span { display: block; height: 100%; background: var(--accent);
  transition: width .4s ease; }
.tb-qa { margin: 10px 0 0; padding-left: 18px; color: var(--muted); font-size: .78em; }
.tb-qa li { margin: 2px 0; }
@media (max-width: 760px) {
  .tb-build-acts { width: 100%; }
  .tb-build-acts > .btn { flex: 1 1 auto; }
}

/* ── Спарклайн: динамика внутри карточки-показателя ────────────────────────
   Один ряд без осей и легенды — форма отвечает на «как менялось», точное значение
   даёт подсказка по наведению и текстовая подпись (aria-label + title у самого SVG).
   ⚠️ Линия — в приглушённом тоне текста и БЕЗ точек: акцентный кружок у последнего
   значения («мы сейчас здесь») убран по решению владельца продукта — он был единственным
   цветным пятном в колонке виджетов. Цвет остался только у курсорной точки, которая
   появляется под указателем вместе с подсказкой. */
.spark-wrap { position: relative; margin: 3px 0 1px; }
/* height задан в CSS, а ширину график занимает целиком (preserveAspectRatio="none"):
   так карточка не меняет высоту от ширины экрана. overflow:visible — иначе точка
   и её обводка срезались бы краем viewBox. */
.spark { display: block; width: 100%; height: 40px; overflow: visible; touch-action: pan-y; }
/* ⚠️ `stroke-opacity: .25` — ещё ВДВОЕ прозрачнее прежних .5 (третья просьба владельца
   продукта, «графики на 50% прозрачнее»): линия и так приглушённого тона `--text-dim`,
   но в колонке виджетов она читалась наравне с числами, ради которых карточку и смотрят.
   Ниже опускать некуда: на светлой теме .2 уже не видно. */
.spark-line { fill: none; stroke: var(--text-dim); stroke-width: 2; stroke-opacity: .25;
  stroke-linecap: round; stroke-linejoin: round; }
/* Точка курсора нарисована отрезком нулевой длины с круглым концом — её не плющит
   растяжение по ширине, в отличие от <circle>. Постоянной точки у последнего значения
   больше нет (см. шапку блока). */
.spark-cur-dot { stroke-linecap: round; }
.spark-cursor { display: none; }
.spark.hov .spark-cursor { display: block; }
.spark-rule { stroke: var(--border-2); stroke-width: 1; }
/* Курсорная точка — с кольцом цвета поверхности: без него она сливается с линией,
   поверх которой стоит (правило «2px surface ring на перекрывающихся метках»). */
.spark-cur-ring { stroke: var(--surface); stroke-width: 10; }
.spark-cur-dot { stroke: var(--accent-text); stroke-width: 6.5; }
.spark-tip { position: absolute; bottom: calc(100% - 1px); transform: translateX(-50%);
  white-space: nowrap; background: var(--surface-2); color: var(--text);
  font-size: .68em; padding: 3px 7px; border-radius: 8px; box-shadow: var(--shadow);
  opacity: 0; pointer-events: none; transition: opacity .12s; z-index: 3; }
.spark-wrap.hov .spark-tip { opacity: 1; }
/* У крайних точек подсказка иначе вылезает за карточку */
.spark-tip[data-side="l"] { transform: none; }
.spark-tip[data-side="r"] { transform: translateX(-100%); }

/* ── Карточка-показатель с графиком ───────────────────────────────────────
   Анатомия иная, чем у .tdash-stat: подпись и действие — одной строкой сверху, под
   ними график, числа — в подвале. Вся карточка НЕ кнопка (в отличие от «Следующего
   урока»): её главная площадь — график, он отвечает на наведение, а действие вынесено
   отдельной ссылкой в углу. */
.tdash-card { align-items: flex-start; }
.tdash-card .tdash-stat-main { display: flex; flex-direction: column; gap: 3px; }
.tdash-card-top { display: flex; align-items: center; justify-content: space-between;
  gap: 8px; min-width: 0; }
.tdash-card-top .tdash-stat-lbl { flex: 0 1 auto; }
/* Крупное число карточки — пропорциональными цифрами: tabular-nums даёт каждой
   цифре ширину нуля, и «600 ₽» на таком кегле выглядит разреженным. Табличные
   цифры остаются там, где числа стоят колонкой (детализация начислений).
   ⚠️ Кегль МЕЛЬЧЕ общего `.tdash-stat-val` (решение владельца продукта «меньше»):
   в карточке с графиком главное — сама кривая, а ставка рядом с ней смотрелась
   заголовком карточки, а не одним из её чисел. */
.tdash-card .tdash-stat-val { font-variant-numeric: normal; margin-top: 0; font-size: 1.06em; }

.tdash-link { display: inline-flex; align-items: center; gap: 2px; flex: 0 0 auto;
  padding: 0; border: 0; background: none; font: inherit; font-size: .74em;
  color: var(--muted); cursor: pointer; white-space: nowrap;
  transition: color .14s, transform .14s; }
.tdash-link:hover { color: var(--accent-text); transform: translateX(1px); }
.tdash-link-i { display: inline-flex; }
.tdash-link svg { width: 15px; height: 15px; }
/* «Ставка» — компактная строка вместо карточки с графиком (макет владельца продукта, вдвое ниже
   прежней): «Ставка | Месяц | История ›», между блоками тонкие вертикальные разделители.
   ⚠️ Высота задана ПАДДИНГОМ, а не min-height: карточка обязана расти вместе с кеглем страницы. */
.tdash-earn { align-items: center; justify-content: space-between; gap: 14px; padding: 16px 18px; }
.tdash-earn-cell { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.tdash-earn-val { font-size: 1.06em; font-weight: 700; color: var(--text); white-space: nowrap;
  line-height: 1.2; }
.tdash-earn-sep { flex: 0 0 auto; align-self: stretch; width: 1px; margin: 2px 0;
  background: color-mix(in srgb, var(--text) 14%, transparent); }
.tdash-earn .tdash-link { margin-left: auto; font-size: .8em; }
@media (max-width: 760px) {
  .tdash-stats > .tdash-earn { grid-column: 1 / -1; padding: 14px 16px; }
  /* ⚠️ «Ставка» занимает целый ряд и сдвигает чётность: остальные плитки встают парами со
     второй позиции, поэтому «одинокая» последняя теперь на ЧЁТНОМ месте, а на нечётном
     (две плитки после ставки) — нет. Общее правило `:nth-child(odd)` выше без поправки
     растянуло бы «Уроки» и оставило «Ученики» половинкой с пустотой рядом. */
  .tdash-stats > .tdash-earn ~ :last-child:nth-child(odd) { grid-column: auto; }
  .tdash-stats > .tdash-earn ~ :last-child:nth-child(even) { grid-column: 1 / -1; }
}

/* Ряд аватарок учеников в подвале «Учеников» (макет владельца продукта).
   ⚠️ Кружки идут ВНАХЛЁСТ и обведены цветом поверхности — то же правило, что у курсорной
   точки графика: без кольца соседние аватарки сливаются в серую полосу.
   ⚠️ `margin-left: auto` прижимает ряд ВПРАВО, к числу «4 чел.»: подвал с двумя
   элементами раскладывается `space-between` (класс `.multi`), и без этого четыре
   аватарки висели бы у левого края, а между ними и числом зияла бы половина карточки.
   Ряд не сжимается и не обрезается: сколько кружков влезает, ограничено числом
   (STU_AV_MAX в schedule.js) и медиазапросами ниже — обрезанная аватарка у края
   читалась бы как сломанная вёрстка. */
.tdash-avrow { margin-left: auto; flex: 0 0 auto; display: inline-flex; align-items: center; }
/* ⚠️ Составной селектор `.avatar.tdash-av`: базовое правило `.avatar` (40px) стоит НИЖЕ
   по файлу, и при равной специфичности размер выигрывал бы оно — та же ловушка, что у
   `.avatar.urow-av` и остальных переопределений аватарки. */
/* ⚠️ Буква — ПРИГЛУШЁННЫМ тоном, а не акцентом, как у одиночных аватарок по всей
   платформе: десяток цветных букв подряд перекрикивал бы и число рядом, и график
   над ними (в карточке акцент один — ссылка «Классы ›»). */
.avatar.tdash-av { flex: 0 0 auto; width: 22px; height: 22px; font-size: .62em;
  color: var(--text-dim); object-fit: cover; box-shadow: 0 0 0 2px var(--surface); }
.avatar.tdash-av + .avatar.tdash-av { margin-left: -7px; }
/* Подвал: один показатель прижат вправо (там же, где итог месяца в «Ставке»),
   несколько — раскладываются по ширине в порядке «месяц · неделя · день». */
.tdash-card-foot { display: flex; align-items: baseline; justify-content: flex-end;
  gap: 8px; min-width: 0; }
.tdash-card-foot.multi { justify-content: space-between; gap: 6px; }
.tdash-metric { flex: 0 1 auto; min-width: 0; display: inline-flex; align-items: baseline;
  gap: 4px; white-space: nowrap; }
.tdash-metric-k, .tdash-metric-u { font-size: .74em; color: var(--muted); }
.tdash-metric-v { font-size: 1.05em; font-weight: 700; color: var(--text); }

/* ── Детализация начислений (окно) и ставка в профиле ─────────────────────── */
.pay-nav { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.pay-arrow { padding: 5px 9px; }
.pay-arrow svg { width: 16px; height: 16px; display: block; }
.pay-month { font-size: 1.05em; font-weight: 700; min-width: 130px; }
.pay-rate { margin-left: auto; font-size: .8em; color: var(--muted); }
.pay-rows { display: flex; flex-direction: column; }
/* Строка начисления: дата · ученик · за что · сумма. Зебра вместо линий-разделителей —
   тем же приёмом, что в попапе урока (у школы с перекрашенной палитрой линии кричат). */
.pay-row { display: grid; grid-template-columns: 96px minmax(0, 1fr) minmax(0, 1.3fr) auto;
  align-items: baseline; gap: 10px; padding: 9px 10px; border-radius: var(--radius-sm);
  font-size: .84em; }
.pay-row:nth-child(odd) { background: color-mix(in srgb, var(--text) 5%, transparent); }
.pay-when { display: flex; gap: 6px; align-items: baseline; white-space: nowrap; }
.pay-when b { font-weight: 600; }
.pay-when i, .pay-who i { font-style: normal; color: var(--muted); font-size: .88em; }
.pay-who { min-width: 0; display: flex; gap: 6px; align-items: baseline; }
.pay-who b { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pay-what { min-width: 0; color: var(--text-dim); }
/* Знак несёт и подпись («−600 ₽»), и цвет: цвет здесь — усиление, а не единственный
   источник смысла. Тона взяты из индикации оценок — они ВНЕ палитры школы, потому что
   «начислено» и «удержано» не должны менять смысл вместе с фирменным цветом. */
.pay-sum { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pay-sum.pos { color: var(--grade-good); }
.pay-sum.neg { color: var(--grade-low); }
.pay-sum.zero { color: var(--muted); }
.pay-total { display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin-top: 12px; padding: 11px 10px; border-radius: var(--radius-sm);
  background: var(--surface-2); }
.pay-total-k { font-size: .85em; color: var(--muted); }
.pay-total-v { font-size: 1.2em; font-weight: 700; font-variant-numeric: tabular-nums; }
.pay-total-v.pos { color: var(--grade-good); }
.pay-total-v.neg { color: var(--grade-low); }
.pay-note { margin: 10px 2px 0; font-size: .76em; color: var(--muted); line-height: 1.5; }

.pay-card { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.pay-card .field { width: 100%; margin: 0; }
.pay-rate-row { display: flex; align-items: center; gap: 8px; }
.pay-rate-input { width: 120px; font-variant-numeric: tabular-nums; }
.pay-cur { color: var(--muted); }

@media (max-width: 620px) {
  /* В телефон четыре колонки не помещаются: дата и сумма остаются строкой, ученик и
     причина уходят под них — обрезать причину многоточием нельзя, в ней весь смысл. */
  .pay-row { grid-template-columns: auto 1fr; gap: 4px 10px; }
  .pay-who { grid-column: 1; grid-row: 2; }
  .pay-what { grid-column: 1 / -1; grid-row: 3; }
  .pay-sum { grid-column: 2; grid-row: 1; text-align: right; }
  .pay-rate { margin-left: 0; width: 100%; }
  .pay-month { min-width: 0; flex: 1 1 auto; }
}

/* ── Отмены, переносы и неявки (views/lesson-rules.js) ──────────────
   ⚠️ Плашка последствий — не «ошибка» и не «успех», а ПРЕДУПРЕЖДЕНИЕ о цене действия,
   поэтому у неё свой тон, а не --danger: человек ещё ничего не сделал, и красный
   прямоугольник в диалоге читался бы как «что-то уже сломалось». */
.lr-note { border-radius: var(--radius-sm); padding: 11px 13px; margin: 0 0 12px;
  background: color-mix(in srgb, var(--text) 5%, transparent); }
.lr-note.lr-warn { background: color-mix(in srgb, var(--warn) 14%, transparent); }
.lr-note-text { font-size: .84em; line-height: 1.5; color: var(--text); }
.lr-note-sub { margin-top: 6px; font-size: .76em; color: var(--muted); line-height: 1.45; }

/* Диалоги отмены и переноса — одной ширины и одного устройства: правила одной
   строкой сверху, поле комментария со счётчиком, выбор, итог, кнопки внизу. */
.modal:has(> .lr-dlg) { max-width: 580px; }
.lr-lead { margin: 0 0 16px; font-size: .88em; line-height: 1.5; color: var(--text-dim); }
.lr-field { position: relative; margin-bottom: 14px; }
.lr-note-in { width: 100%; min-height: 72px; resize: none; padding-bottom: 24px; }
.lr-count { position: absolute; right: 12px; bottom: 9px; font-size: .74em; color: var(--muted);
  pointer-events: none; }
.lr-foot { display: flex; gap: 12px; margin-top: 22px; }
.lr-foot > .btn { flex: 1 1 0; justify-content: center; padding: 12px 16px; font-size: .92em;
  border-radius: 14px; }
.lr-foot-end { justify-content: flex-end; }
.lr-foot-end > .btn { flex: 0 1 50%; }
.lr-nofit { background: var(--bg); border-color: transparent; color: var(--text); }
.btn.lr-go { background: var(--btn-danger); color: var(--on-danger); border-color: transparent; }
.btn.lr-go:hover:not(:disabled) { background: color-mix(in srgb, var(--btn-danger) 86%, var(--text)); }

/* Перенос: легенда окон и сетка «день | часы». */
.lr-legend { display: flex; gap: 20px; flex-wrap: wrap; margin: 0 0 14px; font-size: .84em;
  color: var(--text-dim); }
.lr-legend-i { display: inline-flex; align-items: center; gap: 8px; }
.lr-sw { width: 26px; height: 12px; border-radius: 999px; background: var(--win-student); }
/* Список окон прокручивается ВНУТРИ себя: на горизонте в две недели их бывает под
   сотню, и без своей прокрутки диалог вырастал бы выше экрана вместе с кнопками. */
.lr-slots { max-height: 46vh; overflow-y: auto; display: flex; flex-direction: column;
  gap: 12px; padding-right: 4px; }
.lr-day { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 10px; align-items: start; }
.lr-day-h { font-size: .84em; color: var(--text-dim); padding-top: 5px; white-space: nowrap; }
/* auto-fill даёт таблеткам одну ширину — часы встают ровными столбцами, как в макете. */
.lr-hours { display: grid; grid-template-columns: repeat(auto-fill, minmax(56px, 1fr)); gap: 6px; }
.lr-slot { font-family: var(--font); font-size: .82em; padding: 5px 0; cursor: pointer;
  border-radius: 999px; border: 1px solid transparent; background: var(--win-student);
  color: var(--text); font-variant-numeric: tabular-nums; transition: filter .15s; }
/* ⚠️ Градиент — от ВНЕШНЕГО края (border-box) и без повтора: по умолчанию фон
   начинается внутри прозрачной рамки и повторяется под ней, и по краям таблетки
   торчали полоски чужого цвета. */
.lr-slot.both, .lr-sw.both { background: var(--win-both) border-box no-repeat; }
.lr-slot:hover { filter: brightness(1.15); }
.lr-slot.picked { background: var(--btn-danger); color: var(--on-danger); }
.lr-slot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Отмена: вопрос об инициаторе и итог. */
.lr-who-q { text-align: center; font-size: .95em; color: var(--text-dim); margin: 4px 0 10px; }
.lr-dlg .cw-opt { padding: 12px 16px; border-radius: 14px; border-color: transparent; background: var(--bg); }
.lr-dlg .cw-opt.active { background: var(--presence-on); color: var(--on-presence); }
.lr-dlg .cw-opt.active span { color: var(--on-presence); opacity: .8; }
.lr-sum { margin-top: 20px; text-align: center; font-size: .92em; line-height: 1.55; }
.lr-sum-c { font-weight: 500; }
.lr-sum-ok { color: var(--grade-good); }
.lr-sum-warn { color: var(--warn); }
.lr-sum-sub { font-size: .8em; color: var(--muted); }
@media (max-width: 520px) {
  .lr-day { grid-template-columns: 1fr; gap: 6px; }
  .lr-day-h { padding-top: 0; }
}

/* Просьбы о переносе — над сеткой, рядом с заявками на пробный урок. */
.lr-changes:empty { display: none; }
.lr-changes { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.lr-change { display: flex; align-items: center; gap: 12px; padding: 12px 14px;
  border-radius: var(--radius); background: var(--surface);
  border-left: 3px solid var(--warn); }
.lr-change-main { flex: 1 1 auto; min-width: 0; }
.lr-change-t { font-size: .86em; font-weight: 500; }
.lr-change-s { font-size: .78em; color: var(--muted); margin-top: 3px; }
.lr-change-r { font-size: .76em; color: var(--muted); margin-top: 3px; font-style: italic; }
.lr-change-acts { display: flex; gap: 8px; flex: 0 0 auto; }

.room-wait-noshow:empty { display: none; }

/* Просьба о переносе — окно на весь экран при входе ученика (lesson-rules.js, checkMoveGate).
   Закрывается ответом или крестиком. Поверх модалок (100) и промо (120), под тостами. */
.move-gate { position: fixed; inset: 0; z-index: 140; display: flex; align-items: center;
  justify-content: center; padding: 24px 16px; overflow-y: auto; background: var(--bg); }
body.move-gate-open { overflow: hidden; }
.move-gate-x { position: fixed; top: max(14px, env(safe-area-inset-top)); right: 14px;
  width: 40px; height: 40px; border-radius: 50%; border: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: var(--muted); background: var(--surface-2); }
.move-gate-x:hover { color: var(--text); background: var(--border-2); }
.move-gate-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.move-gate-x .ico svg { width: 18px; height: 18px; }
.move-gate-box { width: 100%; max-width: 520px; display: flex; flex-direction: column;
  align-items: center; text-align: center; gap: 10px; margin: auto; }
.move-gate-ico { width: 56px; height: 56px; border-radius: 50%; display: flex;
  align-items: center; justify-content: center; color: var(--warn);
  background: color-mix(in srgb, var(--warn) 14%, transparent); margin-bottom: 6px; }
.move-gate-ico .ico svg { width: 26px; height: 26px; }
.move-gate-eyebrow { font-size: .72rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--muted); }
.move-gate-title { font-size: clamp(1.4rem, 4.4vw, 2rem); font-weight: 700; line-height: 1.15;
  margin: 0; }
.move-gate-times { display: flex; align-items: center; gap: 18px; margin-top: 14px;
  padding: 16px 22px; border-radius: var(--radius); background: var(--surface);
  border: 1px solid var(--border); }
.move-gate-time { display: flex; flex-direction: column; gap: 4px; }
.move-gate-lbl { font-size: .72rem; color: var(--muted); text-transform: uppercase;
  letter-spacing: .12em; }
.move-gate-old { font-size: 1.05rem; color: var(--text-dim); text-decoration: line-through; }
.move-gate-new { font-size: 1.25rem; font-weight: 600; color: var(--text); }
.move-gate-arrow { font-size: 1.3rem; color: var(--muted); }
.move-gate-sub { font-size: .82rem; color: var(--muted); }
.move-gate-reason { font-size: .86rem; color: var(--text-dim); font-style: italic;
  max-width: 440px; overflow-wrap: anywhere; }
.move-gate-acts { display: flex; gap: 10px; width: 100%; max-width: 380px; margin-top: 18px; }
.move-gate-acts .btn { flex: 1 1 0; justify-content: center; min-height: 46px; }
.move-gate-note { font-size: .78rem; color: var(--muted); margin: 6px 0 0; max-width: 380px; }
@media (max-width: 760px) {
  .move-gate-times { gap: 12px; padding: 14px 16px; }
  .move-gate-acts { flex-direction: column; }
}

@media (max-width: 760px) {
  /* В телефоне действия уходят под текст: две кнопки рядом с длинным именем
     преподавателя не помещаются и обрезали бы его на полуслове. */
  .lr-change { flex-wrap: wrap; }
  .lr-change-acts { width: 100%; }
  .lr-change-acts .btn { flex: 1 1 0; }
  .lr-slots { max-height: 40vh; }
}

/* Очередь спорных неявок в настройках школы. */
.lr-limit-fields { padding-left: 14px; border-left: 2px solid var(--border-2); margin: 4px 0 10px; }
.lr-claims:empty { display: none; }
.lr-claim { background: var(--surface-2); border-radius: var(--radius-sm);
  padding: 12px 14px; margin-bottom: 10px; }
.lr-claim-h { font-size: .84em; font-weight: 500; }
.lr-claim-s { font-size: .78em; color: var(--muted); margin-top: 4px; line-height: 1.45; }
.lr-claim-ev { font-size: .78em; margin-top: 6px; color: var(--warn); }
.lr-claim-c { font-size: .78em; margin-top: 4px; font-style: italic; color: var(--text-dim); }
.lr-claim-acts { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }

/* ══ Настройки → Автоматизация (modules/automations) ════════════════════════
   Список сценариев в настройках и сценарий во весь экран: холст + панель шага.
   ⚠️ Цвета только токенами: вид узла красится через --k (сигнальные токены, свои в
   обеих темах), поэтому отдельных оверрайдов светлой темы у раздела нет.
   ⚠️ Размеры внутри холста — в px, а не em: холст масштабируется transform'ом, и
   размер шрифта должен меняться вместе с узлом, а не с настройкой страницы. */
.k-trigger { --k: var(--accent-text); }
.k-timer { --k: var(--lesson-demo); }
.k-condition { --k: var(--warn); }
.k-wait { --k: var(--lesson-nobal-fg); }
.k-action { --k: var(--text-dim); }
.k-message { --k: var(--lesson-upcoming-fg); }
.k-crm { --k: var(--grade-good); }
.k-end { --k: var(--muted); }

/* Список сценариев */
.aut-intro { color: var(--text-dim); font-size: .88em; line-height: 1.5; margin: 0 0 12px; max-width: 760px; }
.aut-warn { font-size: .8em; color: var(--warn); line-height: 1.45; margin: 0 0 16px; max-width: 760px; }
.aut-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); gap: 14px; }
.aut-card { display: flex; flex-direction: column; gap: 8px; text-align: left; padding: 18px 20px;
  border-radius: var(--radius); border: 1px solid transparent; background: var(--surface); color: var(--text);
  font-family: var(--font); font-size: 1em; cursor: pointer; transition: border-color .15s, background .15s; }
.aut-card:hover { border-color: var(--accent-border); background: var(--surface-2); }
.aut-card-h { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.aut-card-t { font-size: 1em; color: var(--text); line-height: 1.3; }
.aut-card-d { font-size: .82em; color: var(--text-dim); line-height: 1.45; }
.aut-card-m { display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: .76em; color: var(--muted);
  margin-top: auto; padding-top: 10px; border-top: 1px solid var(--border); }
.aut-stat b { color: var(--accent-text); font-weight: 600; }
/* Сводная карта «весь путь»: карточка во всю ширину сетки и этапы воронки */
.aut-card.wide { grid-column: 1 / -1; border-color: var(--accent-border); }
.aut-funnel { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 4px; }
.aut-funnel-s { display: inline-flex; align-items: baseline; gap: 6px; padding: 5px 11px; border-radius: 99px;
  background: var(--surface-2); font-size: .8em; color: var(--text-dim); white-space: nowrap; }
.aut-funnel-s b { color: var(--accent-text); font-weight: 600; }
.aut-funnel-s b.dim { color: var(--muted); font-weight: 400; }
.aut-funnel-a { display: inline-flex; color: var(--muted); }
.aut-funnel-a svg { width: 14px; height: 14px; }
.aut-card-tag { flex: none; margin-left: auto; font-size: .66rem; letter-spacing: .5px; text-transform: uppercase;
  color: var(--accent-text); padding: 3px 9px; border-radius: 99px; background: var(--surface-2); white-space: nowrap; }
.aut-card-tag + .aut-status, .aut-card-tag + .aut-card-draft { margin-left: 0; }
/* Воронки продаж: полоса над карточками, окно воронки, панель на карте */
.aut-sales-bar { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 14px; }
.aut-sales-bar .aut-intro { margin: 0; }
.aut-sales-bar .btn { flex: none; display: inline-flex; align-items: center; gap: 6px; }
.aut-sales-bar[hidden] { display: none; }
.aut-fn-form textarea.input { resize: vertical; min-height: 84px; font-family: var(--font); }
.aut-fn-check { display: flex; align-items: flex-start; gap: 10px; font-size: .86em; color: var(--text-dim);
  line-height: 1.4; margin-bottom: 10px; cursor: pointer; }
.aut-fn-check input { margin-top: 2px; flex: none; accent-color: var(--accent); }
.aut-fn-link { display: flex; align-items: center; gap: 8px; min-width: 0; }
.aut-fn-link code { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: .8em; padding: 7px 10px; border-radius: 8px; background: var(--surface-2); color: var(--accent-text); }
.aut-fn-link .btn, .aut-fn-actions .btn, .aut-fn-add + .ai-note + .btn { display: inline-flex; align-items: center; gap: 6px; flex: none; }
.aut-fn-actions { display: flex; flex-wrap: wrap; gap: 8px; padding-top: 4px; }
.aut-fn-remove { color: var(--danger); }
.aut-fn-code { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border); }
.aut-fn-code.off .aut-fn-code-main { opacity: .5; }
.aut-fn-code-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; font-size: .84em; }
.aut-fn-code-main b { color: var(--text); font-weight: 600; overflow-wrap: anywhere; }
.aut-fn-code-main span { color: var(--muted); font-size: .9em; }
.aut-fn-del { flex: none; padding: 4px 6px; color: var(--muted); }
.aut-fn-del:hover { color: var(--danger); }
.aut-fn-del svg { width: 15px; height: 15px; }
.aut-fn-settings { display: inline-flex; align-items: center; gap: 6px; flex: none; }
.aut-fn-switch { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  font-size: .86em; color: var(--text); cursor: pointer; }
.aut-fn-node { padding: 12px 14px; border-radius: 12px; background: var(--surface-2); }
.aut-fn-node .field { margin-bottom: 10px; }
.aut-fn-node .btn { align-self: flex-start; }
.ai-editor { display: inline-flex; align-items: center; gap: 6px; align-self: flex-start; }
.aut-survey-edit .card { background: transparent; padding-left: 0; padding-right: 0; }
.aut-fn-add { display: grid; grid-template-columns: 1fr 1fr 76px; gap: 6px; padding-top: 6px; }
.aut-fn-add .input { min-width: 0; width: auto; font-size: 16px; }
@media (max-width: 560px) {
  .aut-sales-bar { flex-direction: column; }
  .aut-fn-add { grid-template-columns: 1fr 1fr; }
  .aut-fn-add .ae-num { grid-column: 1 / -1; }
}
.aut-status { flex: none; font-size: .66rem; letter-spacing: .5px; text-transform: uppercase; font-weight: 600;
  padding: 3px 9px; border-radius: 99px; border: 1px solid var(--border-2); color: var(--muted); white-space: nowrap; }
.aut-status.on { color: var(--grade-good); border-color: color-mix(in srgb, var(--grade-good) 40%, transparent); }

/* Сценарий во весь экран */
body.aut-view .app { max-width: none; padding-top: 18px; padding-bottom: 16px; }
.aut-editor { display: flex; flex-direction: column; height: calc(100vh - 95px); height: calc(100dvh - 95px); min-height: 480px; }
.aut-head { display: flex; align-items: center; gap: 14px; padding-bottom: 14px; }
.aut-back { flex: none; padding-left: 8px; }
.aut-back > span { display: inline-flex; }
.aut-back svg { width: 16px; height: 16px; }
.aut-head-main { flex: 1 1 auto; min-width: 0; }
.aut-crumb { font-size: .7em; color: var(--muted); letter-spacing: .6px; text-transform: uppercase; }
.aut-head-t { display: flex; align-items: center; gap: 10px; font-size: 1.2em; color: var(--text); margin-top: 2px; }
.aut-head-s { font-size: .82em; color: var(--text-dim); margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.aut-zoom { display: flex; align-items: center; gap: 2px; background: var(--surface); border-radius: var(--radius-sm); padding: 3px; flex: none; }
.aut-zoom-b { padding: 7px 9px; }
.aut-zoom-v { min-width: 46px; text-align: center; font-size: .78em; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.aut-body { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: 14px; }

.aut-canvas { position: relative; overflow: hidden; border-radius: var(--radius); background-color: var(--surface);
  background-image: radial-gradient(color-mix(in srgb, var(--text) 10%, transparent) 1px, transparent 1.3px);
  background-size: 22px 22px; cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; outline: none; }
.aut-canvas.panning { cursor: grabbing; }
.aut-world { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
.aut-edges, .aut-labels, .aut-nodes, .aut-bands { position: absolute; left: 0; top: 0; }
.aut-band { position: absolute; display: flex; align-items: flex-start; gap: 10px; padding: 12px 16px;
  border: 1px dashed var(--border-2); border-radius: 18px;
  background: color-mix(in srgb, var(--surface) 70%, transparent); }
.aut-band-n { flex: none; display: inline-grid; place-items: center; width: 22px; height: 22px; border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent-text); font-size: .74em; font-weight: 600; }
.aut-band-t { color: var(--text); font-size: .9em; line-height: 22px; white-space: nowrap; }
.aut-band-link { display: inline-flex; align-items: center; gap: 2px; margin-left: 4px; padding: 0 4px;
  border: 0; background: none; color: var(--muted); font: inherit; font-size: .78em; line-height: 22px; cursor: pointer; }
.aut-band-link:hover { color: var(--accent-text); }
.aut-band-link span { display: inline-flex; }
.aut-band-link svg { width: 14px; height: 14px; }
.ai-row-link { width: 100%; border: 0; border-bottom: 1px solid var(--border); background: none; font: inherit;
  font-size: .84em; text-align: left; cursor: pointer; }
.ai-row-link:hover .ai-row-l { color: var(--accent-text); }
.aut-edges svg { display: block; overflow: visible; }
.aut-edge { fill: none; stroke: color-mix(in srgb, var(--text-dim) 45%, transparent); stroke-width: 1.6; }
.aut-edge.hot { stroke: var(--accent-text); stroke-width: 2.2; }
.aut-arrow { fill: color-mix(in srgb, var(--text-dim) 60%, transparent); }
.aut-arrow.hot { fill: var(--accent-text); }
.aut-labels { pointer-events: none; }
.aut-elabel { position: absolute; transform: translate(-50%, -50%); padding: 2px 8px; border-radius: 99px;
  background: var(--bg); color: var(--text-dim); font-size: 11px; line-height: 1.4; white-space: nowrap; }
.aut-elabel.hot { color: var(--accent-text); }

.aut-node { position: absolute; display: flex; align-items: flex-start; gap: 10px; padding: 12px 12px 10px 12px;
  box-sizing: border-box; overflow: hidden; border-radius: 12px; background: var(--surface-2);
  border: 1px solid transparent; cursor: pointer; transition: border-color .15s, box-shadow .15s; }
.aut-node:hover { border-color: var(--border-2); }
.aut-node.l-system { background: color-mix(in srgb, var(--surface-2) 55%, var(--surface)); }
.aut-node.sel { border-color: var(--accent-text); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
.aut-node:focus-visible { outline: 2px solid var(--accent-border); outline-offset: 2px; }
.aut-node.off { opacity: .55; }
/* Свой шаг воронки (steps.js): пунктирная рамка акцентом — видно, что его добавила школа */
.aut-node.l-custom { border-style: dashed; border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.aut-node.l-custom.sel { border-style: solid; border-color: var(--accent-text); }
.l-custom > .an-lock { color: var(--accent-text); }
/* Внутри узла: у .aut-node overflow: hidden, выступающая за край кнопка обрезалась бы */
.aut-node-add { position: absolute; right: 8px; bottom: 8px; width: 24px; height: 24px;
  display: inline-grid; place-items: center; padding: 0; border-radius: 50%; border: 1px solid var(--accent-border);
  background: var(--surface); color: var(--accent-text); cursor: pointer; opacity: 0; z-index: 3;
  transition: opacity .15s, background .15s; }
.aut-node:hover > .aut-node-add, .aut-node.sel > .aut-node-add, .aut-node-add:focus-visible { opacity: 1; }
.aut-node-add:hover { background: var(--surface-2); }
.aut-node-add svg { width: 14px; height: 14px; }
@media (hover: none) { .aut-node-add { opacity: 1; } }
.aut-st-add { display: flex; flex-direction: column; gap: 8px; }
.aut-st-branch { display: flex; gap: 8px; margin-bottom: 4px; }
.aut-st-kind { display: flex; align-items: flex-start; gap: 12px; width: 100%; padding: 12px 14px; text-align: left;
  border-radius: 12px; border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
  font: inherit; cursor: pointer; }
.aut-st-kind:hover { border-color: var(--accent-border); }
.aut-st-kind-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; font-size: .88em; }
.aut-st-kind-main span { color: var(--text-dim); font-size: .92em; line-height: 1.4; }
.aut-st-addbtn { display: inline-flex; align-items: center; gap: 6px; align-self: flex-start; }
.an-ico { flex: none; width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center;
  color: var(--k); background: color-mix(in srgb, var(--k) 15%, transparent); }
.an-ico svg { width: 16px; height: 16px; }
.an-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.an-kind { font-size: 10px; letter-spacing: .6px; text-transform: uppercase; color: var(--muted); }
.an-title { font-size: 13px; color: var(--text); line-height: 1.25; display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden; }
.an-sub { font-size: 11.5px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; }
.an-lock { flex: none; display: inline-flex; color: var(--muted); }
.an-lock svg { width: 13px; height: 13px; }
.l-optional > .an-lock { color: var(--accent-text); }

.aut-legend { position: absolute; left: 12px; bottom: 12px; display: flex; gap: 6px 14px; flex-wrap: wrap;
  padding: 6px 10px; border-radius: var(--radius-sm); background: color-mix(in srgb, var(--bg) 85%, transparent);
  font-size: .74em; color: var(--text-dim); cursor: default; }
.aut-legend-i { display: inline-flex; align-items: center; gap: 5px; }
.aut-legend-i > span { display: inline-flex; color: var(--muted); }
.aut-legend-i.l-optional > span { color: var(--accent-text); }
.aut-legend-i svg { width: 12px; height: 12px; }

/* Панель шага (она же нижний лист на телефоне) */
.aut-side { min-height: 0; overflow-y: auto; background: var(--surface); border-radius: var(--radius); padding: 18px; }
.ai-panel { display: flex; flex-direction: column; gap: 14px; }
.ai-head { display: flex; gap: 12px; align-items: flex-start; }
.ai-kind { flex: none; width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center;
  color: var(--k); background: color-mix(in srgb, var(--k) 15%, transparent); }
.ai-kind svg { width: 19px; height: 19px; }
.ai-head-main { flex: 1 1 auto; min-width: 0; }
.ai-kind-l { font-size: .7em; letter-spacing: .7px; text-transform: uppercase; color: var(--muted); }
.ai-title { font-size: 1.05em; color: var(--text); margin-top: 2px; line-height: 1.3; }
.ai-sub { font-size: .8em; color: var(--text-dim); margin-top: 2px; }
.ai-close { flex: none; padding: 6px; }
.ai-close svg { width: 16px; height: 16px; }
.ai-lock, .ai-legend { display: flex; gap: 10px; }
.ai-lock { padding: 10px 12px; border-radius: var(--radius-sm); background: var(--surface-2); }
.ai-legend { padding: 4px 0; }
.ai-lock-ico { flex: none; display: inline-flex; color: var(--muted); margin-top: 1px; }
.ai-lock-ico svg { width: 15px; height: 15px; }
.l-optional > .ai-lock-ico { color: var(--accent-text); }
.ai-lock-t { font-size: .84em; color: var(--text); }
.ai-lock-s { font-size: .76em; color: var(--muted); margin-top: 2px; line-height: 1.4; }
.ai-about { font-size: .84em; color: var(--text-dim); line-height: 1.5; margin: 0; }
.ai-sec { display: flex; flex-direction: column; gap: 6px; }
.ai-sec-t { font-size: .7em; letter-spacing: 1px; text-transform: uppercase; color: var(--muted); }
.ai-row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; font-size: .84em;
  padding: 7px 0; border-bottom: 1px solid var(--border); }
.ai-row:last-child { border-bottom: none; }
.ai-row-l { color: var(--text-dim); min-width: 0; }
.ai-row-v { color: var(--text); text-align: right; flex: 0 1 auto; max-width: 55%; }
.ai-row-v.on { color: var(--grade-good); }
.ai-row-v.off, .ai-row-v.dim { color: var(--muted); }
.ai-channel { font-size: .86em; color: var(--text); }
.ai-note { font-size: .78em; color: var(--muted); line-height: 1.45; margin: 0; }
.ai-text { background: var(--surface-2); border-radius: var(--radius-sm); padding: 10px 12px; }
.ai-text-h { display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
.ai-text-to { font-size: .82em; color: var(--text); }
.ai-text-state { font-size: .7em; color: var(--muted); white-space: nowrap; }
.ai-text-state.own { color: var(--accent-text); }
.ai-text-sub { font-size: .72em; color: var(--muted); margin-top: 2px; }
.ai-text-body { font-size: .82em; color: var(--text-dim); white-space: pre-wrap; line-height: 1.45; margin-top: 8px; overflow-wrap: anywhere; }
.ai-var { color: var(--accent-text); background: color-mix(in srgb, var(--accent) 10%, transparent); border-radius: 4px; padding: 0 3px; }
.ai-track { padding: 8px 0; border-bottom: 1px solid var(--border); }
.ai-track:last-child { border-bottom: none; }
.ai-track.off { opacity: .6; }
.ai-track-t { font-size: .84em; color: var(--text); }
.ai-track-s { font-size: .76em; color: var(--muted); margin-top: 2px; line-height: 1.4; }
.ai-link { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; padding: 9px 12px;
  border-radius: var(--radius-sm); border: 1px solid transparent; background: var(--surface-2); color: var(--text);
  font-family: var(--font); font-size: .82em; text-align: left; cursor: pointer; }
.ai-link:hover { background: var(--border-2); }
.ai-link-ico { display: inline-flex; color: var(--muted); }
.ai-link-ico svg { width: 14px; height: 14px; }
.ai-next { align-self: flex-start; }
.ai-next > span { display: inline-flex; }
.ai-next svg { width: 14px; height: 14px; }
.modal .ai-lock, .modal .ai-text, .modal .ai-link { background: var(--surface-2); }

/* На телефоне холста нет: сценарий идёт списком шагов, панель шага — нижним листом. */
.aut-list { display: none; }
@media (max-width: 760px) {
  body.aut-view .app { padding-top: 10px; padding-bottom: 24px; }
  .aut-editor { height: auto; min-height: 0; }
  .aut-body, .aut-zoom { display: none; }
  .aut-head { flex-wrap: wrap; align-items: flex-start; gap: 8px; }
  .aut-head-main { flex-basis: 100%; }
  .aut-head-s { white-space: normal; }
  .aut-list { display: flex; flex-direction: column; gap: 8px; }
  .aut-li { display: flex; gap: 12px; align-items: flex-start; width: 100%; text-align: left; padding: 12px 14px;
    border-radius: var(--radius-sm); border: 1px solid transparent; background: var(--surface); color: var(--text);
    font-family: var(--font); font-size: 1em; cursor: pointer; }
  .aut-li-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .aut-li .an-title { font-size: 14px; display: block; -webkit-line-clamp: unset; }
  .aut-li .an-sub { white-space: normal; }
  .aut-li-next { display: flex; flex-direction: column; font-size: 11.5px; color: var(--muted); margin-top: 4px; line-height: 1.45; }
}

/* ── Автоматизация: черновик, правка полей, публикация и история ──
   ⚠️ Метка черновика — ТОЧКА цвета --warn, а не заливка узла: заливку уже занимает вид
   шага и уровень «системный», третий смысл цветом фона не прочитался бы. */
.aut-draft-dot, .an-draft { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--warn);
  flex: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn) 22%, transparent); }
.an-draft { position: absolute; top: 8px; right: 26px; }
.an-draft.static { position: static; }
.aut-list-draft { margin: 0 0 14px; }
.aut-draft { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.aut-draft-t { display: inline-flex; align-items: center; gap: 8px; font-size: .82em; color: var(--text); margin-right: 4px; }
.aut-draft-t.dim { color: var(--muted); }
.aut-bi { display: inline-flex; }
.aut-bi svg { width: 14px; height: 14px; }
.aut-head-draft { flex: none; }
.aut-card-draft { display: inline-flex; align-items: center; gap: 6px; flex: none; margin-left: auto;
  font-size: .72em; color: var(--warn); white-space: nowrap; }
.aut-card-draft + .aut-status { margin-left: 0; }
.aut-node.drafted { border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
.aut-node.drafted.sel { border-color: var(--accent-text); }
.aut-node.dragging { cursor: grabbing; box-shadow: var(--shadow); z-index: 2; transition: none; }
.aut-li { position: relative; }
.aut-li .an-draft { position: static; margin-top: 6px; }

/* Поле правки в панели шага */
.ae-field { display: flex; flex-direction: column; gap: 6px; padding: 10px 0; border-bottom: 1px solid var(--border); }
.ae-field:last-child { border-bottom: none; }
.ae-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.ae-label { flex: 1 1 auto; min-width: 0; font-size: .84em; color: var(--text); display: flex; flex-direction: column; gap: 1px; }
.ae-sub { font-size: .86em; color: var(--muted); }
.ae-state { flex: none; font-size: .7em; color: var(--warn); white-space: nowrap; }
.ae-undo { flex: none; border: none; background: none; padding: 2px 4px; font-family: var(--font); font-size: .74em;
  color: var(--accent-text); cursor: pointer; }
.ae-undo:hover { text-decoration: underline; }
.ae-field.drafted .ae-label { color: var(--text); }
.ae-row { display: flex; align-items: center; gap: 8px; }
.ae-num { width: 110px; }
.ae-unit { font-size: .8em; color: var(--muted); }
.ae-crm { display: flex; flex-direction: column; gap: 6px; }
.ae-text { width: 100%; min-height: 120px; font-size: .86em; line-height: 1.45; resize: vertical; }
/* Текст по умолчанию в пустом поле — заметно бледнее введённого: иначе его принимают за
   уже написанный текст школы и не понимают, почему «Вернуть по умолчанию» не видно. */
.ae-text::placeholder { color: var(--muted); opacity: .75; }
.ae-vars { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.ae-var { border: 1px solid transparent; border-radius: 6px; padding: 2px 6px; font-family: var(--font); font-size: .74em;
  color: var(--accent-text); background: color-mix(in srgb, var(--accent) 10%, transparent); cursor: pointer; }
.ae-var:hover { border-color: var(--accent-border); }
.ae-tools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
.ae-tstate { font-size: .72em; color: var(--muted); margin-right: auto; }
.ae-tstate.own { color: var(--accent-text); }
.ae-link { border: none; background: none; padding: 0; font-family: var(--font); font-size: .74em;
  color: var(--text-dim); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
.ae-link:hover { color: var(--text); }
.ae-msg:empty { display: none; }
.ae-msg { display: flex; flex-direction: column; gap: 3px; }
.ae-err, .ae-warn { font-size: .76em; line-height: 1.4; margin: 0; }
.ae-err { color: var(--danger); }
.ae-warn { color: var(--warn); }
.ai-sec .ae-field:first-of-type { padding-top: 2px; }

/* Окна публикации и истории */
.aut-dlg-note { font-size: .84em; color: var(--text-dim); line-height: 1.5; margin: 0 0 14px; }
.aut-ch-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.aut-ch { background: var(--surface-2); border-radius: var(--radius-sm); padding: 10px 12px;
  display: flex; flex-direction: column; gap: 4px; border: 1px solid transparent; }
.aut-ch.bad { border-color: color-mix(in srgb, var(--danger) 45%, transparent); }
.aut-ch-l { font-size: .8em; color: var(--text-dim); }
.aut-ch-v { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; font-size: .86em; }
.aut-ch-before { color: var(--muted); text-decoration: line-through; text-decoration-color: color-mix(in srgb, var(--muted) 60%, transparent); overflow-wrap: anywhere; }
.aut-ch-arrow { color: var(--muted); }
.aut-ch-after { color: var(--text); overflow-wrap: anywhere; }
.aut-ch-meta { font-size: .72em; color: var(--muted); }
.aut-ch-meta:empty { display: none; }
.aut-force { display: flex; align-items: center; gap: 8px; font-size: .84em; color: var(--warn); margin-bottom: 8px; cursor: pointer; }
.aut-ver { padding: 12px 0; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 6px; }
.aut-ver-h { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.aut-ver-t { font-size: .88em; color: var(--text); }
.aut-ver-m { font-size: .76em; color: var(--muted); margin-right: auto; }
.aut-ver-c { display: flex; flex-direction: column; gap: 2px; padding-left: 10px; border-left: 2px solid var(--border-2); }
.aut-li-master { background: var(--surface); border-radius: var(--radius-sm); padding: 4px 14px; }

@media (max-width: 760px) {
  .aut-head-draft { flex-basis: 100%; }
  .aut-draft .btn { flex: 1 1 auto; }
}

/* ── Автоматизация: журнал выполнений ──
   Цвет состояния — сигнальные токены (--grade-good / --lesson-upcoming-fg / --danger),
   общие для обеих тем; форма значка (галочка, часы, крестик) дублирует цвет смыслом. */
.aut-side { display: flex; flex-direction: column; padding: 0; }
.aut-side-tabs { display: flex; gap: 4px; padding: 8px 10px 0; border-bottom: 1px solid var(--border); flex: none; }
.aut-side-tab { display: inline-flex; align-items: center; gap: 6px; padding: 9px 12px; border: none;
  border-bottom: 2px solid transparent; margin-bottom: -1px; background: none; color: var(--text-dim);
  font-family: var(--font); font-size: .84em; cursor: pointer; }
.aut-side-tab:hover { color: var(--text); }
.aut-side-tab.active { color: var(--selected); border-bottom-color: var(--selected); }
.aut-side-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 16px 18px 18px; }
.aut-runs-btn { display: none; }
.s-done { --s: var(--grade-good); }
.s-active { --s: var(--lesson-upcoming-fg); }
.s-stopped { --s: var(--muted); }
.s-failed { --s: var(--danger); }
.aut-run-dot { flex: none; width: 9px; height: 9px; border-radius: 50%; background: var(--s); margin-top: 5px; }
.aut-run-filters { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
.aut-run-filter { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: 99px;
  border: 1px solid transparent; background: var(--surface-2); color: var(--text-dim); font-family: var(--font);
  font-size: .78em; cursor: pointer; }
.aut-run-filter.active { color: var(--selected); border-color: var(--accent-border); }
.aut-run-count { color: var(--muted); font-variant-numeric: tabular-nums; }
.aut-run-list { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.aut-run { display: flex; align-items: flex-start; gap: 10px; width: 100%; text-align: left; padding: 10px 12px;
  border-radius: var(--radius-sm); border: 1px solid transparent; background: var(--surface-2); color: var(--text);
  font-family: var(--font); font-size: 1em; cursor: pointer; }
.aut-run:hover { border-color: var(--border-2); }
.aut-run-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.aut-run-t { font-size: .84em; color: var(--text); overflow-wrap: anywhere; }
.aut-run-s { font-size: .76em; color: var(--text-dim); }
.aut-run-p { font-size: .72em; color: var(--muted); overflow-wrap: anywhere; }
.aut-run-bad { flex: none; min-width: 20px; padding: 1px 6px; border-radius: 99px; text-align: center;
  font-size: .72em; color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, transparent); }
.aut-run-empty { font-size: .84em; color: var(--muted); margin: 14px 0 0; }
.aut-run-back { align-self: flex-start; padding-left: 6px; }
.aut-run-back > span { display: inline-flex; }
.aut-run-back svg { width: 14px; height: 14px; }
.aut-run-head { display: flex; gap: 10px; align-items: flex-start; }
.aut-steps { display: flex; flex-direction: column; margin-top: 6px; }
.st-ok { --st: var(--grade-good); }
.st-wait { --st: var(--lesson-upcoming-fg); }
.st-fail { --st: var(--danger); }
.st-skip { --st: var(--muted); }
.aut-step { display: flex; gap: 10px; padding: 6px 0 10px; position: relative; }
.aut-step::before { content: ''; position: absolute; left: 10px; top: 28px; bottom: -2px; width: 1px; background: var(--border-2); }
.aut-step:last-child::before { display: none; }
.aut-step.focus .aut-step-t { color: var(--accent-text); }
.aut-step-ico { flex: none; width: 21px; height: 21px; border-radius: 50%; display: grid; place-items: center;
  color: var(--st); background: color-mix(in srgb, var(--st) 16%, transparent); }
.aut-step-ico svg { width: 12px; height: 12px; }
.aut-step-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.aut-step-t { border: none; background: none; padding: 0; text-align: left; font-family: var(--font);
  font-size: .84em; color: var(--text); cursor: pointer; }
.aut-step-t:hover { color: var(--accent-text); }
.aut-step-n { font-size: .76em; color: var(--text-dim); overflow-wrap: anywhere; }
.aut-step-at { font-size: .7em; color: var(--muted); }
.d-sent { --d: var(--grade-good); }
.d-pending, .d-retry { --d: var(--warn); }
.d-failed { --d: var(--danger); }
.aut-dlv { display: flex; gap: 8px; align-items: flex-start; padding: 6px 8px; margin-top: 3px;
  border-radius: 8px; background: var(--surface-2); }
.aut-dlv-ico { flex: none; display: inline-flex; color: var(--d); margin-top: 1px; }
.aut-dlv-ico svg { width: 13px; height: 13px; }
.aut-dlv-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.aut-dlv-t { font-size: .76em; color: var(--text); overflow-wrap: anywhere; }
.aut-dlv-s { font-size: .7em; color: var(--d); }
.aut-dlv-e { font-size: .7em; color: var(--muted); overflow-wrap: anywhere; }
.aut-card-bad { color: var(--danger); }

/* Прогон на холсте: пройденные шаги ярче, остальные тускнеют. Состояние — рамкой и
   значком-кружком в углу, а не заливкой: заливку уже занимает вид шага. */
.aut-node.run-dim { opacity: .35; }
.aut-node.run-ok, .aut-node.run-wait, .aut-node.run-fail, .aut-node.run-skip { border-color: var(--st); }
.aut-node.run-ok { --st: var(--grade-good); }
.aut-node.run-wait { --st: var(--lesson-upcoming-fg); box-shadow: 0 0 0 3px color-mix(in srgb, var(--lesson-upcoming-fg) 22%, transparent); }
.aut-node.run-fail { --st: var(--danger); box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 22%, transparent); }
.aut-node.run-skip { --st: var(--muted); }

@media (max-width: 760px) {
  .aut-runs-btn { display: inline-flex; }
  .aut-runs-sheet .aut-run-detail { gap: 10px; }
}

/* ══ Ежедневный отчёт администратору (modules/daily-report) ═════════════════════
   Плитки — те же, что у посещаемости (.att-tiles): у платформы один «приборный» язык для
   посчитанного. Тяжесть проблемы — полосой слева, как в журнале ИИ-инспектора (.ins-row):
   строк бывает много, и цветной текст в каждой превратил бы список в витраж.
   Цвета — только токены: процент «лучше/хуже» — сигнальные --grade-*, одни на обе темы. */
.dr-head { align-items: flex-start; }
.dr-head-main { min-width: 0; flex: 1 1 320px; }
.dr-head .page-sub { line-height: 1.5; overflow-wrap: anywhere; }
.dr-nav { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.dr-date { width: auto; min-width: 150px; }
.dr-ico { display: inline-flex; }
.dr-ico svg { width: 14px; height: 14px; }
.dr-nav .btn, .dr-prob-acts .btn, .dr-chip { gap: 6px; }
.dr-card { margin-bottom: 16px; }
/* Заголовок блока — первым в карточке: верхний отступ .section-title (32px) рассчитан на
   заголовок между блоками настроек, а над плитками отчёта он оставлял пустую полосу. */
.dr-card > .section-title:first-child { margin-top: 0; }
.dr-tiles { margin-bottom: 10px; }
.dr-cmp { margin-top: 3px; display: flex; flex-wrap: wrap; gap: 2px 6px; align-items: baseline;
  font-size: .72em; color: var(--muted); }
.dr-delta { font-weight: 600; }
.dr-delta.good { color: var(--grade-good); }
.dr-delta.bad { color: var(--grade-low); }
.dr-facts { display: flex; flex-wrap: wrap; gap: 6px; }
.dr-fact { font-size: .8em; color: var(--text-dim); padding: 4px 10px; border-radius: 999px;
  background: var(--surface-2); }
.dr-fact b { color: var(--text); font-weight: 600; }
.dr-list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 6px;
  line-height: 1.5; }
.dr-note { margin: 10px 0 0; font-size: .82em; color: var(--muted); line-height: 1.5; }
.dr-ok { margin: 2px 0 0; color: var(--grade-good); }
.dr-foot { display: flex; flex-wrap: wrap; gap: 8px; }

.dr-probs { display: flex; flex-direction: column; gap: 8px; }
.dr-prob { display: flex; flex-direction: column; gap: 5px; padding: 11px 13px; border-radius: 12px;
  background: var(--surface-2); border: 1px solid transparent; border-left: 3px solid var(--border-2); }
.dr-prob.lvl-alert { border-left-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 7%, var(--surface-2)); }
.dr-prob.lvl-warn { border-left-color: var(--warn); }
.dr-prob-head { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; }
.dr-lvl { font-size: .66em; letter-spacing: .06em; text-transform: uppercase; padding: 1px 6px;
  border-radius: 5px; color: var(--muted); background: color-mix(in srgb, var(--muted) 15%, transparent); }
.dr-prob.lvl-alert .dr-lvl { color: var(--danger); background: color-mix(in srgb, var(--danger) 16%, transparent); }
.dr-prob.lvl-warn .dr-lvl { color: var(--warn); background: color-mix(in srgb, var(--warn) 18%, transparent); }
.dr-when { font-size: .78em; color: var(--muted); }
.dr-count { font-size: .78em; font-weight: 600; color: var(--text-dim); }
.dr-prob-text { font-weight: 600; line-height: 1.4; overflow-wrap: anywhere; }
.dr-prob-note { font-size: .86em; color: var(--text-dim); line-height: 1.5; overflow-wrap: anywhere; }
.dr-prob-quote { font-size: .85em; font-style: italic; color: var(--text-dim); padding-left: 9px;
  border-left: 2px solid var(--border-2); overflow-wrap: anywhere; }
.dr-prob-acts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 3px; }
/* Строка проблемы уже на --surface-2 — у кнопок тот же фон, и без своей заливки они
   сливались бы с ней в надписи без формы. */
.dr-prob-acts .btn { background: var(--surface); }
.dr-prob-acts .btn:hover { background: color-mix(in srgb, var(--text) 8%, var(--surface)); }
.dr-src { font-size: .72em; color: var(--text-dim); }

/* Настройки отчёта: администраторы и то, куда до каждого дойдёт отчёт. */
.dr-time { width: 140px; }
.dr-admins { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
.dr-admin { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: 10px;
  background: var(--surface-2); cursor: pointer; }
.dr-admin input { flex: none; width: 18px; height: 18px; margin: 0; accent-color: var(--toggle-on); }
.dr-admin-main { flex: 1 1 auto; min-width: 0; }
.dr-admin-name { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; font-weight: 600;
  overflow-wrap: anywhere; }
.dr-me { font-size: .74em; font-weight: 400; color: var(--muted); }
.dr-admin-how { margin-top: 2px; font-size: .8em; color: var(--text-dim); }
.dr-admin-how.bad { color: var(--warn); }
.dr-admin-ch { flex: none; display: flex; gap: 6px; }
.dr-chip { display: inline-flex; align-items: center; padding: 3px 9px; border-radius: 999px;
  font-size: .74em; color: var(--muted); border: 1px solid var(--border-2); }
.dr-chip svg { width: 12px; height: 12px; }
.dr-chip.ok { color: var(--grade-good); border-color: color-mix(in srgb, var(--grade-good) 40%, transparent); }
.dr-warn { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-top: 12px;
  padding: 10px 12px; border-radius: 10px; font-size: .85em; line-height: 1.5; color: var(--text-dim);
  background: color-mix(in srgb, var(--warn) 10%, var(--surface-2)); }
.dr-warn > span { flex: 1 1 260px; }
.dr-btns { display: flex; flex-wrap: wrap; gap: 8px; }
.dr-hist { display: flex; flex-direction: column; gap: 6px; }
.dr-hist-row { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: 10px;
  background: var(--surface-2); color: var(--text); text-decoration: none; }
.dr-hist-row:hover { background: color-mix(in srgb, var(--text) 6%, var(--surface-2)); }
.dr-hist-main { flex: 1 1 auto; min-width: 0; }
.dr-hist-t { font-weight: 600; }
.dr-hist-s { margin-top: 2px; font-size: .78em; color: var(--muted); overflow-wrap: anywhere; }
.dr-hist-n { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 2px;
  font-size: .78em; color: var(--text-dim); white-space: nowrap; }
.dr-hist-n .bad { color: var(--warn); }

@media (max-width: 760px) {
  .dr-nav { width: 100%; }
  .dr-date { flex: 1 1 auto; min-width: 0; }
  .dr-admin { flex-wrap: wrap; }
  /* Имя делит строку с галочкой, значки каналов уходят под него: с основой «по
     содержимому» длинная подпись уезжала на свою строку, и галочка висела одна. */
  .dr-admin-main { flex: 1 1 0; }
  .dr-admin-ch { width: 100%; padding-left: 30px; }
}

/* ── API-ключи (Настройки → Система) ───────────────────────────── */
.apik-sub { overflow-wrap: anywhere; }
.apik-key { display: flex; gap: 8px; align-items: center; }
.apik-code, .apik-brief {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .82em;
  background: var(--bg); border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  color: var(--text); user-select: all;
}
.apik-code { flex: 1 1 auto; min-width: 0; padding: 10px 12px; overflow-wrap: anywhere; }
.apik-brief { margin: 0; padding: 12px 14px; white-space: pre-wrap; overflow-wrap: anywhere;
  line-height: 1.55; max-height: 220px; overflow: auto; }

/* ══ «Промо»: баннеры (modules/promo) ═══════════════════════════════════════════
   Сцена баннера (.pb-*) — одна на живое окно ученика, холст конструктора, предпросмотр и
   миниатюры (render.js). Цвета ВНУТРИ баннера — содержимое, которое собрал администратор
   (как цвета доски), а не оформление платформы: в палитру школы и в светлую тему их не
   переводим. Всё вокруг баннера — через токены. */
.pb-fit { position: relative; overflow: hidden; flex: 0 0 auto; }
.pb-stage { position: absolute; left: 0; top: 0; overflow: hidden; transform-origin: 0 0;
  background-size: cover; background-position: center; background-repeat: no-repeat;
  font-family: var(--font); color: #fff; isolation: isolate; text-align: left; }
.pb-el { position: absolute; box-sizing: border-box; transform-origin: 50% 50%; }
.pb-text { white-space: pre-wrap; overflow-wrap: break-word; margin: 0; }
.pb-list { margin: 0; padding: 0 0 0 1.15em; list-style: disc; }
.pb-list li { padding-left: .1em; }
.pb-list li::marker { color: currentColor; font-size: .85em; }
.pb-button { display: flex; align-items: center; justify-content: center; text-align: center;
  text-decoration: none; border: 0; padding: 0 .6em; cursor: pointer; line-height: 1.15;
  white-space: nowrap; overflow: hidden; font: inherit; }
.pb-mode-live .pb-button { transition: filter .15s, transform .1s; }
.pb-mode-live .pb-button:hover { filter: brightness(1.08); }
.pb-mode-live .pb-button:active { transform: translateY(1px); }
.pb-btn-label { overflow: hidden; text-overflow: ellipsis; }
.pb-image { overflow: hidden; }
.pb-image img { display: block; width: 100%; height: 100%; user-select: none; -webkit-user-drag: none; }
.pb-shape svg { display: block; width: 100%; height: 100%; overflow: visible; }
.pb-img-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  border: 2px dashed rgba(255,255,255,.28); background: rgba(255,255,255,.04); color: rgba(255,255,255,.55);
  font-size: 14px; }
.pb-img-ico svg { width: 28px; height: 28px; }

/* ── Живое окно у ученика и преподавателя (show.js) ── */
.promo-overlay { position: fixed; inset: 0; z-index: 120; display: flex; align-items: center; justify-content: center;
  overflow: auto; background: rgba(0,0,0,.62); backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); }
.promo-overlay.is-preview { position: absolute; z-index: 2; }
.promo-pop { position: relative; margin: auto; flex: 0 0 auto; border-radius: 28px;
  box-shadow: 0 24px 70px rgba(0,0,0,.5); }
.promo-x { position: absolute; top: 12px; right: 12px; z-index: 3; width: 36px; height: 36px; padding: 0;
  display: grid; place-items: center; border: 0; border-radius: 50%; cursor: pointer;
  transition: background .15s, transform .1s; }
.promo-x svg { width: 18px; height: 18px; }
.promo-x:active { transform: scale(.94); }
.promo-x-light { background: rgba(255,255,255,.14); color: #fff; }
.promo-x-light:hover { background: rgba(255,255,255,.24); }
.promo-x-dark { background: rgba(0,0,0,.4); color: #fff; }
.promo-x-dark:hover { background: rgba(0,0,0,.55); }
@media (max-width: 600px) { .promo-x { top: 8px; right: 8px; } }
/* Карусель (show.js carouselNode): лента во всю ширину окна, под ней «‹ • • • ›».
   Белые стрелки и точки — на подложке .promo-overlay, а она тёмная в ОБЕИХ темах (как и
   крестик выше), поэтому светлой теме оверрайд не нужен.
   ⚠️ overflow-x: clip, а не hidden: hidden по второй оси превратился бы в auto и срезал бы
   тень карточки сверху и снизу; clip режет только бока. */
.promo-pop.is-carousel { width: 100%; margin: auto 0; border-radius: 0; box-shadow: none; }
.promo-view { position: relative; width: 100%; overflow: hidden; overflow-x: clip; overflow-y: visible;
  touch-action: pan-y; user-select: none; -webkit-user-select: none; }
.promo-view a, .promo-view img { -webkit-user-drag: none; }
.promo-track { display: flex; height: 100%; will-change: transform; }
.promo-slide { flex: 0 0 100%; min-width: 0; display: flex; align-items: center; justify-content: center;
  box-sizing: border-box; }
.promo-sframe { position: relative; flex: 0 0 auto; box-shadow: 0 18px 50px rgba(0,0,0,.45); }
.promo-nav { display: flex; align-items: center; justify-content: center; gap: 14px; height: 52px; padding-top: 12px;
  box-sizing: border-box; }
.promo-dots { display: flex; align-items: center; }
/* Точка 8px, а нажимается круг 20px: прозрачный padding вокруг закрашенной середины. */
.promo-dot { box-sizing: content-box; width: 8px; height: 8px; padding: 6px; border: 0; border-radius: 50%;
  background: rgba(255,255,255,.34); background-clip: content-box; cursor: pointer;
  transition: background-color .2s, transform .2s; }
.promo-dot:hover { background-color: rgba(255,255,255,.6); }
.promo-dot.on { background-color: #fff; transform: scale(1.2); }
.promo-arrow { width: 36px; height: 36px; padding: 0; display: grid; place-items: center; border: 0; border-radius: 50%;
  background: rgba(255,255,255,.14); color: #fff; cursor: pointer; transition: background .15s, opacity .15s; }
.promo-arrow svg { width: 20px; height: 20px; }
.promo-arrow:hover:not(:disabled) { background: rgba(255,255,255,.24); }
.promo-arrow:disabled { opacity: .3; cursor: default; }

/* ── Предпросмотр по устройствам ── */
.pp-shell { position: fixed; inset: 0; z-index: 150; display: flex; flex-direction: column;
  background: color-mix(in srgb, var(--bg) 94%, #000); }
.pp-top { display: flex; align-items: center; gap: 14px; padding: 12px 16px; border-bottom: 1px solid var(--border); }
.pp-title { font-weight: 600; flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pp-tabs { display: flex; gap: 4px; padding: 3px; background: var(--surface-2); border-radius: 12px; }
.pp-tab { display: inline-flex; align-items: center; gap: 6px; padding: 7px 12px; border: 0; border-radius: 9px;
  background: transparent; color: var(--text-dim); font: inherit; font-size: .85em; cursor: pointer; }
.pp-tab.active { background: var(--surface); color: var(--text); box-shadow: 0 1px 3px rgba(0,0,0,.25); }
.pp-tab-ico svg { width: 16px; height: 16px; display: block; }
.pp-close { flex: 1 1 0; display: flex; justify-content: flex-end; background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--text-dim); }
.pp-close svg { width: 34px; height: 34px; padding: 8px; border-radius: 50%; background: var(--surface-2); }
.pp-close:hover { color: var(--text); }
.pp-stage { flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: hidden; }
.pp-holder { position: relative; flex: 0 0 auto; }
.pp-screen { position: absolute; left: 0; top: 0; transform-origin: 0 0; overflow: hidden; border-radius: 18px;
  background: var(--bg); box-shadow: 0 0 0 10px #050505, 0 0 0 11px #2a2a2a, 0 30px 80px rgba(0,0,0,.45); }
.pp-screen.pp-desktop { border-radius: 10px; }
.pp-fake { position: absolute; inset: 0; padding: 18px; display: flex; flex-direction: column; gap: 14px; }
.pp-fake-bar { height: 44px; border-radius: 12px; background: var(--surface); }
.pp-fake-row { display: flex; gap: 14px; }
.pp-fake-row i { flex: 1 1 0; height: 140px; border-radius: 14px; background: var(--surface); }
.pp-fake-wide { flex: 1 1 auto; border-radius: 14px; background: var(--surface); }
.pp-info { padding: 10px 16px 14px; text-align: center; color: var(--muted); font-size: .82em; }
@media (max-width: 700px) {
  .pp-top { flex-wrap: wrap; }
  .pp-tab span:last-child { display: none; }
}

/* ── Настройки → Промо (promo.js) ── */
.promo-ico { display: inline-flex; }
.promo-ico svg { width: 16px; height: 16px; }
.promo-intro { display: flex; align-items: center; gap: 20px; margin-bottom: 18px; }
.promo-intro-main { flex: 1 1 auto; min-width: 0; }
.promo-intro > .btn { flex: 0 0 auto; white-space: nowrap; }
.promo-intro-t { font-size: 1.05em; font-weight: 600; margin-bottom: 4px; }
.promo-intro .page-sub { line-height: 1.55; margin: 0; }
.promo-sect { margin: 6px 0 12px; }
.promo-empty { display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center;
  padding: 44px 20px; background: var(--surface); border-radius: var(--radius); color: var(--text-dim); }
.promo-empty-ico svg { width: 38px; height: 38px; color: var(--muted); }
.promo-empty .btn { margin-top: 6px; }
.promo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; }
.promo-card { display: flex; flex-direction: column; gap: 8px; padding: 14px; }
.promo-card-t { font-weight: 600; overflow-wrap: anywhere; }
.promo-card-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
.promo-card-actions .btn-sm > svg { width: 15px; height: 15px; }
.promo-thumb { display: block; width: 100%; padding: 0; border: 0; background: none; cursor: pointer;
  border-radius: 12px; overflow: hidden; }
.promo-thumb:hover { box-shadow: 0 0 0 2px var(--accent-border); }
.promo-tpls { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.promo-tpl { display: flex; flex-direction: column; gap: 8px; padding: 10px; border: 1px solid transparent;
  border-radius: var(--radius-sm); background: var(--surface-2); cursor: pointer; font: inherit; color: var(--text);
  text-align: left; transition: border-color .15s; }
.promo-tpl:hover { border-color: var(--accent-border); }
.promo-tpl-prev { display: flex; justify-content: center; overflow: hidden; border-radius: 8px; }
.promo-tpl-t { font-size: .9em; }
.promo-ready-list { display: flex; flex-direction: column; gap: 14px; }
.promo-rcard { position: relative; padding: 18px; border: 1px solid transparent; }
.promo-rcard.is-live { border-color: color-mix(in srgb, var(--toggle-on) 45%, transparent); }
.promo-rcard-body { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 20px; }
.promo-grip { position: absolute; left: 4px; top: 50%; transform: translateY(-50%); cursor: grab;
  color: var(--muted); display: inline-flex; padding: 6px 2px; }
.promo-grip svg { width: 15px; height: 15px; }
.promo-rcard:has(.promo-grip) { padding-left: 26px; }
.promo-rcard.dnd-dragging { opacity: .5; }
.promo-rcard-side { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.promo-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.promo-stat { display: flex; flex-direction: column; gap: 2px; padding: 8px; border-radius: var(--radius-sm);
  background: var(--surface-2); font-size: .74em; color: var(--muted); line-height: 1.25; }
.promo-stat b { font-size: 1.45em; color: var(--text); font-weight: 600; }
.promo-rcard-main { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.promo-rcard-head { display: flex; align-items: flex-start; gap: 12px; }
.promo-rcard-title { flex: 1 1 auto; min-width: 0; }
.promo-rcard-title .page-sub { margin-top: 3px; }
.promo-live { display: flex; align-items: center; gap: 10px; cursor: pointer; flex: 0 0 auto; }
.promo-live-t { font-size: .82em; color: var(--text-dim); }
.promo-rcard.is-live .promo-live-t { color: var(--toggle-on); }
.promo-rule { display: flex; flex-direction: column; gap: 6px; }
.promo-rule-l { font-size: .74em; letter-spacing: .4px; text-transform: uppercase; color: var(--muted); }
.promo-rule-hint { font-size: .8em; color: var(--muted); line-height: 1.5; }
.promo-seg { display: flex; flex-wrap: wrap; gap: 4px; padding: 3px; background: var(--surface-2);
  border-radius: 11px; align-self: flex-start; max-width: 100%; }
.promo-segbtn { padding: 7px 13px; border: 0; border-radius: 8px; background: transparent; color: var(--text-dim);
  font: inherit; font-size: .84em; cursor: pointer; transition: background .15s, color .15s; }
.promo-segbtn:hover { color: var(--text); }
.promo-segbtn.active { background: var(--surface); color: var(--text); box-shadow: 0 1px 3px rgba(0,0,0,.25); }
.promo-check { display: flex; align-items: center; gap: 9px; font-size: .86em; color: var(--text-dim); cursor: pointer; }
.promo-check input { width: 16px; height: 16px; accent-color: var(--toggle-on); }
.card .promo-seg { background: var(--bg); }
.card .promo-segbtn.active { background: var(--surface-2); }
/* Общая настройка показа над списком готовых. */
.promo-opt { display: flex; align-items: center; gap: 16px; margin-bottom: 18px; cursor: pointer; }
.promo-opt-main { flex: 1 1 auto; min-width: 0; }
.promo-opt .page-sub { margin: 3px 0 0; line-height: 1.55; }
/* Карточка карусели: миниатюра первого слайда + метка. Метка лежит на картинке баннера
   любого цвета — тёмная плашка с белым текстом читается в обеих темах. */
.promo-cthumb { position: relative; }
.promo-cthumb-badge { position: absolute; left: 8px; top: 8px; display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 9px; border-radius: 99px; background: rgba(0,0,0,.62); color: #fff; font-size: .74em;
  pointer-events: none; }
.promo-cthumb-badge .promo-ico svg { width: 13px; height: 13px; }
.promo-cthumb-empty { display: grid; place-items: center; aspect-ratio: 2 / 1; border-radius: 12px;
  background: var(--surface-2); color: var(--muted); font-size: .85em; }
.promo-note { display: flex; align-items: flex-start; gap: 6px; margin-top: 6px; font-size: .8em;
  color: var(--text-dim); line-height: 1.45; }
.promo-note .promo-ico { margin-top: 2px; }
.promo-note .promo-ico svg { width: 14px; height: 14px; }
.promo-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.promo-chip { padding: 4px 10px; border-radius: 99px; background: var(--surface-2); font-size: .82em;
  color: var(--text-dim); overflow-wrap: anywhere; }
.card .promo-chip { background: var(--bg); }
.promo-chip.is-off { opacity: .55; }
/* «Сценарии»: метка подстановки ({student}) — ярче её пояснения. */
.promo-var b { color: var(--text); font-weight: 600; }
/* Окно «Новая карусель»: плитки готовых баннеров, номер — место слайда. */
.promo-car-form { display: flex; flex-direction: column; gap: 14px; }
.promo-picks { display: grid; grid-template-columns: repeat(auto-fill, minmax(196px, 1fr)); gap: 10px; }
.promo-pick { position: relative; display: flex; flex-direction: column; gap: 8px; padding: 8px;
  border: 2px solid transparent; border-radius: var(--radius-sm); background: var(--surface-2); cursor: pointer;
  font: inherit; color: var(--text); text-align: left; transition: border-color .15s; }
.promo-pick:hover { border-color: var(--accent-border); }
.promo-pick.on { border-color: var(--accent); }
.promo-pick-prev { display: flex; justify-content: center; overflow: hidden; border-radius: 8px; }
.promo-pick-t { font-size: .86em; overflow-wrap: anywhere; }
.promo-pick-n { position: absolute; top: 14px; right: 14px; width: 26px; height: 26px; display: grid; place-items: center;
  border-radius: 50%; background: var(--accent); color: var(--on-accent); font-weight: 700; font-size: .85em;
  box-shadow: 0 2px 8px rgba(0,0,0,.35); }
.promo-pick-n:empty { display: none; }
@media (max-width: 860px) {
  .promo-rcard-body { grid-template-columns: 1fr; }
  .promo-intro { flex-direction: column; align-items: stretch; }
  .promo-tpls { grid-template-columns: 1fr; }
}
[data-theme="light"] .promo-segbtn.active,
[data-theme="light"] .pp-tab.active { box-shadow: 0 1px 3px rgba(20,50,40,.16); }

/* ── Конструктор баннера (editor.js, #/promo/:id) ── */
body.promo-edit-view .topbar { display: none; }
body.promo-edit-view { overflow: hidden; }
.pe { position: fixed; inset: 0; z-index: 50; display: flex; flex-direction: column; background: var(--bg);
  color: var(--text); font-family: var(--font); }
.pe-ico { display: inline-flex; flex: 0 0 auto; }
.pe-ico svg { width: 16px; height: 16px; }
.pe-top { display: flex; align-items: center; gap: 12px; padding: 8px 12px; background: var(--surface);
  border-bottom: 1px solid var(--border); flex: 0 0 auto; min-width: 0; }
.pe-back { flex: 0 0 auto; }
.pe-titlebox { display: flex; align-items: center; gap: 10px; flex: 1 1 auto; min-width: 0; }
.pe-title { flex: 1 1 auto; min-width: 80px; max-width: 360px; padding: 7px 10px; border: 1px solid transparent;
  border-radius: var(--radius-sm); background: transparent; color: var(--text); font: inherit; font-weight: 600; }
.pe-title:hover { background: var(--surface-2); }
.pe-title:focus { outline: none; border-color: var(--accent-border); background: var(--bg); }
.pe-chip { flex: 0 0 auto; padding: 3px 9px; border-radius: 99px; font-size: .74em; color: var(--text-dim);
  border: 1px solid var(--border-2); white-space: nowrap; }
.pe-chip-live { color: var(--toggle-on); border-color: color-mix(in srgb, var(--toggle-on) 50%, transparent); }
.pe-seg { display: inline-flex; gap: 3px; padding: 3px; background: var(--surface-2); border-radius: 10px; flex: 0 0 auto; }
.pe-segbtn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-width: 30px; padding: 6px 10px;
  border: 0; border-radius: 7px; background: transparent; color: var(--text-dim); font: inherit; font-size: .82em; cursor: pointer; }
.pe-segbtn svg { width: 16px; height: 16px; }
.pe-segbtn:hover { color: var(--text); }
.pe-segbtn.active { background: var(--surface); color: var(--text); box-shadow: 0 1px 3px rgba(0,0,0,.3); }
.pe-segbtn.off:not(.active) { opacity: .6; }
.pe-tools, .pe-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.pe-savestate { font-size: .76em; color: var(--muted); max-width: 230px; text-align: right; line-height: 1.3; }
.pe-sep { width: 1px; height: 20px; background: var(--border-2); margin: 0 3px; flex: 0 0 auto; }
.pe-ibtn { display: inline-grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 0; border-radius: 8px;
  background: transparent; color: var(--text-dim); cursor: pointer; flex: 0 0 auto; }
.pe-ibtn svg { width: 17px; height: 17px; }
.pe-ibtn:hover:not(:disabled) { background: var(--surface-2); color: var(--text); }
.pe-ibtn:disabled { opacity: .35; cursor: default; }
.pe-ibtn.pe-danger:hover { color: var(--danger); }
.pe-zoomlabel { min-width: 52px; padding: 6px 4px; border: 0; border-radius: 8px; background: transparent;
  color: var(--text-dim); font: inherit; font-size: .8em; cursor: pointer; }
.pe-zoomlabel:hover { background: var(--surface-2); color: var(--text); }
.pe-main { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: 220px minmax(0, 1fr) 300px; }
.pe-left, .pe-right { background: var(--surface); overflow-y: auto; min-height: 0; }
.pe-left { border-right: 1px solid var(--border); padding: 12px 10px; display: flex; flex-direction: column; gap: 6px; }
.pe-right { border-left: 1px solid var(--border); }
.pe-h { display: flex; align-items: center; gap: 6px; font-size: .72em; text-transform: uppercase; letter-spacing: .6px;
  color: var(--muted); padding: 4px 4px 2px; }
.pe-h-layers { margin-top: 12px; }
.pe-add { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
.pe-addbtn { display: flex; align-items: center; gap: 7px; padding: 8px 8px; border: 0; border-radius: 8px;
  background: var(--surface-2); color: var(--text); font: inherit; font-size: .8em; cursor: pointer; text-align: left;
  min-width: 0; }
.pe-addbtn span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pe-addbtn:hover { background: var(--border-2); }
.pe-layers { display: flex; flex-direction: column; gap: 2px; }
.pe-layer { display: flex; align-items: center; gap: 7px; padding: 6px 6px; border-radius: 7px; cursor: pointer;
  font-size: .8em; color: var(--text-dim); border: 1px solid transparent; }
.pe-layer:hover { background: var(--surface-2); color: var(--text); }
.pe-layer.active { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--text); }
.pe-layer.is-hidden .pe-layer-name { opacity: .45; text-decoration: line-through; }
.pe-layer.drop { border-top-color: var(--accent); }
.pe-layer.dragging { opacity: .4; }
.pe-layer-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pe-lbtn { display: inline-grid; place-items: center; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 5px;
  background: transparent; color: var(--muted); cursor: pointer; opacity: 0; flex: 0 0 auto; }
.pe-lbtn svg { width: 13px; height: 13px; }
.pe-layer:hover .pe-lbtn, .pe-lbtn.on, .pe-layer.is-hidden .pe-lbtn:first-of-type { opacity: 1; }
.pe-lbtn:hover { color: var(--text); background: var(--border-2); }
@media (hover: none) { .pe-lbtn { opacity: 1; } }
.pe-empty-note { font-size: .8em; color: var(--muted); padding: 6px 4px; }

/* Холст: точечная сетка подложки; баннер по центру, под ним — ручка высоты. */
.pe-canvas { position: relative; overflow: auto; min-height: 0; padding: 32px;
  background-color: var(--bg);
  background-image: radial-gradient(color-mix(in srgb, var(--text) 9%, transparent) 1px, transparent 1px);
  background-size: 18px 18px; }
.pe-canvas.pe-drop { box-shadow: inset 0 0 0 3px var(--accent); }
.pe-boardwrap { display: flex; flex-direction: column; align-items: center; gap: 0; min-width: min-content; margin: 0 auto; }
.pe-board { position: relative; flex: 0 0 auto; touch-action: none; user-select: none; -webkit-user-select: none;
  box-shadow: 0 10px 40px rgba(0,0,0,.35); border-radius: 4px; }
.pe-stagehost { position: absolute; inset: 0; }
.pe-stagehost .pb-stage { cursor: default; }
.pe-overlay { position: absolute; inset: 0; pointer-events: none; overflow: visible; }
.pb-mode-edit .pb-el:not(.pb-locked) { cursor: move; }
.pb-mode-edit .pb-el:not(.pb-locked):hover { outline: calc(1px / var(--pe-z, 1)) solid color-mix(in srgb, #4ea8ff 70%, transparent); }
.pb-mode-edit .pb-locked { pointer-events: none; }
.pb-mode-edit .pb-text:empty { min-height: 1.2em; outline: calc(1px / var(--pe-z, 1)) dashed rgba(255,255,255,.3); }
.pb-mode-edit .pe-editing, .pb-mode-edit .pe-editing * { cursor: text; outline: none; user-select: text; -webkit-user-select: text; }
.pe-board.pe-readonly { pointer-events: none; opacity: .92; }
.pe-sel { position: absolute; box-sizing: border-box; border: 1.5px solid #4ea8ff; transform-origin: 50% 50%; }
.pe-sel-locked { border-style: dashed; border-color: color-mix(in srgb, #4ea8ff 60%, transparent); }
.pe-handle { position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px; background: #fff;
  border: 1.5px solid #4ea8ff; border-radius: 2px; pointer-events: auto; touch-action: none; }
.pe-h-nw { left: 0; top: 0; cursor: nwse-resize; } .pe-h-n { left: 50%; top: 0; cursor: ns-resize; }
.pe-h-ne { left: 100%; top: 0; cursor: nesw-resize; } .pe-h-e { left: 100%; top: 50%; cursor: ew-resize; }
.pe-h-se { left: 100%; top: 100%; cursor: nwse-resize; } .pe-h-s { left: 50%; top: 100%; cursor: ns-resize; }
.pe-h-sw { left: 0; top: 100%; cursor: nesw-resize; } .pe-h-w { left: 0; top: 50%; cursor: ew-resize; }
.pe-rot { position: absolute; left: 50%; top: -30px; width: 14px; height: 14px; margin-left: -7px; border-radius: 50%;
  background: #fff; border: 1.5px solid #4ea8ff; pointer-events: auto; cursor: grab; touch-action: none; }
.pe-rot::after { content: ''; position: absolute; left: 50%; top: 100%; width: 1.5px; height: 16px; margin-left: -.75px;
  background: #4ea8ff; }
.pe-size { position: absolute; left: 50%; top: 100%; margin-top: 8px; transform: translateX(-50%); padding: 2px 7px;
  border-radius: 6px; background: #4ea8ff; color: #fff; font-size: 11px; white-space: nowrap; }
.pe-guide { position: absolute; background: #ff3d8b; }
.pe-guide-x { width: 1px; }
.pe-guide-y { height: 1px; }
.pe-marquee { position: absolute; border: 1px solid #4ea8ff; background: rgba(78,168,255,.12); }
.pe-hhandle { width: 100%; height: 18px; display: flex; align-items: center; justify-content: center; cursor: ns-resize;
  touch-action: none; }
.pe-hhandle span { width: 46px; height: 5px; border-radius: 3px; background: var(--border-2); }
.pe-hhandle:hover span { background: var(--accent); }
.pe-notice { width: min(560px, 100%); margin-bottom: 14px; padding: 14px 16px; border-radius: var(--radius);
  background: var(--surface); display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
/* ⚠️ display у кнопок, ручки и плашки (.btn — inline-flex) перебивает атрибут hidden. */
.pe [hidden] { display: none !important; }
.pe-notice-t { font-weight: 600; }
.pe-notice-s { font-size: .84em; color: var(--text-dim); line-height: 1.5; }
.pe-notice .btn { margin-top: 4px; }

/* Панель свойств */
.pe-props { display: flex; flex-direction: column; }
.pe-ptitle { display: flex; align-items: center; gap: 8px; padding: 14px 14px 10px; font-weight: 600; }
.pe-bpnote { margin-left: auto; font-size: .72em; font-weight: 400; color: var(--muted); padding: 2px 8px;
  border-radius: 99px; background: var(--surface-2); }
.pe-sec { display: flex; flex-direction: column; gap: 10px; padding: 12px 14px 14px; border-top: 1px solid var(--border); }
.pe-sec-t { font-size: .72em; text-transform: uppercase; letter-spacing: .6px; color: var(--muted); }
.pe-f { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.pe-f-l { font-size: .76em; color: var(--text-dim); }
.pe-right .input, .pe-right .select, .pe-right .textarea { padding: 7px 9px; font-size: .86em; border-radius: 8px; }
.pe-num { width: 100%; }
.pe-area { min-height: 84px; resize: vertical; }
.pe-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 10px; }
.pe-rangebox { display: flex; align-items: center; gap: 8px; }
.pe-range { flex: 1 1 auto; accent-color: var(--accent); min-width: 0; }
.pe-range-v { flex: 0 0 54px; text-align: right; font-size: .8em; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.pe-colorbox { display: flex; align-items: center; gap: 10px; }
.pe-swatch { width: 34px; height: 26px; border-radius: 7px; border: 1px solid var(--border-2); cursor: pointer; padding: 0;
  background-image: none; }
.pe-hex { font-size: .8em; color: var(--text-dim); font-family: ui-monospace, Menlo, monospace; }
.pe-seg-sm { align-self: flex-start; flex-wrap: wrap; }
.pe-check { display: flex; align-items: center; gap: 8px; font-size: .82em; color: var(--text-dim); cursor: pointer; }
.pe-check input { width: 15px; height: 15px; accent-color: var(--accent); }
.pe-checks { display: flex; flex-wrap: wrap; gap: 8px 16px; }
.pe-row, .pe-alignrow { display: flex; align-items: center; flex-wrap: wrap; gap: 2px; }
.pe-imgrow { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
.pe-hint { font-size: .76em; color: var(--muted); line-height: 1.45; }
.pe-hint-bad { color: var(--danger); }
.pe-bprow { display: flex; align-items: center; gap: 10px; }
.pe-bprow-main { flex: 1 1 auto; min-width: 0; font-size: .86em; }
.pe-keys { display: flex; flex-direction: column; gap: 4px; font-size: .76em; color: var(--muted); }
.pe-keys div { display: flex; justify-content: space-between; gap: 10px; }
.pe-keys b { color: var(--text-dim); font-weight: 500; }
@media (max-width: 1180px) {
  .pe-main { grid-template-columns: 190px minmax(0, 1fr) 270px; }
  .pe-segtxt, .pe-savestate { display: none; }
}
/* Узкий экран: конструктор остаётся рабочим, но панели уходят под холст. Собирать
   баннер удобнее на компьютере — телефон тут для правки опечатки. */
@media (max-width: 860px) {
  /* Одна общая прокрутка экрана: панели идут друг за другом в полный рост. */
  .pe { overflow-y: auto; }
  .pe-top { flex-wrap: wrap; position: sticky; top: 0; z-index: 3; }
  .pe-titlebox { order: 3; flex-basis: 100%; }
  .pe-main { display: flex; flex-direction: column; flex: 0 0 auto; }
  .pe-left, .pe-right { overflow: visible; }
  .pe-left { border-right: 0; border-bottom: 1px solid var(--border); }
  .pe-add { grid-template-columns: repeat(3, 1fr); }
  .pe-layers { max-height: 180px; overflow-y: auto; }
  .pe-canvas { overflow-x: auto; overflow-y: visible; padding: 16px; }
  .pe-right { border-left: 0; border-top: 1px solid var(--border); }
}

/* ── Договор с исполнителем (Настройки → Персонал → «Договор», modules/contracts) ──
   Страницы предпросмотра и подпись — белые САМИ (это холст и картинка документа, а не
   поверхность интерфейса), поэтому цвета фона им не задаём: только кольцо-тень, чтобы
   белый лист не сливался со светлой темой. */
.ctr-pick { max-width: 420px; margin-top: 14px; }
.ctr-group { margin-top: 18px; }
.ctr-group-title { font-weight: 600; font-size: .92em; margin-bottom: 8px; }
.ctr-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 14px; }
.ctr-grid .field { margin-bottom: 10px; }
.ctr-grid .ctr-wide { grid-column: 1 / -1; }
.ctr-short { color: var(--muted); font-size: .8em; margin-top: 5px; min-height: 1em; }
.ctr-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
.ctr-sig { margin-top: 12px; max-width: 360px; }
.ctr-sig img { display: block; width: 100%; height: auto; border-radius: 8px;
  box-shadow: 0 0 0 1px var(--border); }
.ctr-pages { display: flex; flex-direction: column; gap: 14px; }
.ctr-page canvas { display: block; width: 100%; height: auto; border-radius: 4px;
  box-shadow: 0 0 0 1px var(--border); }
.ctr-page-num { color: var(--muted); font-size: .8em; text-align: center; margin-top: 4px; }
.ctr-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 10px; }
.ctr-chip { padding: 4px 10px; border-radius: 999px; border: 1px solid transparent; cursor: pointer;
  background: var(--surface-2); color: var(--text-dim); font: inherit; font-size: .82em; }
.ctr-chip:hover { color: var(--text); background: color-mix(in srgb, var(--text) 10%, var(--surface-2)); }
.textarea.ctr-text { min-height: 420px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .82em; line-height: 1.5; }
.ctr-help-box { margin-top: 10px; font-size: .85em; color: var(--text-dim); }
.ctr-help-box summary { cursor: pointer; color: var(--muted); }
.ctr-help { display: grid; gap: 4px; margin-top: 8px; }
.ctr-help code { font-family: ui-monospace, Menlo, Consolas, monospace; color: var(--text); }
@media (max-width: 640px) {
  .ctr-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ── Кабинет родителя (#/parent, views/parent.js) ─────────────────
   Макет владельца: вкладки детей → «папки» классов с балансом справа → панель класса с
   карточками в две колонки (посещаемость | алмазы, оценка учителя | награды, чат | события).
   Префикс pc-: короткие имена состояний вроде .empty уже заняты утилитами. Цвета — токены:
   акцент школы, --grade-* для сигнала «хорошо/средне/плохо».
   ⚠️ Просьбы владельца (2026-10-21): баланс НЕ переносится на новую строку — вкладки классов
   прокручиваются, а баланс стоит справа в той же строке; всё компактно; карточки одного
   ряда — ОДНОЙ высоты, пустая не короче соседней (сетка тянет ряд, подвал прижат вниз). */
/* Отступ от шапки — вчетверо меньше общего (36 → 9; владелец просил урезать дважды), снизу — без запаса в 80px: на ПК
   страница, которая помещается, не должна прокручиваться (просьба владельца). Кнопка
   «Чат поддержки» висит поверх (position: fixed) и места внизу не требует; на телефоне
   запас под неё остаётся — там она закрывала бы последнюю карточку. */
body.parent-view .app { padding-top: 9px; padding-bottom: 16px; }
.pc { font-size: .94em; }
.pc-kidtabs .tab { padding-top: 6px; }
.pc-kidbar { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px;
  border-bottom: 1px solid var(--border); margin-bottom: 14px; }
.pc-kidtabs { border-bottom: 0; margin-bottom: 0; overflow-x: auto; scrollbar-width: none; min-width: 0; }
.pc-kidtabs .tab { white-space: nowrap; }
.pc-link { display: inline-flex; align-items: center; gap: 6px; padding: 8px 2px; border: 0; flex: 0 0 auto;
  background: none; color: var(--text-dim); font: inherit; font-family: var(--font); font-size: .86em;
  cursor: pointer; white-space: nowrap; }
.pc-link:hover { color: var(--text); }
.pc-link svg { width: 14px; height: 14px; }

.pc-classbar { display: flex; align-items: flex-end; gap: 12px; flex-wrap: nowrap; min-width: 0; }
.pc-classtabs { display: flex; gap: 2px; flex: 1 1 auto; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.pc-classtabs::-webkit-scrollbar { display: none; }
.pc-classtab { padding: 9px 16px; border: 0; border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: transparent; color: var(--text-dim); font: inherit; font-family: var(--font);
  font-size: .95em; font-weight: 600; white-space: nowrap; cursor: pointer; flex: 0 0 auto; }
.pc-classtab:hover { color: var(--text); }
.pc-classtab.active { background: var(--surface); color: var(--text); }
.pc-bal { display: flex; align-items: center; gap: 8px; padding: 0 2px 7px; flex: 0 0 auto;
  white-space: nowrap; font-size: .9em; color: var(--text-dim); }
.pc-bal-num { font-size: 1.55em; font-weight: 700; line-height: 1; color: var(--accent);
  font-variant-numeric: tabular-nums; }
.pc-plus { display: grid; place-items: center; width: 28px; height: 28px; border: 0; border-radius: 50%;
  background: var(--btn-primary); color: var(--on-btn-primary); cursor: pointer; flex: 0 0 auto; }
.pc-plus svg { width: 16px; height: 16px; }
.pc-plus:disabled { opacity: .5; cursor: default; }

/* Панель класса: верхний левый угол прямой, только когда активна ПЕРВАЯ вкладка — она
   и панель тогда одна «папка»; иначе панель скруглена со всех сторон. */
.pc-panel { background: var(--surface); border-radius: var(--radius); padding: 12px 14px 14px; }
.pc-classbar:has(.pc-classtab:first-child.active) + .pc-panel { border-top-left-radius: 0; }
.pc-teacher { display: flex; align-items: center; gap: 7px; margin: 0 0 10px; font-size: .86em;
  color: var(--text-dim); }
.pc-teacher-l { color: var(--muted); }
.avatar.pc-teacher-av { width: 20px; height: 20px; font-size: .6em; }
/* Места карточек закреплены (макет владельца): слева чат класса, под ним события, справа
   столбиком четыре показателя — посещаемость, алмазы, оценка учителя, награды.
   ⚠️ Карточки не идут потоком: когда алмазов нет (школа их выключила), всё съезжало —
   поэтому все шесть карточек есть всегда (parents.md).
   Чат — во всю высоту ТРЁХ показателей, события — ровно в ряд с наградами (макет
   владельца 2026-10-22). Высоту рядов задают показатели: у чата и событий `contain: size`
   — их содержимое ряд не растягивает (лента прокручивается внутри), а `min-height` чата —
   нижняя граница, ниже которой он не сожмётся. Телефон — одна колонка (media ниже). */
.pc-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: stretch;
  grid-template-areas: "chat att" "chat dm" "chat rate" "events rew"; }
.pc-cards > .pc-att { grid-area: att; }
.pc-cards > .pc-dm { grid-area: dm; }
.pc-cards > .pc-rate { grid-area: rate; }
.pc-cards > .pc-rew { grid-area: rew; }
.pc-cards > .pc-chatcard { grid-area: chat; }
.pc-cards > .pc-events { grid-area: events; }
.pc-cards > .spinner { grid-column: 1 / -1; margin: 40px auto; }

.pc-card { background: var(--surface-2); padding: 0; overflow: hidden; min-width: 0;
  display: flex; flex-direction: column; }
/* Четыре карточки показателей — общий минимум (пустая не короче заполненной, и в одну
   колонку на телефоне тоже); награды и события — одного ряда, чуть выше. */
.pc-att, .pc-dm, .pc-rate, .pc-rew { min-height: 150px; }
.pc-rew { min-height: 190px; }
.pc-chatcard, .pc-events { contain: size; }
.pc-chatcard { min-height: 480px; }
.pc-card-main { flex: 1 1 auto; padding: 13px 15px 10px; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.pc-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.pc-card-t { font-size: .98em; font-weight: 500; }
.pc-card-t-lg { padding: 13px 15px 6px; }
.pc-big { font-size: 1.7em; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }
.pc-tone-good { color: var(--grade-good); }
.pc-tone-mid { color: var(--grade-mid); }
.pc-tone-low { color: var(--grade-low); }
.pc-att-body { display: flex; align-items: flex-end; gap: 12px; }
.pc-facts { font-size: .84em; color: var(--muted); line-height: 1.55; flex: 0 0 auto; }
.pc-chart { flex: 1 1 auto; min-width: 0; }
.pc-card .spark { height: 38px; }
/* «Расписание» в посещаемости — маленькая таблетка (макет владельца), открывает сетку недели. */
.pc-pill { display: inline-flex; align-items: center; padding: 4px 13px; border: 0; border-radius: 999px;
  background: var(--btn-primary); color: var(--on-btn-primary); font: inherit; font-family: var(--font);
  font-size: .8em; cursor: pointer; }
.pc-pill:hover { opacity: .88; }
.pc-card .spark-line { stroke: var(--accent); stroke-opacity: .9; }

.pc-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex: 0 0 auto;
  padding: 6px 15px 7px; border: 0; border-top: 1px solid var(--border); background: transparent;
  color: var(--muted); font: inherit; font-family: var(--font); font-size: .76em; cursor: pointer; }
.pc-foot:hover { color: var(--text-dim); }
.pc-chev { display: inline-flex; width: 14px; height: 14px; transition: transform .2s; }
.pc-chev svg { width: 100%; height: 100%; }
.pc-foot.open .pc-chev { transform: rotate(180deg); }
.pc-more { max-height: 220px; overflow-y: auto; padding: 2px 15px 10px; overscroll-behavior: contain; }
.pc-rows { display: flex; flex-direction: column; }
.pc-rowline { display: flex; justify-content: space-between; gap: 12px; padding: 4px 0; font-size: .82em;
  border-bottom: 1px solid var(--border); min-width: 0; }
.pc-rowline:last-child { border-bottom: 0; }
.pc-rowline > span:first-child { min-width: 0; overflow-wrap: anywhere; }
.pc-rowval { font-variant-numeric: tabular-nums; flex: 0 0 auto; text-align: right; }

.pc-dm-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex: 1 1 auto; }
.pc-dm-text { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.pc-dm-note { font-size: .88em; color: var(--text-dim); line-height: 1.4; }
.pc-dm-side { display: flex; flex-direction: column; align-items: center; gap: 6px; flex: 0 0 auto; }
.pc-dm .dmw-dial { width: 64px; height: 64px; }
.pc-dm .dmw-num { font-size: 1.15em; }
/* Медали — 3D-плитки витрины ученика (rewards-shelf), только маленькие и без подписи:
   название и описание — в окне по нажатию. */
.pc-medals { display: grid; grid-template-columns: repeat(4, minmax(0, 76px)); gap: 10px; }
.pc-medals .rw-tile { width: 100%; min-width: 0; padding: 0; background: none; box-shadow: none; }
.pc-medals .rw-tile-name, .pc-medals .rw-tile-badge { display: none; }
.pc-medals .rw-stage { cursor: pointer; }
.pc-medals .rw-empty-ico { font-size: 1.6em; }

.pc-events { padding-bottom: 10px; }
.pc-events > .pc-note-d { padding: 0 15px; }
.pc-ev-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 0 8px 0 10px;
  overscroll-behavior: contain; }
.pc-ev { padding: 3px 6px; font-size: .8em; }
.pc-ev .cka-text { display: flex; flex-wrap: wrap; column-gap: 6px; row-gap: 1px; }
.pc-ev-at { flex-basis: 100%; font-size: .76em; color: var(--muted); }

.pc-note { max-width: 560px; }
.pc-note-t { font-weight: 500; margin-bottom: 6px; }
.pc-note-d { color: var(--text-dim); font-size: .88em; line-height: 1.45; }

.pc-support { position: fixed; right: max(16px, env(safe-area-inset-right)); bottom: max(16px, env(safe-area-inset-bottom));
  z-index: 40; display: inline-flex; align-items: center; gap: 7px; padding: 9px 14px; border: 0;
  border-radius: 12px; background: var(--btn-primary); color: var(--on-btn-primary); font: inherit;
  font-family: var(--font); font-size: .9em; font-weight: 600; cursor: pointer; box-shadow: var(--card-shadow); }
.pc-support svg { width: 16px; height: 16px; }
.pc-unread { display: inline-grid; place-items: center; min-width: 18px; height: 18px; padding: 0 5px;
  border-radius: 9px; font-size: .74em; background: var(--surface); color: var(--text); }

.pc-settings { display: flex; flex-direction: column; gap: 8px; min-width: min(420px, 100%); }
.pc-prof { display: flex; flex-direction: column; gap: 10px; min-width: min(420px, 100%); }
.pc-prof-row { display: flex; align-items: center; gap: 14px; }
.pc-prof-row > div { display: flex; gap: 8px; flex-wrap: wrap; }
.avatar.pc-prof-avimg { width: 56px; height: 56px; font-size: 1.35em; }
.pc-prof-l { font-size: .84em; color: var(--text-dim); margin-top: 6px; }

@media (max-width: 760px) {
  .pc-cards { grid-template-columns: minmax(0, 1fr); grid-template-areas: none; }
  .pc-cards > .pc-card { grid-area: auto; }
  /* Чат на телефоне — на одну карточку показателя выше прежних 400px (просьба владельца). */
  .pc-chatcard { min-height: 0; height: 560px; }
  .pc-events { height: 400px; }
  .pc-panel { padding: 10px; }
  .pc-classtab { padding: 8px 12px; font-size: .9em; }
  .pc-bal-label { display: none; }                   /* на телефоне — число и «+», без подписи */
  body.parent-view .app { padding-top: 4px; }
  .pc { padding-bottom: 60px; }
  /* Порядок на телефоне (просьба владельца): чат класса → оценка учителя → посещаемость →
     алмазы → награды → события. Только CSS-порядком: разметка одна на обе ширины. */
  .pc-cards > .pc-chatcard { order: 1; }
  .pc-cards > .pc-rate { order: 2; }
  .pc-cards > .pc-att { order: 3; }
  .pc-cards > .pc-dm { order: 4; }
  .pc-cards > .pc-rew { order: 5; }
  .pc-cards > .pc-events { order: 6; }
}

/* Блок «Кабинет родителя» в профиле ученика у админа (views/profile.js). */
.pcode { display: flex; flex-direction: column; gap: 12px; }
.pcode-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 1.35em; letter-spacing: 2px; }
.pcode-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pcode-list { display: flex; flex-direction: column; gap: 8px; }
.pcode-item { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 12px; border-radius: var(--radius-sm); background: var(--surface-2); min-width: 0; }
.pcode-item-t { min-width: 0; overflow-wrap: anywhere; }
.pcode-item-s { font-size: .82em; color: var(--muted); }

/* Подпись сообщения родителя в чате ученика с учителем: «Татьяна (Родитель)» (chatui.parentTag). */
.bubble-parent { font-size: .76em; font-weight: 600; color: var(--accent-text); margin-bottom: 3px; }
.room-msg-who.room-msg-parent { color: var(--accent-text); }

/* Чаты кабинета родителя (views/parent.js → chatBox): лента, вложения, ответы. */
.pc-chatcard { padding-bottom: 12px; }
.pc-chat { display: flex; flex-direction: column; gap: 7px; min-width: 0; min-height: 0; padding: 0 12px; flex: 1 1 auto; }
.pc-chat-feed { display: flex; flex-direction: column; gap: 8px; flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: 8px; border-radius: 12px; overscroll-behavior: contain; }
.modal .pc-chat { padding: 0; flex: 0 0 auto; }
.modal .pc-chat-feed { height: min(60vh, 440px); }
.pc-chat-empty { margin: auto; text-align: center; max-width: 380px; }
.pc-msg { display: flex; align-items: flex-end; gap: 7px; max-width: 86%; align-self: flex-start; min-width: 0; }
.pc-msg.own { align-self: flex-end; flex-direction: row-reverse; }
.avatar.pc-msg-av { width: 26px; height: 26px; font-size: .72em; }
.pc-bubble { background: var(--surface); border-radius: 12px; padding: 6px 10px; min-width: 0; font-size: .92em; }
.modal .pc-bubble { background: var(--surface-2); }
.pc-msg.own .pc-bubble { background: color-mix(in srgb, var(--accent) 22%, var(--surface)); }
.pc-msg-who { font-size: .76em; color: var(--muted); margin-bottom: 1px; }
.pc-msg-who.parent { color: var(--accent-text); font-weight: 600; }
.pc-msg-text { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.4; }
.pc-msg-img { display: block; max-width: 100%; max-height: 200px; border-radius: 9px; margin: 2px 0 4px; }
.pc-msg-file { display: inline-block; margin: 2px 0 4px; color: var(--accent-text); overflow-wrap: anywhere; }
.pc-quote { display: block; width: 100%; text-align: left; font: inherit; font-family: var(--font); cursor: pointer;
  font-size: .82em; color: var(--text-dim); background: none; border: 0; border-left: 2px solid var(--accent);
  padding: 1px 0 1px 7px; margin-bottom: 3px; overflow-wrap: anywhere; }
/* Время — у правого края пузыря, одно (плюс значок «закреплено»): соседей у него нет. */
.pc-msg-meta { display: flex; align-items: center; justify-content: flex-end; gap: 4px; margin-top: 2px;
  font-size: .72em; color: var(--muted); }
.pc-msg-pin { display: inline-flex; width: 11px; height: 11px; color: var(--accent-text); }
.pc-msg-pin svg { width: 100%; height: 100%; }
.pc-bubble.pinned { box-shadow: inset 2px 0 0 var(--accent); }
.pc-msg.flash .pc-bubble { background: color-mix(in srgb, var(--accent) 26%, var(--surface)); transition: background .3s; }
/* Действия — иконками СБОКУ от пузыря, как в мессенджере (.bubble-actions): видны при
   наведении, на тач-экране — всегда. */
.pc-acts { display: inline-flex; gap: 1px; align-self: center; flex: 0 0 auto; opacity: 0; transition: opacity .12s; }
.pc-msg:hover .pc-acts, .pc-acts:focus-within { opacity: 1; }
@media (hover: none) { .pc-acts { opacity: .8; } }
.pc-act { display: grid; place-items: center; width: 26px; height: 26px; padding: 0; border: 0; border-radius: 7px;
  background: none; color: var(--muted); cursor: pointer; }
.pc-act svg { width: 15px; height: 15px; }
.pc-act:hover { color: var(--accent-text); background: color-mix(in srgb, var(--text) 8%, transparent); }
.pc-act.on { color: var(--accent-text); }
/* Полоса закреплённых над лентой: одна строка, клик листает по кругу. */
.pc-pinned { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 10px;
  background: var(--bg); min-width: 0; }
.pc-pinned-ico { display: inline-flex; width: 14px; height: 14px; color: var(--accent-text); flex: 0 0 auto; }
.pc-pinned-ico svg { width: 100%; height: 100%; }
.pc-pinned-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; text-align: left;
  border: 0; background: none; padding: 0; color: var(--text); font: inherit; font-family: var(--font); cursor: pointer; }
.pc-pinned-cap { font-size: .72em; color: var(--accent-text); }
.pc-pinned-body { font-size: .84em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pc-staged { display: flex; flex-direction: column; gap: 4px; }
.pc-staged-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: .8em;
  color: var(--text-dim); background: var(--surface); border-radius: var(--radius-sm); padding: 5px 9px; min-width: 0; }
.pc-staged-row > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pc-x { display: grid; place-items: center; width: 20px; height: 20px; border: 0; background: none;
  color: var(--muted); cursor: pointer; flex: 0 0 auto; }
.pc-x svg { width: 13px; height: 13px; }
/* Строка ввода — ОДНА на оба чата (класса и поддержки): общая подложка, внутри скрепка,
   поле и «Отправить» строго одной высоты (30px — владелец просил сузить поле) и по одной линии. Поле прозрачное, без
   своей рамки — иначе у .textarea/.modal .textarea свои отступы и высоты, и элементы
   «скакали». ⚠️ line-height и высота заданы явно: на тач-экранах шрифт поля 16px. */
.pc-chat-form { display: flex; align-items: flex-end; gap: 2px; padding: 2px; border-radius: 12px;
  background: var(--bg); border: 1px solid transparent; transition: border-color .15s; }
.pc-chat-form:focus-within { border-color: var(--border-2, var(--border)); }
.pc-chat-form .pc-chat-input { flex: 1 1 auto; min-width: 0; height: 30px; min-height: 30px; max-height: 120px;
  margin: 0; padding: 4px 6px; border: 0; border-radius: 0; background: transparent; box-shadow: none;
  line-height: 22px; resize: none; overflow-y: hidden; box-sizing: border-box; }
.pc-chat-form .pc-chat-input:focus { outline: none; box-shadow: none; border: 0; }
.pc-icon-btn { display: grid; place-items: center; width: 30px; height: 30px; flex: 0 0 auto; border: 0;
  border-radius: 9px; background: transparent; color: var(--text-dim); cursor: pointer; padding: 0; }
.pc-icon-btn:hover { color: var(--text); background: color-mix(in srgb, var(--text) 6%, transparent); }
.pc-icon-btn svg { width: 16px; height: 16px; }
.pc-icon-btn.pc-send { background: var(--btn-primary); color: var(--on-btn-primary); }
.pc-icon-btn.pc-send:hover { background: var(--btn-primary); filter: brightness(1.08); }
.pc-icon-btn:disabled { opacity: .45; cursor: default; }
.pc-chat-block { margin: 0; }

/* Кабинет родителя в СВЕТЛОЙ теме (просьба владельца): подложка панели темнее, карточки
   белые — наоборот, чем в тёмной. Те же токены, только поменяны местами; вкладка класса
   красится как панель, иначе «папка» разорвалась бы. Пузыри чата на белой карточке —
   серые, иначе слились бы с ней (в окне поддержки они и так --surface-2). */
:root[data-theme="light"] .pc-panel,
:root[data-theme="light"] .pc-classtab.active { background: var(--surface-2); }
:root[data-theme="light"] .pc-card { background: var(--surface); }
:root[data-theme="light"] .pc-bubble { background: var(--surface-2); }
:root[data-theme="light"] .pc-msg.own .pc-bubble { background: color-mix(in srgb, var(--accent) 18%, var(--surface)); }
:root[data-theme="light"] .pc-chat-feed.chat-bg-on .pc-msg:not(.own) .pc-bubble { background: var(--surface); }

/* ── Соединить пары (MATCHING) ─────────────────────────────────────
   Вид ответа — общий с вариантами теста и словами DND (селектор в правиле «ОДИН ВИД У ВСЕХ
   ОТВЕТОВ»: заливка --surface-2, рамка обесцвечена), состояния красят рамку. Пара — одним цветом из палитры текста (--rt-*,
   у неё своя пара для светлой темы) и одним номером: цвет сам по себе не должен быть
   единственным признаком пары. */
.ex-match { display: flex; flex-direction: column; gap: 12px; }
.match-task { color: var(--text); white-space: pre-wrap; }
.match-board { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 22px; align-items: start; }
.match-col { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.match-card {
  --mc: var(--accent);
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 46px; padding: 8px 12px;
  text-align: left; font: inherit; color: var(--text); cursor: pointer;
  border: 2px solid transparent;   /* заливка и радиус — общие, см. «ОДИН ВИД У ВСЕХ ОТВЕТОВ» выше */
  transition: border-color .15s, background .15s; -webkit-tap-highlight-color: transparent;
}
.match-card:hover { border-color: var(--accent-border); }
.match-card.sel { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--surface-2)); }
.match-card.linked { border-color: var(--mc); background: color-mix(in srgb, var(--mc) 12%, var(--surface-2)); }
.match-card.correct { border-color: var(--correct); background: color-mix(in srgb, var(--correct) 14%, transparent); }
.match-card.wrong { border-color: var(--wrong); background: color-mix(in srgb, var(--wrong) 12%, transparent); }
.match-text { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; white-space: pre-wrap; }
.match-badge {
  flex: 0 0 auto; min-width: 22px; height: 22px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: .75em; font-weight: 600; color: var(--muted);
}
.match-left .match-badge { background: var(--surface); }
.match-card.linked .match-badge { background: var(--mc); color: var(--bg); }
.match-card.correct .match-badge { background: var(--correct); color: var(--bg); }
.match-card.wrong .match-badge { background: var(--wrong); color: var(--bg); }
/* Пустой значок справа держит место: текст не прыгает, когда пару соединяют. */
.match-right .match-badge:empty { visibility: hidden; }
.match-card-pic { padding: 6px 10px 6px 8px; }
.match-pic {
  flex: 1 1 0; min-width: 0; width: 100%; max-height: 130px; aspect-ratio: 4 / 3; object-fit: cover;
  display: block; border-radius: calc(var(--radius-sm) - 2px); background: var(--surface);
}
.match-pic-fail { flex: 1 1 auto; font-size: .8em; color: var(--muted); text-align: center; padding: 6px; }

/* «Картинка — слово»: ряд квадратных плиток по центру, под ним ряд слов, пары — линиями
   (макет владельца продукта). Ряд растёт от середины в обе стороны, а когда ширины не хватает,
   плитки переносятся вторым рядом. Расстояние между рядами — место для линий.
   ⚠️ Линия идёт от середины плитки к середине слова ПОД самими плитками (z-index), поэтому
   всё, что лежит над ней, обязано быть непрозрачным: подкраска вердиктов смешивается с
   --surface-2, а не с прозрачным, иначе конец линии просвечивает сквозь слово. */
.match-pics { --mp: clamp(84px, 24vw, 150px); }
/* ⚠️ align-items: stretch — у базовой .match-board стоит start, и ряды не растягивались на всю
   ширину, а «прилипали» влево (колоночный flex + align-items: flex-start, docs/gotchas.md). */
.match-board-pics { position: relative; display: flex; flex-direction: column; align-items: stretch; gap: 68px; }
.match-board-pics .match-col {
  position: relative; z-index: 1; flex-direction: row; flex-wrap: wrap; justify-content: center; gap: 14px;
}
.match-lines { position: absolute; left: 0; top: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; overflow: visible; }
.match-line {
  fill: none; stroke: color-mix(in srgb, var(--text) 80%, var(--bg)); stroke-width: 4; stroke-linecap: round;
  transition: stroke .15s;
}
.match-line.correct { stroke: var(--correct); }
.match-line.wrong { stroke: var(--wrong); }
.match-board-pics .match-card-pic {
  flex: 0 0 var(--mp); width: var(--mp); aspect-ratio: 1; min-height: 0; padding: 0;
  border-width: 3px; border-radius: 18px; overflow: hidden;
}
.match-board-pics .match-pic {
  flex: none; width: 100%; height: 100%; max-height: none; aspect-ratio: auto; object-fit: cover; border-radius: 0;
}
.match-board-pics .match-card-word {
  flex: 0 0 auto; width: auto; min-width: var(--mp); max-width: 100%; padding: 10px 16px;
  justify-content: center; text-align: center; border-radius: 14px;
}
.match-board-pics .match-card-word .match-text { flex: 0 1 auto; }
/* Состояния без палитры пар: нейтральная связь → зелёный/красный вердикт. Правила стоят ПОСЛЕ
   общих .match-card.* и длиннее их селектором, поэтому красят поверх. */
.match-pics .match-card.linked {
  border-color: color-mix(in srgb, var(--text) 70%, var(--bg));
  background: color-mix(in srgb, var(--text) 16%, var(--surface-2));
}
.match-pics .match-card.sel { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, var(--surface-2)); }
.match-pics .match-card.correct { border-color: var(--correct); background: color-mix(in srgb, var(--correct) 22%, var(--surface-2)); }
.match-pics .match-card.wrong { border-color: var(--wrong); background: color-mix(in srgb, var(--wrong) 22%, var(--surface-2)); }
@media (max-width: 560px) {
  .match-board { gap: 10px 12px; }
  /* Расстояние между рядами картинок и слов — место для линий; общий gap выше его сбил бы
     (медиа-правило идёт позже по файлу, а специфичность одинаковая). */
  .match-board.match-board-pics { gap: 56px; }
  .match-card { padding: 8px 9px; gap: 8px; font-size: .92em; }
  .match-card-pic { padding: 5px 7px; }
  .match-badge { min-width: 20px; height: 20px; }
}

/* Конструктор пар */
.match-edit-rows { display: flex; flex-direction: column; gap: 10px; margin-bottom: 10px; }
.match-edit-row { display: flex; align-items: center; gap: 10px; }
.match-edit-row .input { flex: 1 1 0; min-width: 0; }
.match-edit-num { flex: 0 0 22px; text-align: center; color: var(--muted); font-size: .85em; }
.match-edit-link { flex: 0 0 auto; display: flex; color: var(--muted); }
.match-edit-link svg { width: 16px; height: 16px; }
.match-edit-pic { flex: 0 0 auto; }
.match-edit-thumb {
  width: 84px; aspect-ratio: 1; overflow: hidden; cursor: pointer;
  border-radius: var(--radius-sm); background: var(--surface-2); border: 1px dashed var(--border-2);
}
.match-edit-thumb:hover { border-color: var(--accent-border); }
.match-edit-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.match-edit-empty {
  width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 2px; font-size: .72em; color: var(--muted);
}
.match-edit-empty .ico svg { width: 18px; height: 18px; opacity: .7; }
.match-edit-del-gap { flex: 0 0 30px; }
.match-preview .match-card { pointer-events: none; }
@media (max-width: 560px) {
  /* Узко: номер, левая часть и «удалить» — первой строкой, пара — второй на всю ширину. */
  .match-edit-row {
    display: grid; grid-template-columns: 22px minmax(0, 1fr) 30px; gap: 6px 8px;
    grid-template-areas: "num first del" ". second .";
  }
  .match-edit-row > :nth-child(1) { grid-area: num; }
  .match-edit-row > :nth-child(2) { grid-area: first; }
  .match-edit-row > :nth-child(3) { display: none; }
  .match-edit-row > :nth-child(4) { grid-area: second; }
  .match-edit-row > :nth-child(5) { grid-area: del; }
}

/* ── Перетаскивание файла в чат (chatui.enableFileDrop) ───────────── */
/* Подсказка — ::after поверх всей зоны чата; pointer-events:none, иначе она ловила бы
   dragleave и подсветка мигала. Цвета — токены, у светлой темы своя --surface. */
.file-drop-over::after {
  content: attr(data-drop-hint);
  position: absolute; inset: 0; z-index: 60;
  display: grid; place-items: center; padding: 16px; text-align: center;
  font-size: .95em; font-weight: 600; color: var(--text);
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  border: 2px dashed var(--accent); border-radius: inherit;
  pointer-events: none;
}

/* ── Расписание класса в окне кабинета родителя (classScheduleSection, режим viewer) ──
   Неделя целиком и БЕЗ прокрутки — ни вбок, ни вниз, на телефоне тоже (владелец
   2026-10-22). Окно — колонкой во всю доступную высоту (не выше 820px), сетка забирает
   остаток, а строки часов делят его поровну (`flex: 1 1 0`): высота клетки подстраивается
   под экран, а не задана числом. Колонки — `minmax(0, 1fr)`, без `min-width: 680px`.
   Селекторы с :has — (0,2,0), они сильнее мобильного `.modal, .modal-wide`. */
.modal:has(> .cd-sched-view) { max-width: 760px; padding: 20px 22px; overflow: hidden;
  height: min(calc(100dvh - 48px), 820px); display: flex; flex-direction: column; }
.modal:has(> .cd-sched-view) > .modal-title { margin-bottom: 10px; flex: 0 0 auto; }
.cd-sched-view { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: 8px; }
.cd-sched-view .sp-head { margin: 0; gap: 6px 14px; }
.cd-sched-view .win-legend { margin: 0; font-size: .86em; }
.cd-sched-view .cd-sched-miss { margin: 0; font-size: .78em; }
.cd-sched-view .sched-wrap { flex: 1 1 auto; min-height: 0; overflow: hidden; }
.cd-sched-view .sched-grid { --gap: 3px; min-width: 0; height: 100%; display: flex; flex-direction: column; }
.cd-sched-view .sched-grid .sched-row:not(.sched-head) { flex: 1 1 0; min-height: 0; }
.cd-sched-view .sched-row, .cd-sched-view .sched-today-overlay {
  grid-template-columns: 40px repeat(7, minmax(0, 1fr)); }
.cd-sched-view .sched-day { padding: 5px 2px; }
.cd-sched-view .sched-time { font-size: .6em; padding: 0 5px 0 0; }
.cd-sched-view .sched-grid .sched-cell { min-height: 0; border-radius: 6px; cursor: default; }
.cd-sched-view .lesson-chip { flex: 1 1 auto; min-height: 0; padding: 0 5px; border-radius: 5px;
  font-size: .62em; cursor: default; }
.cd-sched-view .lesson-chip .chip-lines { justify-content: center; line-height: 1.1; }
.cd-sched-view .lesson-chip:hover { transform: none; box-shadow: none; filter: none; }
/* «Занят» — урок учителя с другим учеником: нейтральная плашка без имени (busyChip). */
.lesson-chip.busy { background: color-mix(in srgb, var(--text) 30%, var(--surface)); color: var(--text); cursor: default; }
@media (max-width: 760px) {
  .modal:has(> .cd-sched-view) { height: calc(100dvh - 36px);
    padding: 12px 10px calc(12px + env(safe-area-inset-bottom)); }
  .cd-sched-view .sched-grid { --gap: 2px; }
  .cd-sched-view .sched-row, .cd-sched-view .sched-today-overlay {
    grid-template-columns: 34px repeat(7, minmax(0, 1fr)); }
  .cd-sched-view .sched-time { font-size: .54em; padding: 0 3px 0 0; }
  .cd-sched-view .sched-grid .sched-cell { border-radius: 4px; }
  .cd-sched-view .lesson-chip { padding: 0 2px; border-radius: 3px; }
}

/* Реквизиты для договора (профиль → «Оплата труда», modules/contracts/requisites.js) */
.req-acts { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 12px; }
.req-status { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; padding: 10px 12px;
  border-radius: 10px; background: var(--surface); color: var(--text-dim); font-size: .86em; line-height: 1.45; }
.req-status[hidden] { display: none; }
.req-status svg { width: 16px; height: 16px; color: var(--grade-good); flex: none; }
.req-status.is-wait .btn { margin-left: auto; }
.req-form .ctr-group:first-of-type { margin-top: 12px; }

/* ── Юридические документы и уведомление о cookie (views/legal-settings.js, legal.js) ── */
.lg-docs { display: flex; flex-direction: column; gap: 8px; margin: 12px 0 14px; }
.lg-doc { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-radius: var(--radius-sm);
  background: var(--bg); border: 1px solid var(--border); }
.lg-doc-ico { display: inline-flex; color: var(--accent-text); flex: none; }
.lg-doc-ico svg { width: 20px; height: 20px; }
.lg-doc-off .lg-doc-ico { color: var(--muted); }
.lg-doc-main { flex: 1; min-width: 0; }
.lg-doc-title { font-size: .92em; color: var(--text); }
.lg-doc-off .lg-doc-title { color: var(--text-dim); }
.lg-doc-sub { font-size: .78em; color: var(--muted); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lg-doc-sub a { color: var(--accent-text); text-decoration: none; }
.lg-doc-sub a:hover { text-decoration: underline; }
.lg-doc .btn { flex: none; display: inline-flex; align-items: center; gap: 6px; }
.lg-doc .btn .ico svg { width: 14px; height: 14px; }

.lg-links { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 8px; }
.lg-links-l { font-size: .78em; color: var(--muted); margin-right: 2px; }
.lg-chip { display: inline-flex; align-items: center; gap: 5px; padding: 5px 10px; border-radius: 999px;
  border: 1px solid var(--border-2); background: none; color: var(--text-dim); font: inherit; font-size: .78em; cursor: pointer; }
.lg-chip:hover { border-color: var(--accent-border); color: var(--text); }
.lg-chip .ico svg { width: 13px; height: 13px; }
.lg-sample-cap { font-size: .72em; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; margin: 14px 0 6px; }
.lg-sample { padding: 14px 16px; border-radius: var(--radius-sm); border: 1px dashed var(--border-2); }
.lg-sample-text { font-size: .8em; color: var(--muted); text-align: center; line-height: 1.7; white-space: pre-line; }
.lg-sample-text:empty::before { content: '—'; }
.lg-sample-text a { color: var(--text-dim); text-decoration: underline; text-underline-offset: 2px; }
.lg-sample-banner { display: flex; justify-content: center; padding: 18px 12px; }

/* Окно редактора */
.lg-head { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
.lg-slug { display: flex; align-items: center; background: var(--bg); border-radius: var(--radius-sm); overflow: hidden; }
.lg-slug-host { padding: 0 2px 0 14px; color: var(--muted); font-size: .9em; white-space: nowrap; }
.lg-slug .lg-slug-in { flex: 1; min-width: 60px; padding-left: 2px; }
.lg-tabs { margin-bottom: 12px; }
.lg-pane { min-height: 0; }
.lg-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin-bottom: 8px; }
.lg-tb { height: 32px; padding: 0 10px; border-radius: var(--radius-sm); border: 1px solid var(--border-2);
  background: none; color: var(--text-dim); font: inherit; font-size: .8em; cursor: pointer; }
.lg-tb:hover { border-color: var(--accent-border); color: var(--text); }
.lg-linkrow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 8px; }
.lg-linkrow .input { flex: 1; min-width: 200px; }
.lg-edit { min-height: 46vh; max-height: 56vh; overflow-y: auto; padding: 18px 20px; border-radius: var(--radius-sm);
  background: var(--bg); color: var(--text-dim); line-height: 1.65; font-size: .95em; outline: none; overflow-wrap: anywhere; }
.lg-edit:focus { box-shadow: 0 0 0 1px var(--accent-border); }
.lg-edit:empty::before { content: 'Вставьте или наберите текст документа'; color: var(--muted); }
.lg-edit h2 { font-size: 1.3em; color: var(--text); margin: 1.2em 0 .4em; }
.lg-edit h3 { font-size: 1.1em; color: var(--text); margin: 1.1em 0 .35em; }
.lg-edit h4 { font-size: 1em; color: var(--text); margin: 1em 0 .3em; }
.lg-edit > :first-child { margin-top: 0; }
.lg-edit p, .lg-edit div { margin: 0 0 .7em; }
.lg-edit ul, .lg-edit ol { margin: 0 0 .8em; padding-left: 1.5em; }
.lg-edit a { color: var(--accent-text); }
.lg-edit b, .lg-edit strong { color: var(--text); }
.lg-edit blockquote { margin: 0 0 .8em; padding-left: 1em; border-left: 3px solid var(--border-2); }
.lg-edit table { border-collapse: collapse; margin: 0 0 1em; }
.lg-edit th, .lg-edit td { border: 1px solid var(--border-2); padding: 4px 8px; }
.textarea.lg-code { min-height: 50vh; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .8em; line-height: 1.5; }
.lg-devices { margin-bottom: 10px; }
.lg-devices .seg-btn { display: inline-flex; align-items: center; gap: 6px; }
.lg-devices .ico svg { width: 15px; height: 15px; }
.lg-stage { height: 58vh; overflow: hidden; padding: 12px; border-radius: var(--radius-sm); background: var(--bg);
  display: flex; justify-content: center; }
.lg-frame { flex: none; border: 1px solid var(--border-2); border-radius: 8px; background: var(--surface);
  transform-origin: top center; }
.lg-frame-phone { border-radius: 22px; border-width: 6px; border-color: var(--border-2); }
.lg-actions { align-items: center; }
.lg-del { display: inline-flex; align-items: center; gap: 6px; color: var(--danger); }
.lg-del .ico svg { width: 15px; height: 15px; }
@media (max-width: 760px) {
  .lg-head { grid-template-columns: 1fr; }
  .lg-edit { min-height: 40vh; max-height: none; }
  .lg-stage { height: 62vh; }
}

/* Уведомление о cookie: маленькое окно в углу, ниже модалок (у .overlay z-index 100). */
.cookie-banner { position: fixed; left: 24px; bottom: 24px; z-index: 90; width: min(360px, calc(100vw - 32px));
  padding: 22px 22px 20px; border-radius: var(--radius); background: var(--surface); color: var(--text);
  border: 1px solid var(--border); box-shadow: var(--shadow); }
.cookie-preview { position: relative; left: auto; bottom: auto; z-index: auto; }
.cookie-text { margin: 0 22px 16px 0; font-size: .88em; line-height: 1.55; white-space: pre-line; }
.cookie-text a { color: var(--accent-text); text-decoration: none; }
.cookie-text a:hover { text-decoration: underline; }
.cookie-x { position: absolute; top: 12px; right: 12px; width: 28px; height: 28px; padding: 0; display: grid;
  place-items: center; border: none; background: none; color: var(--accent-text); cursor: pointer; border-radius: 8px; }
.cookie-x:hover { background: var(--surface-2); }
.cookie-x svg { width: 18px; height: 18px; }
.cookie-ok { text-transform: uppercase; letter-spacing: .5px; padding-left: 26px; padding-right: 26px; }
@media (max-width: 760px) {
  .cookie-banner:not(.cookie-preview) { left: 16px; right: 16px; width: auto;
    bottom: calc(16px + env(safe-area-inset-bottom)); }
}


/* ── Качество преподавания (views/teaching.js, app/modules/teaching) ──────────
   Только токены темы: светлая тема работает без своих оверрайдов. Цвет вердикта и
   баллов — --grade-good / --grade-mid / --grade-low, тревога — --danger. */
.tq-pill { font: inherit; font-size: .62em; line-height: 1.2; padding: 2px 9px 2px 7px; border-radius: 999px;
  border: 1px solid var(--border); background: transparent; color: var(--text-dim); cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; flex: 0 0 auto; }
.tq-pill::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--muted); }
.tq-pill:hover { color: var(--text); border-color: var(--muted); }
.tq-pill.tq-v-good::before { background: var(--grade-good); }
.tq-pill.tq-v-ok::before { background: var(--grade-good); opacity: .55; }
.tq-pill.tq-v-warn::before { background: var(--grade-mid); }
.tq-pill.tq-v-bad { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, transparent); }
.tq-pill.tq-v-bad::before { background: var(--danger); }

.modal.tq-modal { max-width: 1120px; padding: 26px 28px 22px; }
.tq { display: flex; flex-direction: column; gap: 16px; font-size: .95em; }
.tq-muted { color: var(--muted); }
.tq-head { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding-right: 30px; }
.tq-av { width: 56px; height: 56px; font-size: 1.2em; flex: 0 0 auto; }
.tq-who { flex: 1 1 260px; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.tq-eyebrow { font-size: .74em; text-transform: uppercase; letter-spacing: .8px; color: var(--muted); }
.tq-name { margin: 0; font-size: 1.45em; font-weight: 700; }
.tq-period { display: inline-flex; gap: 2px; padding: 3px; background: var(--surface-2); border-radius: 10px; }
.tq-period-btn { font: inherit; font-size: .82em; border: 0; background: transparent; color: var(--text-dim);
  padding: 6px 12px; border-radius: 8px; cursor: pointer; }
.tq-period-btn.active { background: var(--surface); color: var(--text); box-shadow: 0 0 0 1px var(--border); }

.tq-verdict { border-radius: var(--radius); padding: 16px 18px; border: 1px solid var(--border);
  background: var(--surface-2); display: flex; flex-direction: column; gap: 10px; }
.tq-verdict-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.tq-verdict-cap { font-size: .74em; color: var(--muted); text-transform: uppercase; letter-spacing: .8px; }
.tq-verdict-label { font-size: 1.45em; font-weight: 800; }
.tq-v-bad.tq-verdict { border-color: color-mix(in srgb, var(--danger) 50%, transparent);
  background: color-mix(in srgb, var(--danger) 8%, var(--surface-2)); }
.tq-v-bad .tq-verdict-label { color: var(--danger); }
.tq-v-warn.tq-verdict { border-color: color-mix(in srgb, var(--grade-mid) 50%, transparent);
  background: color-mix(in srgb, var(--grade-mid) 8%, var(--surface-2)); }
.tq-v-warn .tq-verdict-label { color: var(--grade-mid); }
.tq-v-good.tq-verdict { border-color: color-mix(in srgb, var(--grade-good) 50%, transparent);
  background: color-mix(in srgb, var(--grade-good) 8%, var(--surface-2)); }
.tq-v-good .tq-verdict-label, .tq-v-ok .tq-verdict-label { color: var(--grade-good); }
.tq-v-na .tq-verdict-label { color: var(--text-dim); }
.tq-reasons { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 5px; }
.tq-reason { display: flex; gap: 9px; align-items: baseline; font-size: .9em; }
.tq-reason::before { content: ''; flex: 0 0 7px; height: 7px; border-radius: 50%; transform: translateY(-1px); }
.tq-reason.red::before { background: var(--danger); }
.tq-reason.orange::before { background: var(--grade-mid); }
.tq-reason.green::before { background: var(--grade-good); }

.tq-card { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.tq-card-title, .tq-h { margin: 0; font-size: .92em; font-weight: 700; }
.tq-h { margin-top: 4px; font-size: 1.02em; }
.tq-row3 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr) minmax(0, 1fr); gap: 12px; }
.tq-row2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.tq-big { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.tq-big-val { font-size: 2.6em; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }
.tq-big.sm .tq-big-val { font-size: 1.75em; }
.tq-big-of { color: var(--muted); font-size: 1.05em; }
.tq-big-val.good, .tq-chip.good { color: var(--grade-good); }
.tq-big-val.mid, .tq-chip.mid { color: var(--grade-mid); }
.tq-big-val.low, .tq-chip.low { color: var(--grade-low); }
.tq-big-val.na { color: var(--muted); font-size: 1.6em; }
.tq-delta { font-size: .8em; font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; }
.tq-delta.up { color: var(--grade-good); }
.tq-delta.down { color: var(--grade-low); }
.tq-n { font-size: .78em; margin-left: auto; }
.tq-scale { display: grid; grid-template-columns: repeat(5, 1fr); gap: 4px; }
.tq-scale-step { text-align: center; font-size: .72em; color: var(--muted); padding-top: 7px;
  border-top: 3px solid var(--border); }
.tq-scale-step:nth-child(-n+2) { border-top-color: color-mix(in srgb, var(--grade-low) 55%, transparent); }
.tq-scale-step:nth-child(3) { border-top-color: color-mix(in srgb, var(--grade-mid) 55%, transparent); }
.tq-scale-step:nth-child(n+4) { border-top-color: color-mix(in srgb, var(--grade-good) 55%, transparent); }
.tq-scale-step.on { color: var(--text); font-weight: 700; border-top-width: 5px; }
.tq-cov { font-size: .8em; color: var(--text-dim); display: flex; flex-direction: column; gap: 3px; }
.tq-cov-warn { color: var(--grade-mid); }

.tq-radar svg { display: block; width: 100%; height: auto; max-height: 250px; }
.tq-r-grid { fill: none; stroke: var(--border); stroke-width: 1; }
.tq-r-cur { fill: color-mix(in srgb, var(--accent) 18%, transparent); stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; }
.tq-r-prev { fill: none; stroke: var(--muted); stroke-width: 1.3; stroke-dasharray: 4 3; }
.tq-r-dot { fill: var(--accent); }
.tq-r-label { fill: var(--text-dim); font-size: 10.5px; }
.tq-r-label.na { fill: var(--muted); }
.tq-legend { display: flex; gap: 14px; justify-content: center; font-size: .74em; color: var(--muted); }
.tq-lg::before { content: ''; display: inline-block; width: 16px; height: 0; margin-right: 6px; vertical-align: middle;
  border-top: 2px solid var(--accent); }
.tq-lg.prev::before { border-top: 1.5px dashed var(--muted); }

.tq-dist { display: flex; flex-direction: column; gap: 4px; }
.tq-dist-row { display: flex; justify-content: space-between; font-size: .8em; color: var(--text-dim); }
.tq-bar { height: 7px; border-radius: 4px; background: var(--border); overflow: hidden; }
.tq-bar-fill { display: block; height: 100%; border-radius: 4px; background: var(--muted); }
.tq-bar-fill.s5, .tq-bar-fill.s4 { background: var(--grade-good); }
.tq-bar-fill.s3 { background: var(--grade-mid); }
.tq-bar-fill.s2, .tq-bar-fill.s1 { background: var(--grade-low); }

.tq-crits { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 12px; }
.tq-crit { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 13px 15px; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.tq-crit-head { display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
.tq-crit-title { font-weight: 700; font-size: .9em; }
.tq-weight { font-size: .7em; color: var(--muted); }
.tq-level { font-size: .8em; color: var(--text-dim); line-height: 1.35; }
.tq-level::first-letter { text-transform: uppercase; }
.tq-note { font-size: .84em; line-height: 1.4; }
.tq-ex { display: flex; flex-direction: column; gap: 4px; padding-left: 10px; border-left: 2px solid var(--border); }
.tq-quote { font-size: .8em; color: var(--text-dim); font-style: italic; line-height: 1.4; overflow-wrap: anywhere; }
.tq-ex-meta { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; font-size: .74em; color: var(--muted); }
.tq-link { font: inherit; border: 0; background: none; padding: 0; color: var(--accent-text, var(--accent));
  cursor: pointer; font-size: .95em; }
.tq-link:hover { text-decoration: underline; }

.tq-item { display: flex; flex-direction: column; gap: 6px; padding-bottom: 10px; border-bottom: 1px solid var(--border); }
.tq-item:last-child { border-bottom: 0; padding-bottom: 0; }
.tq-item-head { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; font-size: .9em; }
.tq-chip { font-weight: 700; font-variant-numeric: tabular-nums; font-size: .9em; }
.tq-chip.na { color: var(--muted); }
.tq-tag { font-size: .7em; padding: 1px 7px; border-radius: 999px; border: 1px solid var(--border); color: var(--text-dim); }
.tq-tag.alert { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, transparent); }
.tq-tag.warn { color: var(--grade-mid); border-color: color-mix(in srgb, var(--grade-mid) 45%, transparent); }
.tq-tag.muted { color: var(--muted); }
.tq-strong { border-top: 3px solid color-mix(in srgb, var(--grade-good) 60%, transparent); }
.tq-grow { border-top: 3px solid color-mix(in srgb, var(--grade-mid) 60%, transparent); }
.tq-flags { border-top: 3px solid color-mix(in srgb, var(--danger) 55%, transparent); }

.tq-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.tq-tile { padding: 10px 12px; border-radius: var(--radius-sm); background: var(--surface); border: 1px solid var(--border); }
.tq-tile-val { font-size: 1.3em; font-weight: 800; font-variant-numeric: tabular-nums; }
.tq-tile.bad .tq-tile-val { color: var(--danger); }
.tq-tile-label { font-size: .78em; color: var(--text-dim); margin-top: 2px; }
.tq-tile-sub { font-size: .7em; color: var(--muted); margin-top: 2px; }
.tq-lost { display: flex; flex-direction: column; gap: 4px; font-size: .84em; }
.tq-lost-row { display: flex; gap: 10px; flex-wrap: wrap; }

.tq-les { display: grid; grid-template-columns: 86px minmax(90px, 160px) 42px minmax(0, 1fr) auto; gap: 10px;
  align-items: baseline; font-size: .85em; padding: 6px 0; border-bottom: 1px solid var(--border); }
.tq-les:last-child { border-bottom: 0; }
.tq-les-date { color: var(--muted); font-variant-numeric: tabular-nums; }
.tq-les-who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tq-les-goal { display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap; color: var(--text-dim); min-width: 0; }
.tq-les-act { display: flex; gap: 12px; white-space: nowrap; }
.tq-foot { margin: 0; font-size: .78em; color: var(--muted); line-height: 1.45; }
.tq-fb { display: flex; flex-direction: column; gap: 12px; }
.tq-fb-text { width: 100%; box-sizing: border-box; font: inherit; line-height: 1.5; resize: vertical; }
.tq-fb-btns { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }

@media (max-width: 900px) {
  .tq-row3 { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .tq-row3 > :nth-child(2) { grid-column: 1 / -1; order: 3; }
}
@media (max-width: 640px) {
  .modal.tq-modal { padding: 18px 16px; }
  .tq-row3, .tq-row2 { grid-template-columns: minmax(0, 1fr); }
  .tq-row3 > :nth-child(2) { grid-column: auto; }
  .tq-les { grid-template-columns: 74px minmax(0, 1fr) 36px; }
  .tq-les-goal { grid-column: 1 / -1; }
  .tq-les-act { grid-column: 1 / -1; }
}
