/* style-04-group-overview.css — часть бывшего style.css (распил 22.10.2026): монитор группы: панели дней, плашка-фильтр, каретка дней.
   Файлы style-NN-*.css — ОДИН каскад: index.html подключает их подряд и по номерам, порядок значим
   (правило ниже по каскаду перебивает равное выше). Стережёт tools/check.js. */
  /* GROUP OVERVIEW — two-pane screen (docs/teacher-overview-screen.md) */
  .ostrip{margin:0 0 4px}
  /* ЗАНАВЕС ПАНЕЛЕЙ (blox52 #12). Тап по дню каретки: обе панели глушатся прозрачностью и
     перестают ловить нажатия, СТАРОЕ содержимое остаётся на месте (никаких «Загрузка…» —
     правило «экран не прыгает»); render() после ответа снимает класс. Мгновенный ответ из
     кэша занавес НЕ мигает: transition 160мс сам гасит короткую вспышку — класс, снятый
     через пару мс, успевает увести opacity лишь на доли процента. Поэтому transition тут
     НЕ снимается и под reduced-motion: это не движение, а противомигательный механизм
     (тот же довод, что у спиннера .is-loading). pointer-events выключается мгновенно —
     ему и надо: панель в занавесе не должна ловить второй тап ни единого кадра. */
  #gpanes{transition:opacity .16s var(--ease)}
  #gpanes.veil{opacity:.6;pointer-events:none}
  /* blox84 #8 — СВЕТЛАЯ ПОЛОСА ПОД ЛИСТАМИ (владелец): от середины зазора между кареткой и рамкой
     листа и ниже всё, что не занято листом ДЗ, залито светлым тоном кнопок (--card). Зазор был 10px
     (margin-top) — теперь 5 воздуха над полосой + 5 полосы над рамкой, то есть линия ровно посередине,
     и лист не сдвинулся ни на пиксель. Во всю ширину экрана, за поля страницы: тень-разлив по бокам,
     обрезанная clip-path по верху и низу, — места в раскладке она не занимает, горизонтальной
     прокрутки не даёт. Сам лист внутри своей рамки остаётся цвета стола (.opane.pub ниже): плашки
     тасков бумажные, на светлой заливке они бы растворились (blox51 #2). */
  .otwo{display:flex;align-items:stretch;gap:0;margin-top:5px;padding:5px 0 12px;
    background:var(--card);box-shadow:0 0 0 100vmax var(--card);clip-path:inset(0 -100vmax)}
  .oarr{flex:0 0 auto;align-self:center;border:1px solid var(--line-soft);background:var(--card);border-radius:var(--r-sm);
    width:38px;height:54px;font-size:var(--ic-md);cursor:pointer;color:var(--ink)}
  .oarr:disabled{background:var(--fill-soft);border-color:var(--line-soft);
    color:var(--faint);cursor:default}
  /* blox51 #2 — панель занятия БОЛЬШЕ НЕ ЗАЛИВАЕТСЯ. Уровня ровно два: стол
     (--bg) и бумага (плашки внутри). Белая заливка делала третий, почти такой
     же светлый слой, и плашка на нём читалась как вмятина, а не как листок.
     Границу панели оставляем: на мониторе «прошло» и «предстоит» стоят рядом
     двумя колонками, и рамка тут несёт смысл — её растворять нельзя. */
  .opane{--pane-bw:1px;flex:1 1 0;min-width:0;border:var(--pane-bw) solid var(--line);border-radius:var(--r-md);
    padding:calc(13px - var(--pane-bw));margin:0 6px;background:transparent}
  /* blox84 #4 — ЛИСТ ДЗ В ТОЛСТОЙ РАМКЕ ЦВЕТА СХЕМЫ (владелец): выданный — сплошной, черновик и
     правка выданного — пунктиром. Толстая рамка, а не волосок (с blox84 #9 — 2px): лист — главный предмет панели, и на
     мониторе он обязан отличаться от пустого дня и «прошло без ДЗ» с одного взгляда. Цвет — --anchor,
     тот же, что у первичной кнопки схемы (Maya / графит / баклажан). ГЕОМЕТРИЯ НЕ ПРЫГАЕТ: отступ
     внутри — 13px минус толщина рамки, так что содержимое стоит на той же оси у любой панели и в
     любом состоянии (пустой день → черновик → выдано). Красная рамка «ДЗ не выдано»
     (.opane.next.nag ниже) сильнее по селектору — она перекрашивает и толстую. */
  /* blox84 #9 — рамка тоньше: 3px → 2px. Владелец просил «вдвое», но 1.5px не существует: Chromium
     округляет толщину рамки вниз до целого CSS-пикселя (замер, Chromium 149 — 1.5px рисуется 1px при
     любой плотности экрана), и лист сравнялся бы с волоском пустой панели. 2px — ступень лестницы
     толщин (tools/check.js: 1px волосок · 2px линия-сигнал) и одинакова во всех браузерах. */
  .opane.pub,.opane.draft,.opane.build.pubedit{--pane-bw:2px;border-color:var(--anchor)}
  .opane.pub{background:var(--bg)}   /* blox84 #8 — лист цвета стола посреди светлой полосы */
  /* шапка панели отбита той же гравированной линейкой; линия здесь по НИЖНЕЙ грани,
     поэтому блик уходит наружу (--rule-b), а не внутрь */
  /* СЕТКА 1fr · auto · 1fr, а не flex (blox53 #6). Средняя колонка — тумблер Notes/Tasks: раньше
     он стоял абсолютом ПОВЕРХ шапки и своей непрозрачной подложкой закрывал то, на что наезжал.
     Подложки у него больше нет (два слова через слеш), и наложение стало видно: замер по всем
     урокам группы дал 19 наездов на 375px — на длинную дату («12 сентября») и на пару
     «выдано · сдач ещё нет» + Edit. В потоке наложение невозможно в принципе: колонки делят
     ширину, содержимое переносится. Боковые 1fr равны, поэтому тумблер стоит РОВНО по центру
     панели в обоих режимах и не ездит (assert «тумблер на месте ±2px»); align-items:start
     держит его Y на месте, даже если соседняя колонка выросла на строку. */
  .opane-h{display:grid;grid-template-columns:1fr auto 1fr;align-items:start;gap:6px;margin-bottom:8px;
    border-bottom:1px solid var(--line-soft);padding-bottom:6px;box-shadow:var(--rule-b)}
  .opane-d{font-weight:800;min-width:0;align-self:center}
  /* правая часть шапки: кнопка Edit (если есть) + статус «прошло/предстоит» на одной строке.
     Edit стоит ЛЕВЕЕ «предстоит»; отдельной строки .opane-tools больше нет (blox28 #1). */
  .opane-hr{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap;
    min-width:0;grid-column:3;align-self:center}
  /* ТЕЛЕФОН: средней колонки на 343px не хватает — дата и «выдано · сдач ещё нет» ужимались
     в 99px и рвались на пять строк. Тут тумблер уходит на СВОЮ строку над шапкой (flex-обёртка:
     order:-1 + basis 100%), и дата с Edit получают всю ширину. Y тумблера от режима не зависит,
     так что «не ездит при переключении» держится и здесь. */
  @media(max-width:760px){
    .opane-h{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}
    .opane-h{align-items:flex-start}
    .opane-h .opane-d{flex:0 0 auto}
    .opane-h .opane-hr{flex:1 1 0}   /* остаток строки — его и меряет head-fit.js */
    .opane-h .onotes{order:-1;flex:1 0 100%;width:auto;max-width:none}
  }
  /* blox84 #7 — ШАПКА ЛИСТА: плашка-дата слева, кнопки ВСЕГДА одной строкой справа. Перенос кнопок
     запрещён (nowrap), и если словами они не влезают, head-fit.js ставит .icons — подписи прячутся,
     остаются значки (полное слово — в title). Отступы у всех кнопок шапки одни, поэтому Attendance,
     Edit и Duplicate одного роста и стоят на одной линии у обоих листов. */
  .opane-hr{flex-wrap:nowrap}
  /* тумблер Notes/Tasks есть только у левого листа; у шапки без него средней колонке держать нечего —
     её место отдаём кнопкам, иначе три кнопки сжимались до значков при пустой половине шапки */
  .opane-h:not(:has(.onotes)){grid-template-columns:auto 0 1fr}
  .opane-hr > *{flex:0 0 auto}
  .opane-hr .btn-ghost{padding:5px 12px;font-size:var(--fs-sm)}
  .opane-hr.icons .btn-ghost{padding:5px 10px}
  .opane-hr.icons .obl{display:none}
  .opane-hr .oico{display:none}
  .opane-hr.icons .oico{display:inline}   /* «Все на уроке» значком: 👥✓, а не голые человечки, как у Attendance */
  /* ПЛАШКА-ДАТА — статус по времени урока: прошло (серая, тихая) · сегодня (залита цветом схемы) ·
     предстоит (бумага в рамке цвета схемы). Слово статуса внутри плашки, тоньше даты. */
  /* ОДНА ЛИНИЯ: плашка ростом 30px стоит по центру 44-пиксельной строки кнопок (отступ 7px сверху),
     а подпись «выдано · сдач ещё нет» ложится ПОД неё — и не тянет плашку вверх, как раньше тянула дату */
  .opane-h .opane-d,.opane-h .opane-hr{align-self:start}
  .odate{box-sizing:border-box;height:30px;margin-top:7px;
    display:inline-flex;align-items:center;white-space:nowrap;padding:0 11px;
    border:1px solid transparent;border-radius:var(--r-pill);font-size:var(--fs-sm);font-weight:800;line-height:1.2}
  .odate .odw{font-weight:600;white-space:pre}
  .odate.past{background:var(--fill-soft);border-color:var(--line);color:var(--muted-strong)}
  .odate.today{background:var(--anchor);border-color:var(--anchor);color:#fff}
  .odate.up{background:var(--card);border-color:var(--anchor);color:var(--anchor-ink)}   /* текст — чернилами якоря: ночью сам якорь (заливка) читался 2.9:1 */
  .opane-d .opubnote{margin:2px 0 0 2px;line-height:16px}
  /* blox52 #14 — подпись состояния выданного листа («выдано · сдач ещё нет»): вторая мелкая
     строка под датой ВНУТРИ шапки. У шапки min-height:44px, а дата+подпись вместе ~34px —
     высота шапки не меняется и плашки под ней не едут. Тон --muted: факт без срочности. */
  .opubnote{display:block;font-size:var(--fs-xs);font-weight:600;color:var(--muted)}
  /* blox52 #14 — полоска-подтверждение успешной выдачи: акцент СВОЕЙ схемы по верхней кромке
     панели, absolute поверх кромки (содержимое не сдвигает), ~1.2с и гаснет. Базовое состояние
     ::before — opacity:0, анимация однократная: по её концу полоска исчезает сама, снимать
     класс не нужно (следующий рендер его и так не поставит — pubFlashLid одноразовый). */
  .opane.pubflash::before{content:"";position:absolute;left:calc(-1 * var(--pane-bw));right:calc(-1 * var(--pane-bw));
    top:calc(-1 * var(--pane-bw));height:4px;
    background:var(--grad-primary);border-radius:var(--r-md) var(--r-md) 0 0;
    opacity:0;animation:pubflash 1.2s ease-out;pointer-events:none}
  @keyframes pubflash{0%{opacity:0}12%{opacity:1}70%{opacity:1}100%{opacity:0}}
  /* попросили систему не анимировать — движения не остаётся: полоску не показываем вовсе
     (факт выдачи и так виден — панель сменилась на лист, в шапке подпись состояния) */
  @media (prefers-reduced-motion:reduce){ .opane.pubflash::before{animation:none} }
  /* blox51 #6: у выданного листа БОЛЬШЕ НЕТ салатовой рамки — он берёт рядовой волосок --line.
     Зелёный в проекте значит ровно одно: «сделано / проверено / делать нечего». Выданный лист
     этого не значит: рамка говорила «порядок» на панели, внутри которой «Не сделали: Emma,
     Fred, Lena, Rita, Sonya, Steve». Плюс «выдано» — состояние ПО УМОЛЧАНИЮ, у девяти панелей
     из десяти, и красить умолчание нечем: цвет остаётся там, где он чего-то требует —
     сиреневый черновик, alert «ДЗ не выдано», чернильный пунктир «ближайшее».
     ПЕРЕСМОТРЕНО в blox84 #4: владелец вернул листу рамку — толстую, цвета схемы (правило у .opane). */
  /* blox51 #4: тот же ПОДНОС, что и у .opane.build.draft ниже — это ОДНА панель в двух
     режимах (черновик закрыт / открыт в конструкторе). Пока заливки различались, панель
     меняла цвет от одного клика; теперь состояние держит только рамка. */
  .opane.draft{background:var(--surface)}
  .opane.empty{border-style:dashed}
  /* blox83 #5 — чёрный пунктир ближайшего предстоящего снят: выданный лист в пунктире читался
     черновиком (владелец). Класс .next остался — на нём держится рамка «ДЗ не выдано» ниже. */
  /* ДЗ ещё не выдано — alert, как nag на каретке. ВТОРОЙ СЕЛЕКТОР — не украшение (blox52 #9):
     у панели с открытым конструктором черновик красит рамку правилом .opane.build.draft, и
     сила у него ровно та же (три класса), а записано оно НИЖЕ — красная рамка ближайшего
     занятия молча становилась сиреневой ровно в тот момент, когда учитель открывал черновик.
     Приоритет разведён явно, но БЕЗ !important: хватает лишнего класса в селекторе. */
  /* blox52 #11: «надо выдать ДЗ» — ОЧЕРЕДЬ РАБОТЫ, не пожар. Рамка ближайшего без ДЗ носит
     якорь схемы (тот же голос, что у счётчика «к проверке»); красный остаётся настоящим
     тревогам (провал/просрочка — реестр RED_OK в tools/check.js). Пара селекторов и их
     порядок — починка blox52 #9 (черновик не смеет перекрасить рамку), НЕ трогать. */
  .opane.next.nag,
  .opane.next.nag.build{border-color:var(--alert)}
  /* ПАНЕЛЬ-КРАЙ — пустая линованная страница журнала (blox52 #15). Была коробкой во всю
     высоту соседа с одной строкой по центру — читалось «сломалось», а не «ещё не заполнено»
     (пустой понедельник — самое частое состояние учителя). Теперь: бледные горизонтальные
     линии (чернила 4%, шаг ровно 28px — целые px против муара на дробном dpr), подпись в
     верхнем левом углу тоном --faint, высота своя (~200px), а не растянутая по соседу.
     align-self:flex-start снимает stretch только на широкой раскладке (в телефонном столбике
     он вернул бы себе ширину — там медиа ниже ставит auto). Высота меняется ТОЛЬКО вместе с
     содержимым при перерисовке пары — «экран не прыгает» соблюдён. Класс только у ПУСТОЙ
     панели: у панелей с плашками линий нет и просвечивать им не под чем. */
  .opane.edge{color:var(--faint);background:var(--surface);align-self:flex-start;min-height:200px;
    background-image:repeating-linear-gradient(to bottom, transparent 0 27px, var(--page-rule) 27px 28px)}
  .opane.edge .omut{color:var(--faint)}
  .opane.past{background:var(--surface);border-color:var(--line-soft)}   /* прошло без ДЗ — приглушённая, но с шапкой сверху */
  .opane-mid{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;
    padding:26px 8px;text-align:center}
  .obig{font-size:var(--fs-md);padding:12px 18px}
  .odraft-t{font-weight:800;color:var(--draft-ink)}
  /* blox51 #3 (заход 3) снял пульсацию (три тревоги об одном факте); blox52 #11 забрал у
     этой панели и красный: «ближайшее занятие без ДЗ» — очередь учителя, а не провал, и
     говорит она якорем схемы, как рамка .nag выше. Заливки нет вовсе — рамка + большая
     кнопка «Установить ДЗ» (акцент схемы) и так самое громкое на панели, а красная
     плоскость была одной из «семи заливок пожара» из диагноза судей. */
  .opane.empty.urgent{border-color:var(--alert);
    animation:nagpulse 2.6s ease-in-out infinite}
  /* ПУЛЬСАЦИЯ ближайшего занятия БЕЗ ЛИСТА (вернулась в blox59 #4). Старая версия (shimmerGold)
     мигала в ЗАШИТЫЙ #fff — ярче нашей бумаги в любой схеме и просто вспышка белым ночью;
     поэтому её и сняли вместе с цветом. Здесь обе опоры схемные: 0/100% — прозрачность (у
     .opane фон прозрачный, так что это и есть «как у стола»), 50% — --alert-tint своей схемы.
     Пульсирует ТОЛЬКО пустой лист: у черновика панель обычно уже открыта в конструкторе, и
     дышащий фон под тасками — шум; ему достаточно красной рамки .opane.next.nag.
     Движение гасится по prefers-reduced-motion, но факт остаётся: вместо анимации — статичный
     тинт, иначе выключенное движение отбирало бы у листа половину сигнала. */
  @keyframes nagpulse{0%,100%{background-color:transparent}50%{background-color:var(--alert-tint)}}
  @media (prefers-reduced-motion:reduce){
    .opane.empty.urgent{animation:none;background-color:var(--alert-tint)} }
  .orow{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px}
  .ochip{padding:6px 9px}
  .oents{display:flex;flex-direction:column;gap:7px}
  /* blox53 #19 — text-align задан ЯВНО, и это не украшение. Плашка — карточка, а не надпись: её
     заголовок и подпись читаются от левого края, как у любого таска выданного листа. Но панель
     ПУСТОГО дня носит класс `.opane.empty`, а `empty` в проекте занят общим помощником «Загрузка…»
     (`.empty{text-align:center}`, строка выше) — имена совпали, и всё внутри такой панели молча
     центровалось. Пока внутри стоял только .opane-mid (центрированный сам по себе), этого не было
     видно; плашка встала туда — и текст поехал в середину. Свой text-align делает плашку
     независимой от того, в какую коробку её смонтировали. */
  .oent{--rail-x:5px;--rail-y:7px;--rail-w:6px;text-align:left;
    background:var(--task-plaque);border-radius:var(--r-sm);padding:8px 10px 9px 19px;font-size:var(--fs-sm)}
  /* blox73 #10 — обложка книги на плашке монитора: полоса над заголовком, у одной плашки листа
     (coverHolders, format.js — то же правило, что у ребёнка) */
  .oent-art{display:block;width:100%;aspect-ratio:3/1;object-fit:cover;object-position:50% 0;
    border-radius:var(--r-xs);margin:0 0 6px;background:#0b0a0d}
  /* ПЛАШКА-ФИЛЬТР (blox52 #12). Плашка задания выглядела карточкой, но была мертва — при
     живых чипах учеников рядом. Теперь у плашки СО списком «не сделали» (класс ставит
     oEntityCard) есть наведение (та же тень, что у живых карточек) и клик: подсветить чипы
     из своего списка (.rchip.hl выше). Плашка БЕЗ списка (все сделали / плейлист / снят)
     класса не получает — и не притворяется: cursor остаётся обычным, hover-тени нет;
     граница «жива/мертва» честная. */
  /* blox59 #13 — зажатие плашки держит подсветку, и долгое нажатие на телефоне не должно
     параллельно запускать родное «выделить слово» (Android Chrome и iOS Safari одинаково) и
     меню «Копировать». user-select в двух формах — Safari без префикса понимает его лишь с 16.4;
     touch-callout есть только у Safari, остальные его пропускают. Свойства наследуются: ссылка
     в заголовке тоже не выделяется, но кликается как прежде. */
  .oent-live{cursor:pointer;transition:box-shadow var(--dur-state) var(--ease-standard),transform var(--dur-release) var(--ease-spring);
    -webkit-user-select:none;user-select:none;-webkit-touch-callout:none}
  @media (hover:hover){.oent-live:hover{box-shadow:var(--sh-2)}}
  /* blox70 #2 — тап по ЛЮБОЙ живой плашке открывает «сдачи по таску» (task-review.js), так что
     нажимаемой она выглядит и без списка «не сделали». Выделение текста гасим тем же рецептом. */
  .oent-open{cursor:pointer;transition:box-shadow var(--dur-state) var(--ease-standard),transform var(--dur-release) var(--ease-spring);
    -webkit-user-select:none;user-select:none;-webkit-touch-callout:none}
  @media (hover:hover){.oent-open:hover{box-shadow:var(--sh-2)}}
  /* аудит blox70 #2 (#12): плашка встаёт в Tab-цепочку, Enter/пробел открывают экран таска */
  .oent-open:focus-visible{outline:none;box-shadow:var(--ring)}
  /* ОТКЛИК НА НАЖАТИЕ (ночной редизайн 27.10, грамматика журнала redesign): плашка сухо
     вдавливается (--press-scale-lg, --dur-press-in) и пружиной встаёт обратно (--dur-release,
     --ease-spring). Зажатая плашка держит подсветку чипов — и остаётся вдавленной, пока держат:
     это одно и то же «держу». Нажатие на ссылку в заголовке или на имя файла плашку НЕ вдавливает —
     это свои кнопки (:active всплывает к предкам, поэтому :has их отсекает). transform только на
     время нажатия: в покое у плашки его нет. */
  .oent-open:active:not(:has(a:active,.ofile-a:active)),
  .oent-live:active:not(:has(a:active,.ofile-a:active)){transform:scale(var(--press-scale-lg));
    transition-duration:var(--dur-press-in);transition-timing-function:var(--ease-standard)}
  @media (prefers-reduced-motion:reduce){
    .oent-open:active:not(:has(a:active,.ofile-a:active)),
    .oent-live:active:not(:has(a:active,.ofile-a:active)){transform:none} }
  /* ОДИН ГРОМКИЙ ПРЕДМЕТ НА ЭКРАНЕ (ночной редизайн 27.10, агент 4). Громкий — лист ВЫБРАННОГО
     урока: у него толстая рамка цвета схемы (.opane.pub выше), его день обведён на каретке
     (.viewing), и его плашки лежат на столе с тенью листа (--sheet-1). Сосед по паре — контекст
     (предыдущий урок слева на ПК, ниже на телефоне; в разметке он ВСЕГДА первая панель .otwo):
     его плашки той же бумаги, но лежат вровень — только обрезной край (--sheet-edge), без тени.
     Тень возвращается под курсором — плашка соседа такая же живая, как у выбранного листа.
     Ничего не убрано и не сдвинуто: тень вне потока, геометрия плашек та же до пикселя. */
  .otwo > .opane:first-child .oent{box-shadow:var(--sheet-edge)}
  @media (hover:hover){
    .otwo > .opane:first-child .oent-open:hover,
    .otwo > .opane:first-child .oent-live:hover,
    .otwo > .opane:first-child .oent.pool-open:hover{box-shadow:var(--sheet-1)}
  }
  .oent.pool{color:var(--muted)}
  /* blox9 #7: у плашки плейлиста рельс радужный (как бар ученика), а не серый.
     blox53 #10 — тот же радужный рельс теперь и у БОЛЬШОЙ плашки playlist-группы: это одна и та же
     плашка в двух режимах группы, и «тиснение» у них обязано быть одно. */
  /* blox53 #16 — рельс радужный у ПЛЕЙЛИСТА, но не у контрольного файла: он вынесен в свою
     плашку (.plctrl) и обязан читаться отдельной сущностью, поэтому берёт обычный рельс. */
  .oent.pool::before,.oent.plbig:not(.plctrl)::before{background:var(--grad-playlist-v)}
  /* blox53 #7/#9 — плашка плейлиста кликабельна (дверь на экран «Плейлист») и должна выглядеть
     кликабельной В ПОКОЕ, а не только под курсором (правило 12 lingo-ui): курсор-рука + свой
     волосок по контуру; наведение поднимает тень, нажатие чуть вдавливает. */
  .oent.pool-open{cursor:pointer;border:1px solid var(--line-soft);
    transition:box-shadow var(--dur-state) var(--ease-standard),border-color var(--dur-state) var(--ease-standard),
      transform var(--dur-release) var(--ease-spring)}
  @media (hover:hover){.oent.pool-open:hover{box-shadow:var(--sh-2);border-color:var(--accent-deep)}}
  .oent.pool-open:active{box-shadow:none}
  /* та же грамматика нажатия, что у плашек тасков; шеврон вида — отдельная кнопка, его нажатие
     дверь не вдавливает (редизайн 27.10) */
  .oent.pool-open:active:not(:has(.pool-vw:active)){transform:scale(var(--press-scale-lg));
    transition-duration:var(--dur-press-in);transition-timing-function:var(--ease-standard)}
  @media (prefers-reduced-motion:reduce){.oent.pool-open:active:not(:has(.pool-vw:active)){transform:none}}
  .oent.pool.ptop{margin-bottom:9px}   /* плашка стоит НАД листом, отдельно от его карточек */
  /* blox53 #13 — ПУСТОЙ плейлист: та же плашка, но последней строкой панели, под всеми тасками. */
  .oent.pool.pbot{margin-top:9px}
  /* blox53 #14/#17 — плашка держит ОБЕ колонки разом, поэтому края берёт у разных соседей:
     ЛЕВЫЙ — у карточек тасков (карточка вне рамки темы сдвинута .oent.loose на 11px, чтобы
     совпасть с карточками ВНУТРИ рамки; плашка — прямой ребёнок панели, и без этого сдвига её
     левый край торчал левее всего листа), ПРАВЫЙ — у чипов учеников, то есть у самого края
     панели: колонка учеников идёт до внутренней границы, и правый отступ у плашки нулевой. */
  .opane>.oent.pool{margin-left:11px;margin-right:0}
  .oweek{margin-top:4px;font-size:var(--fs-xs);font-weight:700;color:var(--accent-dark)}
  /* blox53 #16 — контрольных файлов на уроке бывает несколько, и каждый идёт своей плашкой под
     наслушкой плейлиста. Панели .plonly, для которой писалось прежнее правило, больше нет
     (blox56 #2): плашки стоят внутри .plstats, а колонку им задаёт она. */
  .plstats{display:flex;flex-direction:column;align-items:stretch;gap:9px}
  /* blox56 #5 — СПИСОК «Из плейлиста» в окне таска: плоские строки-кнопки, одна на файл. Нарочно
     не плитки проводника: тут не ходят по папкам, тут один тап по имени — и файл выбран. */
  .pl-poolpick{display:flex;flex-direction:column;gap:4px;margin-top:6px;max-height:240px;overflow:auto}
  .pl-poolrow{display:flex;align-items:center;gap:8px;width:100%;text-align:left;
    padding:8px 10px;background:var(--card);border:1px solid var(--line-soft);
    border-radius:var(--r-xs);cursor:pointer;font-size:var(--fs-sm);font-weight:700;color:var(--ink)}
  @media (hover:hover){.pl-poolrow:hover{border-color:var(--accent-deep)}}
  .pl-poolrow .pl-nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .pl-poolrow svg{width:16px;height:16px}
  .plctrl-none{padding:2px 4px 4px}
  .oent.plbig{gap:6px}
  /* blox56 #2 — те же строки живут теперь ВНУТРИ обычной плашки плейлиста (развёрнутый вид), а не
     только в большой плашке бывшей playlist-группы: предок .plbig из селекторов убран. */
  .plb-sec{margin-top:9px;font-size:var(--fs-xs);font-weight:800;color:var(--muted);
    display:flex;align-items:center;gap:6px}
  .plb-sec svg{width:14px;height:14px;opacity:.75}
  .plb-sec.plb-ctrl{color:var(--accent-dark)}
  .plb-list{display:flex;flex-direction:column;gap:4px;margin-top:5px}
  /* blox56 #8 — ИМЯ И ЦИФРА ВСТРЕЧАЮТСЯ У СЕРЕДИНЫ. Было justify-content:space-between: имя
     прижато к левому краю, число — к правому, и на широкой панели между «Liza» и «7» лежала
     ладонь пустоты; глаз терял строку и читал чужое число. Делим плашку пополам (по сетке, не
     линией): имена прижаты к ПРАВОМУ краю левой половины, числа — к ЛЕВОМУ краю правой. Пара
     стоит вплотную к невидимой середине, и длина имени больше не растаскивает их. min-width:0 —
     чтобы длинное имя резалось многоточием, а не распирало свою колонку. */
  .plb-row{display:grid;grid-template-columns:1fr 1fr;align-items:center;gap:0 10px;
    padding:5px 10px;background:var(--card);border:1px solid var(--line-soft);border-radius:var(--r-xs)}
  .plb-name{min-width:0;justify-self:end;text-align:right;
    font-weight:700;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .plb-n{min-width:0;justify-self:start;text-align:left;
    font-weight:800;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .plb-row.zero .plb-n{color:var(--muted);font-weight:600}
  .plb-none{padding:6px 2px}
  /* blox56 #2 — ШЕВРОН ВИДА в углу плашки плейлиста. Не второе равное действие рядом с дверью, а
     настройка самой плашки, поэтому он мелкий, без подписи и лежит в углу; 28px — минимум, ниже
     которого палец мажет (полный 24px-квадрат внутри + воздух). Развёрнутый вид поворачивает тот
     же шеврон, отдельной иконки «вверх» для этого не нужно. */
  .oent.pool{position:relative}
  /* дверь плашки — кнопка, но выглядит текстом плашки (см. poolPlateHTML) */
  .pool-door{all:unset;display:inline-flex;align-items:center;gap:inherit;min-height:24px;cursor:pointer;font:inherit;color:inherit}   /* ≥24px — цель касания (axe target-size) */
  .pool-door:focus-visible{outline:2px solid var(--ring-c);outline-offset:2px;border-radius:var(--r-xs)}
  /* blox72 #4 — ЗОНА НАЖАТИЯ ≠ ЗНАЧОК. Было 28×28 в углу, а вся остальная плашка — дверь в «Плейлист»:
     учитель целился в стрелку, промахивался на палец и улетал с монитора. Теперь кнопка — полоса во
     всю высоту свёрнутой плашки у правого края (56×64: палец не промахнётся), а видимый значок —
     кружок 32px в её середине, чтобы в покое было видно, что это отдельная кнопка (lingo-ui §12).
     Высота фиксирована, а не до низа: в развёрнутом виде ниже лежит наслушка, её строки полоса
     перекрывать не должна. Поворачивается только значок — коробка прозрачная. */
  .pool-vw{position:absolute;top:0;right:0;width:56px;height:64px;display:grid;place-items:center;
    padding:0;border:0;border-radius:0 var(--r-sm) 0 0;background:transparent;color:var(--muted);cursor:pointer}
  .pool-vw svg{width:20px;height:20px;padding:6px;box-sizing:content-box;border-radius:50%;
    background:var(--fill-soft);transition:transform var(--t) var(--ease),color var(--t) var(--ease),background var(--t) var(--ease)}
  @media (hover:hover){.pool-vw:hover svg{background:var(--line-soft);color:var(--ink)}}
  .pool-vw:focus-visible{outline:none}
  .pool-vw:focus-visible svg{box-shadow:var(--ring)}
  .pool-vw.on{color:var(--ink)}
  .pool-vw.on svg{transform:rotate(180deg)}
  @media (prefers-reduced-motion:reduce){.pool-vw svg{transition:none}}
  .oent.pool .plstats{margin-top:8px}
  /* имя прикреплённого файла — в плашке Листа ДЗ и в карточке разбора ученика */
  .ofile{display:flex;align-items:center;gap:5px;margin-top:4px;font-size:var(--fs-xs);font-weight:600;
    color:var(--muted);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* blox59 #14 — имя файла таска у учителя (монитор, личка) — кнопка, файл открывается нашим
     плеером / листалкой PDF, как у ученика. Сброс кнопки как у .pool-door; выглядит ссылкой. В
     строке .ofile она flex-ребёнок и сама режет длинное имя многоточием (иначе обрезал бы
     контейнер, а подчёркивание уезжало бы за край); в заголовке .oent-t.big/.srtitle — inline,
     наследует перенос заголовка. */
  .ofile-a{all:unset;display:inline;cursor:pointer;font:inherit;color:var(--accent-dark);
    text-decoration:underline;text-underline-offset:2px}
  .ofile-a:focus-visible{outline:2px solid var(--ring-c);outline-offset:2px;border-radius:var(--r-xs)}
  .ofile .ofile-a{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* blox80 #3 — «+ N files» у многофайлового задания: не режется, имя первого файла уступает место */
  .omore{display:inline-flex;align-items:center;gap:3px;flex:0 0 auto;white-space:nowrap;color:var(--muted);font-weight:700}
  .ofile .omore{margin-left:2px}
  /* сам текст задания / ссылка / комментарий — в плашке Листа ДЗ и в карточке разбора ученика.
     Тип таска говорит ЧТО делать, а это — что именно задано (blox14 #2). Перенос по словам:
     длинная ссылка не должна выпихивать плашку за правый край. */
  /* blox28 #16: текст задания от учителя (body → .otext) и комментарий (.ocomment) говорят на ОДНОМ
     визуальном языке — иконка-облачко слева + мягкая нейтральная подложка «это написал учитель».
     Раньше body шёл голым текстом без иконки, из-за чего у таска do текст не читался как «текст от
     учителя». Подложка (var(--fill-soft)) — мягкое выделение, а НЕ синий-подчёркнутый (тот читается
     как ссылка и кликается, а текст задания не кликабелен). Ссылка (.olink) — отдельно, она цветная
     и КЛИКАБЕЛЬНА по-настоящему. */
  .otext,.ocomment{display:flex;align-items:flex-start;gap:6px;margin-top:5px;
    padding:6px 9px;border-radius:var(--r-sm);background:var(--fill-soft);min-width:0;overflow-wrap:anywhere}
  .otext{font-size:var(--fs-sm);line-height:1.45;color:var(--ink);white-space:pre-wrap}
  .ocomment{font-size:var(--fs-xs);line-height:1.4;color:var(--muted);white-space:pre-wrap}
  .otext svg,.ocomment svg{flex:0 0 auto;margin-top:2px}
  .olink{display:flex;align-items:flex-start;gap:5px;margin-top:4px;font-size:var(--fs-xs);
    color:var(--muted);min-width:0;overflow-wrap:anywhere}
  .olink a{color:var(--accent-dark);font-weight:600}
  .oent-h{display:flex;align-items:center;gap:6px}
  .oent-t{font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* заголовок карточки выданного таска = САМО ДЗ: оно длиннее типа, поэтому переносится (до 3
     строк) вместо обрезки в одну; тип таска живёт ниже мелкой подписью (.oent-sub). */
  .oent-h.htop{align-items:flex-start}
  .oent-h.htop>.eico,.oent-h.htop>.omut{flex:0 0 auto;margin-top:1px}
  /* счётчик «6/10» в столбец (blox52 #15): фиксированный минимум ширины + выравнивание вправо —
     все «x/10» панели встают в одну вертикаль (цифры табличные глобально, theme.css). */
  .oent-h.htop>.omut{min-width:5.5ch;text-align:right}
  /* pre-line, а не normal: тот же сборный таск стоит заголовком и здесь (лесенка taskHeadline),
     и строка на упражнение обязана дожить до монитора. Обрезка в две строки остаётся. */
  .oent-t.big{font-size:var(--fs-md);font-weight:800;line-height:1.3;white-space:pre-line;overflow-wrap:anywhere;
    display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
  /* таск-ссылка: URL — ПЕРВАЯ строка заголовка и кликается прямо с монитора (blox46 #6) */
  .oent-t.big .oent-url{display:block;color:var(--accent-dark);font-weight:700;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .oent-sub{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-top:4px}
  .oent-sub:empty{display:none}
  .oent-sub .otype{font-size:var(--fs-xs);font-weight:700;color:var(--muted)}
  .omut{font-size:var(--fs-xs);color:var(--muted)}
  .oarch{font-size:var(--fs-2xs);background:var(--alert-fill);color:var(--alert-ink);padding:1px 6px;border-radius:var(--r-xs)}
  /* Списки имён в плашке таска (blox52 #11): цвет ушёл ИЗ ТЕКСТА В МАРКЕР. Красные строки
     «Не открывали: …» на каждой плашке складывались в «экран пожара» (диагноз обоих судей),
     хотя «ещё не открыл» до урока — факт без срочности, а не провал. Теперь сам текст —
     чернила/серый, а состояние несёт маленький знак перед списком: ● зелёный «есть» /
     ○ пустое кольцо «нет» — тот же язык, что у точек треков в плейлисте ребёнка.
     Маркер строчный (::before), высоту строки не меняет — плашка не растёт. */
  .opos{font-size:var(--fs-xs);color:var(--ink);margin-top:3px}
  .opos::before{content:'●';color:var(--ok);font-size:var(--fs-2xs);margin-right:5px;vertical-align:1px}
  .opos.all{font-weight:700;color:var(--ok-ink)}
  .opos.all::before{content:none}
  .oneg{font-size:var(--fs-xs);color:var(--muted);margin-top:2px}
  .oneg::before{content:'○';color:var(--muted);font-size:var(--fs-2xs);margin-right:5px;vertical-align:1px}
  /* blox30 #2 — «открыл, но не дослушал»: промежуточное состояние между прослушал и не открывал */
  .omid{font-size:var(--fs-xs);color:var(--draft-ink);margin-top:2px}
  .omid svg,.opos svg{width:12px;height:12px;vertical-align:-1px}
  /* разделитель панелей «прошло/предстоит» — ПЕЧАТНАЯ ЛИНЕЙКА (blox52 #15): волосок 1px во всю
     высоту, без ореола и скругления — прежний 2px-рельс глаз читал как ползунок или ручку
     шторки, которой нет. Смысл .now («сейчас — между прошедшим и предстоящим», каретка стоит
     на дефолтной паре) НЕ потерян: линейка красится акцентом схемы (--anchor) вместо прежнего
     градиента accent→ok — тот же язык, что у точки lday-alert и рамки .nag. На телефоне линейки
     нет вовсе (медиа ниже): панели стоят столбиком и различаются шапками, «сейчас» там несёт
     кольцо «сегодня» и заливка .viewing на каретке. Мёртвые .oaxis-lbl/.oaxis-now зачищены —
     подпись из разметки ушла раньше, правила остались сиротами. */
  .oaxis{flex:0 0 auto;display:flex;flex-direction:column;padding:0 5px}
  .oaxis-line{width:1px;flex:1;background:var(--line-mid);min-height:60px}
  .oaxis.now .oaxis-line{background:var(--anchor)}
  /* published pane: Лист ДЗ on the left, roster column on the right */
  .opane-body{display:flex;gap:10px;align-items:flex-start}
  .opane-body>.oents{flex:1 1 0;min-width:0}
  .oroster{flex:0 0 148px;display:flex;flex-direction:column;gap:6px}
  /* blox83 #4 — на ПК чип снова в ОДНУ строку: имя слева, плашка статуса справа (владелец откатил
     двухстрочный чип blox82 #6 на компьютере). Двухстрочный остался только телефону — см. блок
     ≤760px ниже. */
  .oroster .rchip{width:100%;justify-content:space-between;padding:6px 9px}
  /* чип ученика — кнопка (личка ученика): отклик на нажатие тем же движком, что у чипа группы
     (.gchip, журнал redesign §5): сухо вдавился, пружиной назад. Переходы цвета — на токенах; вспышка
     .hl идёт своей анимацией по тени и фону и с transform не спорит. Редизайн 27.10, агент 4. */
  .oroster .rchip{transition:border-color var(--dur-state) var(--ease-standard),background-color var(--dur-state) var(--ease-standard),
      box-shadow var(--dur-state) var(--ease-standard),transform var(--dur-release) var(--ease-spring)}
  .oroster .rchip:active{transform:scale(var(--press-scale));
    transition-duration:var(--dur-press-in);transition-timing-function:var(--ease-standard)}
  @media (prefers-reduced-motion:reduce){.oroster .rchip:active{transform:none}}
  /* theme frame inside a pane — title at the START of the row (left). Рамка — общая
     (см. .tframe,.oframe,.theme выше); тут только своя плотность. */
  .oframe{padding:8px 10px 10px;margin-bottom:9px;--fpad:10px;--frail:3px}
  .oframe>.ofh{font-weight:800;font-size:var(--fs-sm);color:var(--ink);padding:0 2px 8px 0;text-align:right}
  .oframe>.ofcards{display:flex;flex-direction:column;gap:7px}
  /* сущности «вне темы» — same width as framed ones, just shifted right to line up */
  .oent.loose{margin-left:11px;margin-right:10px}
  /* pane hosting the inline конструктор (постановка) */
  /* blox51 #4: конструктор — ПОДНОС, а не лист. Сперва лавандовая заливка черновика делала
     рабочую поверхность мутной: на ней же лежат белые плашки тасков, и они переставали
     читаться как бумага. Первая версия сделала панель самой бумагой (--card) — стало чисто,
     но плашки легли белым на белое и снова потеряли край. Правильный уровень — СРЕДНИЙ:
     --surface (`--paper-200`, «углубление в бумаге»), который по замеру делит зазор ровно
     пополам — 1.15 до бумаги вверх и 1.15 до стола вниз при 1.32 между ними. Плашка на нём
     снова читается листом, а сам поднос — углублением, а не третьим парящим слоем.
     Состояние никуда не делось — его держит РАМКА (с blox84 #4 — пунктир цвета схемы). */
  /* blox51 #6: конструктор на обычном уроке тоже потерял салатовую рамку — по той же причине,
     что и .opane.pub выше. А вот pubedit метку СОХРАНЯЕТ: «ты правишь лист, который дети уже
     видят» — единственное состояние здесь, о котором стоит предупредить. Метка переехала с
     зелёной на ЧЕРНИЛЬНУЮ: чернила в этой панели уже значат «смотри сюда» (.opane.next —
     чернильный пунктир «ближайшее»), и у них нет смысла-цвета, который можно прочитать
     неправильно. Зелёный читался «всё хорошо» ровно там, где нужна осторожность.
     С blox84 #4 метка правки — тот же толстый пунктир цвета схемы, что у черновика (правило у .opane);
     про «дети уже видят» говорит строка .obuild-top над конструктором. */
  .opane.build{background:var(--surface)}
  .opane.build.draft{background:var(--surface)}
  /* ЧЕРНОВИК = «ЕЩЁ НЕ ОТПЕЧАТАНО» (blox52 #8). Состояние держал ОДИН признак — цвет
     рамки (сиреневый волосок), и на общем плане панель черновика была неотличима от
     выданной. Теперь черновик говорит МАТЕРИАЛОМ, тем же языком, что и остальной лист:
       · РАМКА ПУНКТИРОМ — та же ступень лестницы линий (волосок 1px, см. [[line-scale]]),
         меняется только стиль штриха, поэтому геометрия панели не сдвигается ни на пиксель
         (правило «экран не прыгает»). Пунктир в проекте уже значит «ещё не настоящее»:
         так нарисованы «+ тема», зона сброса и ближайшее занятие (.opane.next);
       · КОСАЯ ШТРИХОВКА по подложке — бумага, которую ещё не пропустили через печать.
         Сила ≤4% (--draft-hatch, theme.css): гуще — и панель прочиталась бы ОТКЛЮЧЁННОЙ,
         а черновик как раз рабочий. На карточки внутри штриховка не лезет: у .oent/.bitem
         своя непрозрачная бумага (--task-plaque), она ложится поверх.
     Правило объявлено ПОСЛЕ .opane.build.draft и перечисляет ОБА селектора: у панели с
     открытым конструктором заливка написана короткой записью background, а она обнуляет
     background-image — одной .opane.draft штриховка до неё бы не доехала. */
  /* blox84 #4 — пунктир носит и ПРАВКА ВЫДАННОГО (.pubedit): пока лист открыт в конструкторе, он
     «ещё не отпечатан» так же, как черновик; толщина и цвет — общие с выданным (правило у .opane). */
  .opane.build.pubedit{border-style:dashed}
  .opane.draft,.opane.build.draft{border-style:dashed;
    background-image:repeating-linear-gradient(45deg,
      var(--draft-hatch) 0 1px, transparent 1px 7px)}
  .obuild-top{font-size:var(--fs-xs);font-weight:700;color:var(--muted-strong);margin:2px 0 8px}
  .obuildhost{min-height:40px}
  /* CARUSEL: a continuous weekday ribbon; lesson days carry a date + status dot; the two active
     lessons are boxed (left solid, right dashed). Draggable; «к сегодня» = pull-down / dbl-click. */
  .ncar-outer{position:relative;overflow:hidden;border:1px solid var(--line-soft);border-radius:var(--r-md);background:var(--surface-2)}
  /* лента непрозрачна и лежит НАД кольцом (z-index 1): при оттягивании вниз она открывает кольцо
     сверху. touch-action:pan-x — палец скроллит дни по горизонтали нативно, а вертикальный жест
     (pull-to-refresh) остаётся нам. .snap — плавный возврат ленты после отпускания. */
  .ncar{display:flex;gap:1px;overflow-x:auto;cursor:grab;user-select:none;padding:6px 8px;scrollbar-width:thin;
    position:relative;z-index:1;background:var(--surface-2);touch-action:pan-x;will-change:transform}
  .ncar.snap{transition:transform var(--dur-panel) var(--ease-enter)}
  .ncar.grabbing{cursor:grabbing}
  /* ДОРОЖКА полосы прокрутки задана явно (blox51 #11): как только у ::-webkit-scrollbar
     появляется хоть одно своё правило, Chromium перестаёт рисовать дорожку сам и берёт
     светло-серую по умолчанию — в тёмной схеме под лентой уроков светилась белая полоса,
     самое яркое пятно экрана. Прозрачная дорожка верна во всех схемах: видно ползунок. */
  .ncar::-webkit-scrollbar{height:6px;background:transparent}
  .ncar::-webkit-scrollbar-track{background:transparent}
  .ncar::-webkit-scrollbar-thumb{background:var(--line);border-radius:var(--r-xs)}
  .nday{flex:0 0 auto;width:42px;display:flex;flex-direction:column;align-items:center;gap:2px;
    padding:5px 2px;border:1px solid transparent;border-radius:var(--r-sm);background:transparent;font:inherit;line-height:1.1}
  /* caption + date are real content, not hints → --muted (grape-400/300 gave ~1.4–3.2:1).
     noday (a filler day with no lesson) recedes via муteness alone; the lesson days stand out
     with bold --ink dates + the coloured underline, so no dimming opacity is needed. */
  .nday .nwd{font-size:var(--fs-2xs);color:var(--muted)}
  .nday .ndate{font-size:var(--fs-2xs);color:var(--muted)}
  /* тонкая недельная полоса на стыке Вс→Пн: рисуется на ячейке понедельника (.wkstart) как
     ::before в зазоре слева. Высота = высоте плашки (top/bottom:0 тянут её на весь рост ячейки,
     ячейки в .ncar растянуты по stretch). Не меняет ширину ячейки и не влияет на прокрутку
     (blox28 #2). ТОЛЩИНА 1.5px намеренная (blox52 #3: это знак, а не граница — вне лестницы
     линий законно), а вот ЦВЕТ сменён с --frame-line(=чернила) на --line-mid (blox52 #15):
     недельная линейка была самой тёмной линией экрана — темнее плашек с датами, то есть
     иерархия ежедневника стояла вверх ногами (сетка обязана быть тише записей). --line-mid
     перекрашивается схемой так же, как перекрашивался --frame-line. */
  /* blox83 #5 — урок в понедельник сливался с полосой: она стояла в зазоре 1px вплотную к его
     рамке. Теперь на стыке недель свой воздух (margin-left 8px) и полоса посередине этого зазора.
     Прокрутку не трогает: центрирование меряет живые координаты (getBoundingClientRect), а dayW
     нужен только для числа дней-распорок по краям. */
  .nday.wkstart{position:relative;margin-left:8px}
  .nday.wkstart::before{content:'';position:absolute;left:-5.25px;top:0;bottom:0;width:1.5px;
    border-radius:var(--r-2xs);background:var(--line-mid)}
  /* blox52 #12 — переход у наведения/нажатия дня: рамка и тень уже ехали (пачка 7), но заливка
     (.viewing ставится оптимистично по тапу) и вдавливание меняли вид скачком. background-color
     и transform добавлены в тот же transition; вдавливание — тот же приём, что у .gchip:active,
     только мягче (.96 на плашке 42px читается, .97 было заведено под чип покрупнее). */
  /* рамка плашки — --line-mid, а не --line-soft (blox52 #15): день с уроком обязан стоять
     В иерархии ВЫШЕ сетки (недельной линейки того же --line-mid, но без заливки и содержимого) —
     с невидимым --line-soft плашка держалась на одной заливке и проигрывала линейке. */
  .nday.lday{cursor:pointer;background:var(--card);border-color:var(--line-mid);position:relative;
    -webkit-tap-highlight-color:transparent;
    transition:border-color var(--dur-state) var(--ease-standard),box-shadow var(--dur-state) var(--ease-standard),
               background-color var(--dur-state) var(--ease-standard),transform var(--dur-release) var(--ease-spring)}
  /* redesign 27.10 — вдавливание сухое и короткое (--dur-press-in), отпускание — с пружиной
     (базовый переход выше). Чёрная рамка .viewing при этом не прыгает, а ПЕРЕТЕКАЕТ: у прежнего
     дня она гаснет, у нового проявляется за те же --dur-state (border-color + box-shadow в
     переходе). Скользящую рамку (одна рамка едет по ленте) не делали: это отдельный узел и JS в
     paintCarousel — см. журнал redesign, агент 3. */
  .nday.lday:active{transform:scale(var(--press-scale));
    transition-duration:var(--dur-press-in);transition-timing-function:var(--ease-standard)}
  /* фокус с клавиатуры — кольцо ВНУТРЬ: лента режет всё, что выходит за её край (overflow) */
  .nday.lday:focus-visible{outline:none;box-shadow:var(--ring-inset)}
  /* «уменьшенное движение» гасит НОВОЕ движение пачки blox52 #12: подчёркивание группы
     прыгает к чипу без анимации, день не вдавливается и не везёт transform. Цветовые
     переходы (рамка/заливка/тень) остаются — это не движение. Занавес #gpanes тут не
     назван намеренно: его transition — противомигательный механизм, см. комментарий выше. */
  @media (prefers-reduced-motion:reduce){
    .gind{transition:none}
    .nday.lday{transition:border-color var(--dur-state) var(--ease-standard),box-shadow var(--dur-state) var(--ease-standard),
               background-color var(--dur-state) var(--ease-standard)}
    .nday.lday:active{transform:none}
  }
  /* НАВЕДЕНИЕ НА ПЛАШКУ ДНЯ (blox52 #8). Лента дней была последним рядом нажимаемых плиток
     без отклика на курсор — при том что у плитки кабинета, призрачной кнопки и чипа группы
     (blox52 #3) он есть. Тот же ключ, что у .hub-card: акцент ЛИНИЕЙ (--accent-deep) плюс
     тень на ступень выше; ни размер, ни отступы не трогаются — лента прокручивается по
     ширине плашки 42px, и подвинуть её значило бы сдвинуть весь расчёт каретки.
     Подложку намеренно НЕ красим: --accent-tint на этой ленте уже занят и значит «эту пару
     сейчас смотрим» (.nday.viewing) — наведение не имеет права говорить то же самое.
     Селектор написан ОДНИМ классом (.lday:hover, а не .nday.lday:hover) и объявлен ДО
     состояний ниже: у активной пары, у «сегодня» и у «ДЗ не выдано» рамка своя и обязана
     пережить курсор — при равной силе побеждает то правило, что записано позже. Тот же
     приём, что у .gchip:hover перед .gchip.on. */
  @media (hover:hover){.lday:hover{border-color:var(--accent-deep);box-shadow:var(--sh-1)}}
  /* blox35 #1 — тортик на плашке урока-праздника: в УГЛУ и выступает за плашку, как счётчик
     непрочитанных над кнопкой. Выход наружу безопасен: у .ncar padding 6px/8px, поэтому значок
     остаётся ВНУТРИ ленты и не обрезается её .ncar-outer{overflow:hidden}; вбок он заходит на
     пустое поле соседней ячейки (текст в ней центрирован и узкий), ширину плашки не меняет —
     на 42px держится расчёт прокрутки каретки. Геометрию стережёт DOM-ассерт #10. */
  .nday .ncake{position:absolute;top:-6px;right:-6px;line-height:0;pointer-events:none}
  .nday .ncake .ic{width:16px;height:16px}
  /* Число и месяц — ДВЕ строки (blox51 #9). Замер: внутри плашки 36px, «12 сент» просит 46 —
     в одну строку месяц не помещается ничем (трёхбуквенный просит ~50), а расширить плашку нельзя:
     от её ширины 42px считается прокрутка ленты. Высота ленты при этом постоянна — месяц стоит на
     КАЖДОЙ плашке урока, поэтому она не растёт и не садится от того, какие дни видны. nowrap на
     обеих строках: табличные цифры (заход 6) сделали единицу шириной с ноль, и перенос внутри
     строки снова поднял бы ячейку выше соседних. */
  .nday.lday .ndate{font-size:var(--fs-xs);font-weight:700;color:var(--ink);white-space:nowrap}
  .nday .nmon{font-size:var(--fs-2xs);font-weight:600;color:var(--muted);white-space:nowrap;line-height:1.05}
  /* blox86 #4 — «есть что проверить» говорит маленький «Check», тот же, что на чипе ребёнка
     (.rchip .rrev.pend: якорь схемы под белым текстом), только без числа: точку читали как
     «что-то не так», а «Check» — это прямо действие. Значок стоит в КАЖДОЙ плашке урока и у
     прочих спрятан (visibility, не display) — место держит, плашки одной высоты, лента не
     прыгает при прокрутке. Влезает в 36px внутри плашки: стережёт ассерт «каретка · дата
     помещается в плашку урока». Очередь, не пожар → якорь, не красный (blox52 #11). */
  /* Ширина — впритык: «Check» в Inter 11px жирным 800 просит 39px, в 36px не лезет (а Andika — 30,
     поэтому замер только в Andika врал). Насыщенность 600 и разрядка -.02em дают 34px в Inter. */
  /* Форма — скруглённый прямоугольник, как у «Check N» на чипе ребёнка (blox86 #5): --r-xs (8px) на
     значке высотой ~16px давал полукруглые торцы — «таблетку»; --r-2xs (4px) держит углы. */
  .nday.lday .ncheck{font-size:var(--fs-2xs);font-weight:600;letter-spacing:-.02em;line-height:1.25;padding:1px 1px;
    border-radius:var(--r-2xs);color:var(--on-dark);background:var(--anchor);white-space:nowrap;
    visibility:hidden}
  .nday.lday-alert .ncheck{visibility:visible}
  /* blox83 #5 — рамки «сегодня» (чёрная у последнего прошедшего, пунктир у ближайшего) и заливка
     просматриваемой пары сняты. Чёрная рамка перешла к ОДНОМУ уроку — тому, чей лист стоит первым
     (на ПК справа) — это единственная рамка на ленте (blox86 #3). */
  .nday.viewing{border-color:var(--ink);box-shadow:0 0 0 1px var(--ink) inset}
  /* redesign 27.10 — скользящая рамка (glideFrame в teacher.js): на время езды поверх ленты — копия
     рамки .viewing, у нового дня своя рамка спрятана, у прежнего гаснет сразу (под копией), без
     перетекания — иначе за уезжающей рамкой тянулся бы её призрак. */
  .ncar-glide{position:absolute;z-index:2;pointer-events:none;box-sizing:border-box;
    border:1px solid var(--ink);border-radius:var(--r-sm);box-shadow:0 0 0 1px var(--ink) inset}
  .nday.lday.nglide-hold{border-color:var(--line-mid);box-shadow:none;transition:none}
  .nday.lday.nglide-from{transition:background-color var(--dur-state) var(--ease-standard),
    transform var(--dur-release) var(--ease-spring)}
  /* фокус на дне, который и так в рамке: рамка остаётся, кольцо ложится под неё (redesign 27.10) */
  .nday.lday.viewing:focus-visible{box-shadow:0 0 0 1px var(--ink) inset,var(--ring-inset)}
  .nday.today{opacity:1}                                             /* сегодня всегда яркий, даже если не день занятия */
  .nday.today .ndate{display:flex;align-items:center;justify-content:center;width:22px;height:22px;
    border:2px solid var(--ink);border-radius:50%;color:var(--ink);font-weight:800;font-size:var(--fs-xs)}  /* кружок толще — blox83 #5 */
  /* Сб и Вс — розовым, как на настенном календаре (blox83 #5): день недели, число и месяц, в том
     числе у урока и у сегодняшнего дня (кружок остаётся чернильным). Записано ПОСЛЕ правил даты
     урока и «сегодня» — при равной силе селектора побеждает последнее. */
  .nday.wkend .nwd,.nday.wkend .ndate,.nday.wkend .nmon{color:var(--weekend)}
  /* «Вернуться к сегодня» без кнопки: кольцо ПОД лентой. Оттягивание ленты вниз открывает его
     сверху, а дуга замыкается по мере оттягивания (--prog 0→1). За порогом .ready → зелёная,
     на отпускании .spin → крутится, пока грузится актуальный урок. blox31 #2. */
  .ncar-refresh{--prog:0;position:absolute;top:6px;left:50%;transform:translateX(-50%);
    width:28px;height:28px;opacity:0;z-index:0;pointer-events:none;transition:opacity var(--dur-press) var(--ease-standard)}
  .ncar-refresh.show{opacity:1}
  .ncr-svg{width:28px;height:28px;transform:rotate(-90deg)}
  .ncr-bg{fill:none;stroke:var(--line);stroke-width:3}
  .ncr-arc{fill:none;stroke:var(--ink);stroke-width:3;stroke-linecap:round;
    stroke-dasharray:94.2;stroke-dashoffset:calc(94.2 * (1 - var(--prog,0)))}
  .ncar-refresh.ready .ncr-arc{stroke:var(--ok-solid)}
  .ncar-refresh.spin .ncr-svg{animation:ncr-spin .7s linear infinite}
  .ncar-refresh.spin .ncr-arc{stroke:var(--ok-solid);stroke-dashoffset:calc(94.2 * .25)}
  @keyframes ncr-spin{to{transform:rotate(360deg)}}
  /* под «уменьшить движение» крутилка не встаёт, а замедляется — как все крутилки (theme.css) */
  @media (prefers-reduced-motion:reduce){.ncar-refresh.spin .ncr-svg{--motion-reduced:ncr-spin 2s linear infinite}}
  @media(max-width:760px){
    .otwo{flex-direction:column-reverse}   /* DOM: earlier, axis, later -> reversed = later on TOP */
    /* blox84 #8 — на телефоне до рамки 16px (10 у .otwo + 6 у .opane): линия на 8px */
    .otwo{margin-top:8px;padding-top:2px}
    .opane{flex:1 1 100%;margin:6px 0}
    /* на телефоне между панелями — просто зазор (blox52 #15): горизонтальная полоса читалась
       ползунком; сами панели и так различимы шапками, «сейчас» несёт каретка */
    .oaxis{display:none}
    .opane.edge{align-self:auto}   /* столбик: cross-axis — ширина, flex-start её бы сузил */
    .oarr{width:100%;height:34px}
    .opane-body{flex-direction:column;align-items:stretch}
    /* ростер на телефоне — сетка плиток 4 колонки: блок «ПЕРЕРИСОВКА B — МОНИТОР» в конце файла
       (прежние две колонки чипов и раскладка blox84 #6 .two/.long/.lfirst сняты вместе с roster-fit.js) */
  }
  /* таск в черновике ДЗ = такая же плашка, как у ученика и на проверке: белый прямоугольник,
     тонкая рамка и цветной рельс слева (--rail: серый / синий «нужна сдача») — blox10 #8 */
  .bitem{display:flex;align-items:flex-start;flex-wrap:wrap;gap:8px 12px;padding:12px 12px 12px 21px;
    border:1px solid var(--line-soft);border-radius:var(--r-lg);margin-bottom:8px}
  /* blox63 #2 — та же правка, что у ребёнка (#8): иконка типа живёт В СТРОКЕ типа (.bm), а не
     отдельной колонкой слева. Размер по строке меты, не по заголовку. */
  .bitem .bico{font-size:var(--ic-md)}
  .bitem .binfo{flex:1 1 100%;min-width:0;overflow-wrap:anywhere}
  /* кнопки действий уходят на свою строку под текстом, вся тройка прижата вправо */
  .bitem .act:first-of-type{margin-left:auto}
  /* blox58 #6: СБОРНЫЙ таск («выбрать несколько» в учебнике) приходит строкой на упражнение —
     переводы строк в нём несущие, а HTML их схлопывает, и шесть упражнений читались сплошняком.
     pre-line (а не pre-wrap): переносы сохраняем, но отступы разметки шаблона не печатаем —
     рядом с текстом в .bt стоят чипы «для:» и «челлендж». */
  .bitem .bt{font-weight:700;font-size:var(--fs-md);white-space:pre-line}   /* тот же размер, что у трёх других карточек */
  .bitem .bm{font-size:var(--fs-xs);color:var(--muted);margin-top:2px;
    display:flex;align-items:center;gap:6px;flex-wrap:wrap}
  .bitem .act{background:none;border:none;cursor:pointer;font-size:var(--fs-sm);font-weight:700;
    color:var(--accent-dark);padding:4px}
  .bitem .act.del{color:var(--red)}

  /* ═══════════════════════════════════════════════════════════════════════
     ПЕРЕРИСОВКА B — КАРЕТКА ДНЕЙ НА ПОЛОСЕ ГРУППЫ (агент 2, эскиз MonB). Только вид: ширина
     плашки 42px (от неё считаются распорки dayW), сборка, перекраска, скользящая рамка,
     оттягивание к сегодня — всё прежнее. Выбранный день — белая плитка с цифрами цвета группы,
     дни с уроком — полупрозрачная белая плитка, пустые дни — без плитки и тише, «сегодня» —
     подчёркнутое число, числа — витринным шрифтом. «Check» — тот же прямоугольник blox86 #5. */
  .gband .ncar-outer{border:0;border-radius:0;background:transparent}
  /* лента непрозрачна (под ней кольцо «к сегодня») — значит цвета полосы, не стола */
  .gband .ncar{background:var(--group);gap:4px;padding:6px 0 8px}
  .gband .ncar::-webkit-scrollbar-thumb{background:var(--on-group-line)}
  .gband .nday{height:72px;box-sizing:border-box;justify-content:center;padding:4px 1px;
    border-color:transparent;border-radius:var(--r-btn);color:var(--on-group)}
  /* день без урока отступает весом 500 и отсутствием плитки, НЕ прозрачностью: .75 роняла белое число
     до 3.9 и розовое Сб/Вс до 3.3 на #a3501a (замер агента 6, все восемь цветов) */
  .gband .nday.noday{opacity:1}
  .gband .nday .nwd{color:var(--on-group);font-size:var(--fs-2xs);font-weight:500}
  .gband .nday .ndate{font-family:var(--font-display);font-size:var(--fs-daynum);font-weight:var(--fw-display-soft);
    line-height:22px;color:var(--on-group);letter-spacing:-.02em}
  .gband .nday.lday .ndate{font-size:var(--fs-daynum);font-weight:var(--fw-display);color:var(--on-group)}
  .gband .nday .nmon{color:var(--on-group);font-weight:500}
  .gband .nday.lday{background:var(--on-group-fill);border-color:transparent}
  /* наведение: подложка гуще и белый контур (проба blox52 #8 смотрит на рамку); размер не меняется */
  @media (hover:hover){.gband .lday:hover{background:var(--on-group-hover);border-color:var(--on-group-line);box-shadow:none}}
  /* ВЫБРАННЫЙ ДЕНЬ — белая плитка, всё на ней цвета группы. Рамки нет: «выбран» несёт заливка */
  .gband .nday.viewing{background:var(--on-group);border-color:transparent;box-shadow:none}
  .gband .nday.viewing :is(.nwd,.ndate,.nmon){color:var(--group)}
  .gband .nday.lday.viewing:focus-visible,.gband .nday.lday:focus-visible{box-shadow:var(--ring-inset)}
  /* скользящая рамка (glideFrame) на полосе — белый контур плитки; у нового дня своя заливка
     уже белая, поэтому прятать нечего, кроме контура */
  .gband .ncar-glide{border:2px solid var(--on-group);border-radius:var(--r-btn);box-shadow:none}
  .gband .nday.lday.nglide-hold{border-color:transparent;box-shadow:none}
  /* СЕГОДНЯ — подчёркнутое число (эскиз: 3px, отступ 4px) вместо кружка */
  .gband .nday.today .ndate{display:block;width:auto;height:auto;border:0;border-radius:0;
    font-size:var(--fs-daynum);text-decoration:underline;text-decoration-thickness:3px;text-underline-offset:4px}
  /* Сб и Вс отмечены цветом ВСЕГДА (blox83 #5; владелец 28.10 после агента 6 — «выходные не отмечены
     цветом» — вернул и выходным с уроком, и выбранному). Каждый вид клетки — своим цветом, который там
     проходит контраст во всех восьми цветах группы, днём и ночью (проба group-color #4/#5):
     · пустой день — весь розоватый --on-group-weekend (≥4.5 на голой полосе);
     · день с уроком — розовое только ЧИСЛО (--on-group-weekend-big, крупный текст ≥3), подписи белые:
       на заливке клетки мелкий розовый не дотягивает до 4.5 ни в каком оттенке;
     · выбранный — на белой плитке красным грейпфрутом (--on-group-weekend-ink, 4.89). */
  .gband .nday.wkend :is(.nwd,.ndate,.nmon){color:var(--on-group-weekend)}
  .gband .nday.wkend.lday:not(.viewing) :is(.nwd,.nmon){color:var(--on-group)}
  .gband .nday.wkend.lday:not(.viewing) .ndate{color:var(--on-group-weekend-big)}
  .gband .nday.wkend.viewing :is(.nwd,.ndate,.nmon){color:var(--on-group-weekend-ink)}
  .gband .nday.wkstart::before{background:var(--on-group-line)}
  /* «Check» — «ждёт учителя»: белый прямоугольник с текстом цвета группы; на выбранной белой
     плитке — наоборот. Скругление --r-badge-sm (4px), не таблетка (blox86 #5). */
  .gband .nday.lday .ncheck{background:var(--on-group);color:var(--group);border-radius:var(--r-badge-sm)}
  /* агент 3 — «Check» упирался в края плитки: 11px Inter давали прямоугольник 36px на плитке 42 —
     по 3px с боков, на глаз «впритык». Эскизные 10px (разрядку и поля 1px оставляем — их просит
     Inter) дают 34px в Inter и 30 в Andika: воздух ≥4px с каждой стороны (проба blox51 #9 мерит и его) */
  .gband .nday.lday .ncheck{font-size:var(--fs-check)}
  .gband .nday.lday.viewing .ncheck{background:var(--group);color:var(--on-group)}
  .gband .ncr-bg{stroke:var(--on-group-line)}
  .gband .ncr-arc{stroke:var(--on-group)}

  /* ═══════════════════════════════════════════════════════════════════════
     ПЕРЕРИСОВКА B — МОНИТОР УЧИТЕЛЯ (агент 3, эскиз MonB). Листы урока больше не коробки в толстой
     рамке: под полосой группы — стол B, на нём белые карточки без тени, разделённые воздухом.
     Сверху листа — дата витринным шрифтом и состояние словом, рядом Notes | Tasks; ГЕРОЙ — сетка
     плиток учеников (кружок инициалов, имя, плашка статуса); под ней серый ряд действий
     (Attendance · Post · Chat · ☰ — строку инструментов туда переносит placeTools на телефоне);
     ниже ОДНА белая карточка листа: плейлист и таски строками через волосок, у каждого таска —
     тонкая полоса того же счёта «сделали N из M», что напечатан в строке. Классы и id прежние. */
  /* полосы под листами больше нет (blox84 #8 был светлой подложкой под коробками; в B подложка —
     сам стол): листы лежат прямо на столе */
  .otwo{background:transparent;box-shadow:none;clip-path:none;margin-top:var(--sp-2);padding:0 0 var(--sp-3)}
  /* ЛИСТ — прозрачный: рамки нет, но её место (2px) держат ВСЕ листы — черновик и правка выданного
     рисуют в нём пунктир цвета схемы, и переход «выдано ↔ черновик» не сдвигает ни пикселя
     (проба blox52 #8). Отступ внутри 8+2: содержимое стоит на поле страницы 16px, а сама коробка —
     в 6px от края экрана (отрицательные поля ниже, только телефон). */
  .opane{--pane-bw:2px;border:var(--pane-bw) solid transparent;border-radius:var(--r-card);
    padding:calc(10px - var(--pane-bw));background-color:transparent}
  /* только background-color, не короткая запись: та обнулила бы штриховку черновика (background-image) */
  .opane.pub{border-color:transparent;background-color:transparent}
  /* черновик и открытый конструктор — поднос (--tray, theme.css: в B вмятина ниже стола) в пунктире
     цвета схемы; штриховка черновика — прежнее правило выше (background-image) */
  .opane.draft,.opane.build,.opane.build.draft{background-color:var(--tray)}
  /* пустой предстоящий день и «прошло без ДЗ» — белые карточки; рамки нет */
  .opane.empty,.opane.past{background-color:var(--card);border-color:transparent;
    padding:calc(12px - var(--pane-bw)) calc(14px - var(--pane-bw))}
  /* записано ПОСЛЕ белых карточек: при равной силе пунктир черновика побеждает их прозрачную рамку */
  .opane.draft,.opane.build.draft,.opane.build.pubedit{border-color:var(--anchor);border-style:dashed;background-color:var(--tray)}
  .opane.next.nag,.opane.next.nag.build{border-color:var(--alert)}
  /* «ДЗ ещё не выдано» у ближайшего урока — розовая карточка эскиза (тинт тревоги) в красной рамке
     (сигнал blox59 #4 держится), «Установить ДЗ» — прежняя кнопка; дыхание — между тинтом и
     заливкой тревоги, а не до прозрачного: карточка розовая всегда */
  .opane.empty.urgent{background-color:var(--alert-tint);border-color:var(--alert)}
  @keyframes nagpulse{0%,100%{background-color:var(--alert-tint)}50%{background-color:var(--alert-fill)}}
  .opane.edge{background-color:transparent}
  /* край с тумблером: тумблер — абсолют (8 + 44 от внутренней кромки рамки), текст — ниже него с
     воздухом 4px (прежнее calc(57px - bw) при рамке 2px уводило текст под кнопку — проба blox47 #3) */
  .otwo .opane.edge:has(.onotes){padding-top:56px}
  /* тумблер-абсолют у края — на той же высоте, что тумблер в шапке любого другого листа (верх
     содержимого = 10px от кромки при рамке 2px), иначе при Notes ↔ Tasks он прыгал на 4px (проба) */
  .otwo .opane > .onotes{top:calc(10px - var(--pane-bw))}
  .opane.past .opane-mid,.opane.empty .opane-mid{padding:var(--sp-3) 0 var(--sp-1)}
  /* ШАПКА ЛИСТА: дата — витринным шрифтом (эскиз 20/700), под ней состояние словом (13/500);
     плашки-пилюли нет. Волосок под шапкой снят — шапку отделяет воздух. */
  .opane-h{border-bottom:0;box-shadow:none;padding-bottom:0;margin-bottom:var(--sp-3);align-items:start}
  /* кнопки шапки стоят на линии ДАТЫ (дата + слово состояния), а не всей колонки: подпись «выдано ·
     сдач ещё нет» ниже их не тянет (проба шапки листа, blox84 #7) */
  .opane-h .opane-d,.opane-h .opane-hr{align-self:start}
  .opane-h .opane-hr{min-height:calc(var(--fs-heading) * 1.2 + var(--fs-sm) * 1.3)}
  .odate,.odate.past,.odate.today,.odate.up{height:auto;margin:0;padding:0;border:0;border-radius:0;background:transparent;
    display:inline-flex;flex-direction:column;align-items:flex-start;white-space:nowrap;
    font-family:var(--font-display);font-size:var(--fs-heading);font-weight:var(--fw-display);line-height:1.2;color:var(--ink)}
  .odate .odw{display:block;font-family:var(--font-body);font-size:var(--fs-sm);font-weight:500;line-height:1.3;color:var(--faint)}
  .odate .odsep{display:none}
  .odate.today .odw{color:var(--group-ink);font-weight:700}   /* «сегодня» — голосом группы */
  .opane-d .opubnote{margin:2px 0 0}
  /* ГЕРОЙ — сетка плиток учеников: 4 колонки на телефоне (эскиз), на планшете и ПК — сколько
     влезет плиток от 96px (лист 600px на ПК → 5). Плитка 96px ростом, белая, радиус плитки; имя
     одной строкой с многоточием (полное — в title кнопки), плашка статуса под именем. Место ребёнка
     зависит только от алфавита, не от статуса — одно в обеих панелях. */
  .opane-body{flex-direction:column;align-items:stretch;gap:var(--sp-3)}
  .oroster{flex:none;display:grid;grid-template-columns:repeat(auto-fill,minmax(96px,1fr));gap:var(--sp-2);margin:0}
  .oroster .rchip{flex-direction:column;align-items:center;justify-content:center;gap:5px;
    width:auto;min-width:0;height:96px;box-sizing:border-box;padding:6px 4px;text-align:center;
    border:1px solid transparent;border-radius:var(--r-tile);background:var(--card);box-shadow:none}
  @media (hover:hover){.oroster .rchip:hover{border-color:var(--line-mid);box-shadow:none}}
  .oroster .rchip:focus-visible{outline:none;box-shadow:var(--ring)}
  .oroster .rname{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
    font-size:var(--fs-ctl);font-weight:600;line-height:1.25;color:var(--ink)}
  .oroster > .omut{grid-column:1 / -1}
  /* РЯД ДЕЙСТВИЙ ЛИСТА — серые кнопки 44px (эскиз): Attendance, а на телефоне и строка инструментов
     группы. Узел строки растворяется (display:contents) — его кнопки встают в ряд наравне с
     Attendance, гамбургер последним. Пустой ряд (будущий урок без строки) не занимает места. */
  .oacts{display:flex;align-items:center;gap:var(--sp-2);min-width:0}
  .oacts:empty{display:none}
  .opane > .oacts{margin-bottom:var(--sp-3)}
  .oacts .btn-ghost{height:44px;box-sizing:border-box;flex:1 1 auto;min-width:0;justify-content:center;
    padding:0 10px;border-radius:var(--r-btn);font-size:var(--fs-ctl);font-weight:600;white-space:nowrap}
  .oacts > .omut{display:inline-flex;align-items:center;gap:6px}
  .oacts .oico{display:none}
  .oacts .ghead-tools{display:contents}
  .oacts .ghead-tools .gt-post{margin-left:0}
  .oacts .gkebab-wrap{order:1;flex:0 0 auto}
  .oacts .gkebab{width:44px;height:44px;border:0;border-radius:var(--r-btn);background:var(--btn-2-bg);color:var(--btn-2-ink)}
  @media (hover:hover){.oacts .gkebab:hover{background:var(--btn-2-bg-hover);color:var(--btn-2-ink)}}
  .oacts .gkebab[aria-expanded="true"]{background:var(--btn-2-bg-hover);color:var(--btn-2-ink)}
  /* меню гамбургера здесь стоит у ПРАВОГО края экрана — якорим его по правому краю кнопки */
  .oacts .gkebab-menu{left:auto;right:0;transform-origin:top right}
  /* КАРТОЧКА ЛИСТА — одна белая, строки через волосок. Строки (плашки тасков, плейлист, рамки тем)
     теряют свою бумагу и тень, рельс типа таска остаётся (смысл «нужна сдача», проба рельса). */
  .opane .oents{background:var(--card);border-radius:var(--r-tile);overflow:hidden;gap:0}
  .opane-body > .oents{flex:none}   /* в столбце basis 0 схлопнул бы карточку в ноль */
  .opane .oents > .oent,.opane .oents .ofcards > .oent{margin:0;border:0;border-radius:0;background:transparent;
    box-shadow:none;border-bottom:1px solid var(--line-soft);padding:12px 14px 12px 22px;
    --rail-x:8px;--rail-y:12px;--rail-w:4px}   /* рельс тоньше: в строке карточки он метка типа, не край листа */
  .opane .oents > :last-child,.opane .oents .ofcards > .oent:last-child{border-bottom:0}
  .otwo > .opane:first-child .oent{box-shadow:none}
  @media (hover:hover){
    .opane .oents .oent-open:hover,.opane .oents .oent-live:hover,.opane .oents .oent.pool-open:hover,
    .otwo > .opane:first-child .oents .oent-open:hover,.otwo > .opane:first-child .oents .oent.pool-open:hover{
      box-shadow:none;background:var(--surface-2)}
  }
  .opane .oents .oent-open:focus-visible{box-shadow:var(--ring-inset)}
  .opane .oents .oent-open:active:not(:has(a:active,.ofile-a:active)),
  .opane .oents .oent-live:active:not(:has(a:active,.ofile-a:active)){transform:none;background:var(--surface)}
  /* рамка темы внутри карточки — секция с подписью, а не скобка */
  .opane .oents > .oframe{margin:0;padding:10px 0 0;border-bottom:1px solid var(--line-soft)}
  .opane .oents > .oframe::before{display:none}
  .opane .oents > .oframe > .ofh{text-align:left;padding:0 14px 2px 22px;font-size:var(--fs-xs);font-weight:700;color:var(--muted)}
  .opane .oents > .oframe > .ofcards{gap:0}
  .opane .oents > .omut{padding:12px 14px}
  .oent-h .oent-t.big{font-size:var(--fs-md);font-weight:600;line-height:1.3}
  /* ПОЛОСА ПРОГРЕССА — тот же счёт, что «N/M» в строке таска (i.done / i.assigned, oEntityCard):
     дорожка серой кнопки, заполнение цветом группы, у «все сделали» — зелёным. Не новое число. */
  .obar{display:flex;height:6px;margin-top:8px;border-radius:var(--r-pill);background:var(--btn-2-bg);overflow:hidden}
  .obar > span{display:block;background:var(--group)}
  .obar.full > span{background:var(--ok-solid)}
  /* ПЛЕЙЛИСТ — первая строка карточки (или последняя у пустого): кружок цвета группы со значком,
     название 15/600, подпись 13 тихим тоном. Радужный рельс заменил кружок. */
  .opane .oents > .oent.pool{padding:12px 64px 12px 62px;color:var(--ink);margin:0}
  .opane .oents > .oent.pool::before{display:none}
  .opane .oents > .oent.pool .pool-door{font-size:var(--fs-md);font-weight:600;min-height:20px}
  .opane .oents > .oent.pool .eico{position:absolute;left:14px;top:12px;width:36px;height:36px;border-radius:50%;
    display:flex;align-items:center;justify-content:center;background:var(--group);color:var(--on-group)}
  .opane .oents > .oent.pool .oweek{margin-top:1px;font-size:var(--fs-sm);font-weight:400;color:var(--faint)}
  .opane .oents > .oent.pool.pbot,.opane .oents > .oent.pool.ptop{margin:0}
  /* то же для листов без карточки тасков (черновик, прошло без ДЗ, пустой день): плашка плейлиста —
     своя белая карточка */
  .opane > .oent.pool{margin:0 0 var(--sp-3);border:0;border-radius:var(--r-tile);background:var(--card);box-shadow:none}
  .opane > .oent.pool.pbot{margin:var(--sp-3) 0 0}
  .opane.empty > .oent.pool,.opane.past > .oent.pool{background:var(--surface-2)}
  @media(max-width:760px){
    .otwo{margin-top:var(--sp-2)}
    .opane{margin:var(--sp-1) -10px var(--sp-3)}
    .opane.empty,.opane.past,.opane.edge{margin:var(--sp-1) 0 var(--sp-3)}
    /* эскиз: Notes | Tasks в строке даты, у правого края; не влезает (тесная шапка) — переносится
       своей строкой вниз, ни на что не наезжая */
    .otwo .opane-h{flex-wrap:wrap;align-items:flex-start;row-gap:var(--sp-2)}
    .otwo .opane-h .onotes{order:2;flex:0 0 auto;width:auto;margin-left:auto}
    .otwo .opane-h .onotes .onw{flex:0 0 auto;padding:0 12px}
    .oroster{grid-template-columns:repeat(4,minmax(0,1fr))}
    /* на телефоне ряд несёт четыре кнопки — без значков, словами (эскиз) */
    .oacts .btn-ghost > .ic{display:none}
  }
  /* ПЛАНШЕТ (761–1180): листы ещё рядом, но лист узкий — дата витринным шрифтом и тумблер по центру
     (сетка 1fr · auto · 1fr ПК) делили 370px, и тумблер накрывал дату. Здесь тумблер встаёт СВОЕЙ
     строкой над датой (одно место в обоих режимах — не ездит), а у правого листа та же строка
     зарезервирована пустой: ростеры обоих листов начинаются на одной высоте. */
  @media (min-width:761px) and (max-width:1180px){
    .otwo .opane-h{display:flex;flex-wrap:wrap;align-items:flex-start;column-gap:6px;row-gap:var(--sp-2);
      min-height:calc(44px + var(--sp-2) + 62px)}
    .otwo .opane-h .opane-d{flex:0 1 auto;min-width:0}
    .otwo .opane-h .opane-hr{flex:1 1 0}
    .otwo .opane-h .onotes{order:-1;flex:1 0 100%}
    .otwo .opane-h .onotes .onw{flex:1 1 0}
    .otwo .opane-h:not(:has(.onotes))::before{content:"";order:-1;flex:1 0 100%;height:44px}
  }
