/* style-08-modals.css — часть бывшего style.css (распил 22.10.2026): окна и шторки, затемнение, PDF-просмотрщик.
   Файлы style-NN-*.css — ОДИН каскад: index.html подключает их подряд и по номерам, порядок значим
   (правило ниже по каскаду перебивает равное выше). Стережёт tools/check.js. */
  /* blox29 #1 — окно таска держит НИЗ на месте при смене ВИДа МЕХАНИЧЕСКИ, без ручной подгонки высот.
     Раньше это делали ПЯТЬ взаимозависимых констант (поле 70/210, резерв 325/340, компенсация 185/200,
     проводник 224/170): меняешь одну — пересчитывай остальные руками, иначе низ окна прыгает, и это
     видно только по скриншоту (история: blox28 #5/#9/#14 — всё ручные перетюнинги этих чисел). Теперь:
     обёртка .taskmid с ФИКСИРОВАННЫМ минимальным резервом, а #a-fields — единственный растущий ребёнок
     (flex:1), который сам добирает/отдаёт разницу. Стало выше поле «Текст» — середина ужалась ровно на
     столько же; сумма (а значит и низ окна) неизменна САМА, без правки резерва. Резерв — ОДНО число на
     ширину (телефон/ПК), заданное под самый высокий вид «Файл»; всё прочее выводится из него. */
  /* ЗАЗОР ОКНА над и под панелью — одно число, --modal-gap (:root). На ПК он 10px вместо 22:
     окну таска и окну учебника на ноутбуке не хватало высоты, а 24px воздуха ничего не решали
     (владелец, 15.09.2026: «прибавку на ПК делаем»). Телефон НЕ трогаем — там 22px остаются.
     Честная сноска: правило .emodal выше с max(36px, safe-area) ЭТОЙ строкой перекрыто давно, на
     телефоне зазор тоже 22px (замер 375px: paddingTop 22px) — если ✕ окажется под часами в
     установленном приложении, чинить надо здесь, а не там. Потолки панелей ниже считают от того же
     токена, чтобы менять зазор в одном месте. */
  .emodal{padding-top:var(--modal-gap);padding-bottom:var(--modal-gap)}
  .emodal .panel .field{margin-bottom:10px}
  /* blox30 #1 — окно таска ВЛЕЗАЕТ в экран целиком, даже на коротком ноутбуке. Раньше .taskmid был
     ЖЁСТКИЕ 395px, и всё окно (≈725px) не помещалось: на экране ниже ~725px кнопка «Добавить» уходила
     за нижний край (см. Task.jpg — виден только «Челлендж», кнопки нет). Теперь панель таск-модалки
     (и добавление, и редактирование — обе несут .taskmid) ограничена высотой вьюпорта, а ужимается
     ТОЛЬКО средний резерв .taskmid: он отдаёт высоту, проводник Я.Диска внутри скроллится (overflow
     уже включён), а низ — тип таска, челлендж, «Добавить» — прибит и всегда виден. На высоком экране
     резерв держит свои 395px (flex-grow:0), так что механизм blox29 #1 (низ не прыгает при смене вида)
     сохранён. Крайний случай сверхкороткого экрана деградирует мягко: .emodal сам скроллится (overflow
     :auto), как и раньше. */
  .emodal .panel:has(.taskmid){display:flex;flex-direction:column;max-height:calc(100dvh - 2 * var(--modal-gap))}
  .emodal .panel:has(.taskmid) > *{flex-shrink:0}                       /* низ окна не жмётся */
  .emodal .panel:has(.taskmid) > .taskmid{flex:0 1 395px;min-height:0}  /* ужимается только резерв */
  @media(max-width:859px){ .emodal .panel:has(.taskmid) > .taskmid{flex-basis:410px} }
  /* display:flex column — внутренняя раскладка резерва: поле «Текст» + растущий #a-fields (flex:1),
     который сам добирает/отдаёт разницу при смене вида; overflow:auto проводника даёт ему явный потолок. */
  /* blox34 #10 — резерв ещё и СКРОЛЛИТСЯ внутри себя. Раньше он просто сжимался, и пока снизу
     ничего не прибавлялось, этого хватало; но галочка «Личное» открывает список учеников (~130px),
     резерв уходил ниже высоты своего содержимого — и панель файла НАЕЗЖАЛА на пикер типа (G9).
     Теперь лишнее прячется в скролл середины: низ окна остаётся на месте, ничего не перекрывается. */
  .taskmid{display:flex;flex-direction:column;overflow-y:auto}

  /* ── ОКНО ТАСКА ВПИСАНО В ЛИСТ ДЗ (blox55 #23) ───────────────────────────────────────────────
     Жалоба владельца: «окно постановки таска должно вписываться в размер текущего листа дз, чтобы
     учитель мог одновременно видеть левую половину монитора (прошлое дз или notes)». Замер на
     1366×768: листы по 650px (22–672 и 695–1344), окно — 263–1103 шириной 840, то есть от левого
     листа оставалась полоска в 240px. Теперь окно встаёт РОВНО в полосу своего листа: тот, на
     котором открыт конструктор (.opane.build) — он не всегда правый, учитель может строить ДЗ и
     на левом. Координаты приезжают из builder.js двумя переменными (лист меряется по месту, а не
     вычисляется из ширины экрана: между листами стоит ось со стрелками, и делить экран пополам —
     врать на 20–40px). Значения ниже — запасные, на случай если разметка встретится без JS.
     По вертикали НЕ вписываем: лист длиннее экрана, вписывать не во что.
     ВКЛЮЧАЕТСЯ ЭТО НЕ ВЕЗДЕ: узкий лист (<440px) и совсем низкое окно (<420px) окна не кормят, а
     между 420 и 600 вписывание даётся только тонкому указателю — мыши. Пороги и почему их два,
     а не один (ноутбук владельца, 13.09.2026) — в builder.js рядом с DOCK_MIN_H; здесь важно
     одно: признак ставит JS, и если он его не поставил, окно ведёт себя как до blox55 #23. */
  :root{--dock-l:0px;--dock-w:100%;--dock-top:96px;--modal-gap:22px}
  @media(min-width:860px){ :root{--modal-gap:10px} }
  body.taskdock .emodal{background:transparent;display:block;overflow:visible;padding:0}
  /* ВУАЛЬ ТОЛЬКО НАД СВОИМ ЛИСТОМ. Затемнять весь экран здесь бессмысленно: ради того, чтобы
     соседний лист было ВИДНО, всё и затевалось. Полоса шире листа на 10px с каждой стороны —
     иначе её край совпал бы с краем окна и читался вторым контуром. */
  body.taskdock .emodal::before{content:"";position:fixed;top:0;bottom:0;
    left:calc(var(--dock-l) - 10px);width:calc(var(--dock-w) + 20px);background:var(--scrim)}
  body.taskdock .emodal .panel{position:fixed;top:var(--modal-gap);left:var(--dock-l);width:var(--dock-w);
    max-width:none;max-height:calc(100dvh - 2 * var(--modal-gap));box-shadow:var(--sheet-4);
    /* окно становится системой отсчёта для своей начинки: раскладка плиток вида ниже спрашивает
       ширину ОКНА, а не экрана (пикер типов меряет себя сам — blox80 #8, builder.js fitRecBoxes) */
    container-type:inline-size}
  /* РАСКЛАДКА ПО ШИРИНЕ ОКНА, А НЕ ЭКРАНА. Восемь кнопок типа в одну строку (правило
     @media(min-width:860px) выше) считали ширину ЭКРАНА — а окно теперь шириной с лист: 650px на
     ноутбуке, 479 на айпаде. Восемь колонок по 55px нечитаемы. Здесь тот же телефонный расклад
     парами, но включает его ширина самого окна. Стоит ПОСЛЕ @media(min-width:860px) намеренно:
     специфичность у правил одинаковая, спор решает порядок. */
  @container (max-width:819px){
    .emodal .typepick button{flex-direction:column;padding:8px 6px;gap:4px}
  }
  /* СОСЕДНИЙ ЛИСТ ОСТАЁТСЯ ЖИВЫМ, НО ТОЛЬКО НА ЧТЕНИЕ (решение владельца: «клик за пределами окна
     кроме кнопки notes/tasks не делает ничего, но прокрутка содержания левого листа доступна»).
     z-index поднимает лист НАД окном-заслонкой (.emodal, z:60) — иначе заслонка съедала бы и
     прокрутку, и тумблер. Ниже все органы управления листа обесточиваются, кроме самого тумблера:
     редактировать чужой день, пока строишь этот, всё равно нельзя. */
  .otwo .opane.dockside{position:relative;z-index:61;display:flex;flex-direction:column;
    max-height:calc(100vh - var(--dock-top) - 12px)}
  .otwo .opane.dockside > .opane-h{flex:0 0 auto}      /* дата + тумблер всегда на виду */
  .otwo .opane.dockside > .opane-body{flex:1 1 auto;min-height:0;overflow-y:auto;
    overscroll-behavior:contain}
  /* opacity — не украшение, а ЧЕСТНОСТЬ (lingo-ui #12: контрол обязан выглядеть контролом).
     Обесточенная кнопка, которая выглядит живой, — мёртвый клик: человек жмёт «удалить» на
     соседнем листе и не понимает, почему ничего не произошло. Приглушаем ТОЛЬКО органы
     управления; текст, карточки и сводка досье остаются в полную силу — их пришли читать. */
  .otwo .opane.dockside button:not(.onotes):not(.onw),
  .otwo .opane.dockside a, .otwo .opane.dockside input, .otwo .opane.dockside select,
  .otwo .opane.dockside textarea{pointer-events:none;opacity:.55}
  /* перерисовка B (агент 3): плашки тасков и плейлиста — не кнопки, а div с обработчиком тапа
     («сдачи по таску», дверь в «Плейлист»), и правило выше их не обесточивало. Пока ростер стоял
     колонкой справа, под пальцем после прокрутки соседнего листа оказывался чип; теперь — строка
     таска, и тап открывал экран таска поверх окна. Обесточиваем и их (прокрутку колесом это не
     трогает: событие уходит в прокручиваемое тело листа под ними); текст остаётся в полную силу. */
  .otwo .opane.dockside :is(.oent-open,.oent-live,.pool-open){pointer-events:none}
  /* ПРОВОДНИК Я.ДИСКА ИЗ ОКНА ТАСКА — В ТУ ЖЕ ПОЛОСУ (blox55 #23, п.4). Шторка проводника общая:
     её же открывают плейлист и проверка сдачи — там она обязана остаться во весь экран. Поэтому
     правило висит на body.taskdock: признак стоит ТОЛЬКО пока открыто вписанное окно таска. */
  body.taskdock .overlay.exp-shutter{align-items:flex-start;justify-content:flex-start}
  body.taskdock .overlay.exp-shutter .exp-sheet{max-width:none;width:var(--dock-w);
    margin-left:var(--dock-l);margin-top:var(--modal-gap);height:calc(100dvh - 2 * var(--modal-gap));max-height:calc(100dvh - 2 * var(--modal-gap))}
  /* #a-fields / #e-fields — растущая середина: flex:1 «съедает» слабину, поэтому низ окна не двигается.
     Внутри — тоже flex-колонка, чтобы вид «Файл» отдал проводнику всю доступную высоту (см. .afld-file).
     (flex-контейнер не схлопывает margin'ы детей — прежний display:flow-root больше не нужен, blox25 #8
     решается сам.) */
  .afields{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
  .afields > .field{margin-bottom:0}
  /* blox42 — строка-индикатор распознанной ссылки под полем «Текст»: «link: …». Место занято
     ВСЕГДА (visibility, не display) — появление/пропажа не двигает середину (lingo-ui §1);
     не переносится и не растит окно вширь: nowrap + ellipsis (решение владельца). */
  .linkline{visibility:hidden;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
    min-height:17px;font-size:var(--fs-sm);color:var(--accent-dark);margin-top:3px}
  .linkline.on{visibility:visible}
  /* вид «Файл»: поле-обёртка и панель проводника растягиваются на всю середину, поэтому карточка
     выбранного файла / сообщение «Забираем файл…» ужимают САМ проводник (он flex внутри, см.
     playlist.css), а не роняют низ окна вниз — поведение blox28 #14/#5 теперь СЛЕДСТВИЕ того же
     механизма, без отдельной пары высот 224/170. */
  .afld-file{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
  .afld-file > #a-fb, .afld-file > #e-fb{flex:1 1 auto;min-height:0}
  /* blox25 (фидбек) — переключатель ЧЕЛЛЕНДЖА шёл вплотную под кнопками ТИПА (особенно в окне
     редактирования, где нет резерва середины) и «придавливался» к ним. Даём пикеру типа отступ
     снизу — челлендж отделён от кнопок типа. Отступ одинаков для всех видов, высоты не рассинхронит. */
  .recwrap{margin-top:2px;margin-bottom:14px}

  /* modal overlay + bottom sheet (turn-in / review) */
  /* ЗАТЕМНЕНИЕ ПОД МОДАЛКОЙ — тон тени СВОЕЙ схемы (blox52 #4, п.6). Было зашито
     виноградным числом: в холодных схемах накладка тянула тёплый фиолетовый, а в ночной
     оставалась светлее нужного. */
  .overlay{position:fixed;inset:0;background:var(--scrim);display:flex;align-items:flex-end;
    justify-content:center;z-index:50}
  /* ШТОРКА — СКРУГЛЕНИЕ ПО РАНГУ (redesign 27.10): лист поверх всего экрана берёт верхнюю ступень
     лестницы --r-xl (та же, что у окна .panel), карточки внутри — --r-md. Потолок высоты — ВИДИМЫЙ
     экран (dvh): у айфона 90vh выше видимой части на панель Safari, и низ шторки с кнопкой «Готово»
     уезжал под неё. 90vh остаётся строкой выше для браузера без dvh. */
  .sheet{background:var(--card);width:100%;max-width:520px;border-radius:var(--r-xl) var(--r-xl) 0 0;padding:18px;
    max-height:90vh;max-height:90dvh;overflow:auto;box-shadow:var(--sheet-rise)}

  /* ── ПОЯВЛЕНИЕ ШТОРОК И ОКОН (redesign 27.10) ─────────────────────────────────────────────
     Затемнение и лист проявляются вместе (--dur-panel, --ease-enter); почему лист не едет — ниже. ПОЧЕМУ НЕ @starting-style НА САМОМ ЛИСТЕ: многие шторки перерисовывают себя
     целиком (ov.innerHTML = … в roll.js, copy-pick.js, send-assignment.js, student.js) — на каждый
     тап по строке рождается НОВЫЙ .sheet, и @starting-style заново проигрывал бы появление: шторка
     мигала бы на каждое действие (lingo-ui §1 «перерисовка не мигает»). Поэтому движение живёт на
     ОВЕРЛЕЕ — он создаётся один раз на открытие: его анимация ведёт наследуемое число --ov-in
     (1 → 0), а лист только читает его. Лист, пересобранный позже, застаёт --ov-in уже нулём и
     встаёт сразу, без повтора. Под «уменьшить движение» анимация объявлена тихой версией
     (--motion-reduced, линейная) — то же проявление.
     УХОДА НЕТ, и это честно: шторки закрываются ov.remove() в двух десятках модулей, узел исчезает
     мгновенно, и CSS не может проиграть уход того, чего уже нет в документе. Общий хелпер закрытия —
     дневная работа (журнал redesign, агент 3). */
  @property --ov-in{syntax:"<number>";inherits:true;initial-value:0}
  @keyframes ov-in{from{--ov-in:1;background-color:transparent}to{--ov-in:0}}
  .overlay,.emodal{--ov-in:0;animation:ov-in var(--dur-panel) var(--ease-enter) backwards}
  /* ЛИСТ ТОЛЬКО ПРОЯВЛЯЕТСЯ — БЕЗ СДВИГА И МАСШТАБА, и это два замера, а не вкус.
     1) Сдвиг вниз на --enter-shift-lg вывел шторку, прижатую к низу, за край экрана на 24px на
        первые ~280мс — это поймала проба «перекличка» (monitor-toolbar: «шторка вылезает из окна
        391…804 при 780»), а для человека это «не помещается на экран» (lingo-ui §3, G1v).
     2) Любой translate/scale, кроме none, — даже «нулевой» в покое — делает лист системой отсчёта
        для position:fixed внутри него. А внутри живут фиксированные вещи: всплывашка типа таска на
        телефоне (.recbox.pop .recpick/.recveil в окне таска) и меню проводника (.exp-menu в
        .exp-sheet) — они встали бы относительно листа, а не экрана. Прозрачность 1 в покое не
        создаёт ничего, поэтому лист читает --ov-in только как opacity.
     .mediabox — просмотр сданного файла и «Слушать подряд» (вид — style-02). */
  .overlay > .sheet,.overlay > .pdfbox,.overlay > .mediabox,.emodal > .panel{opacity:calc(1 - var(--ov-in))}
  /* вписанное окно таска (body.taskdock) затемняет полосу своим ::before — пусть проявляется и оно */
  body.taskdock .emodal::before{opacity:calc(1 - var(--ov-in))}
  @media (prefers-reduced-motion:reduce){
    .overlay,.emodal{--motion-reduced:ov-in var(--dur-panel) linear backwards}}

  /* blox59 #2 — шторка «Отправить лист ДЗ в группу»: СТРОКА НА ГРУППУ. Лентой (.gstrip) чипы тут
     были до 16.09.2026, и дате рядом с ними места не было: лента едет вбок и обрезает всё, что не
     влезло. Столбец даёт каждой группе свою строку «чип + дата ближайшего урока» и растёт вниз,
     внутри собственной прокрутки шторки. */
  .sg-list{display:flex;flex-direction:column;gap:8px;margin:0 0 14px;max-height:52vh;overflow:auto}
  .sg-row{display:flex;align-items:center;gap:8px}
  /* чип занимает остаток строки и обрезает длинное имя многоточием: дата справа не должна
     уезжать за край шторки из-за названия группы */
  .sg-chip{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:7px;text-align:left}
  .sg-chip .sgname{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .sg-chip .sgmark{display:flex;color:var(--line)}          /* «не отмечено» — кружок есть, он погашен */
  .sg-chip.on{border-color:var(--accent-deep)}
  .sg-chip.on .sgmark{color:var(--accent-ink)}
  .sg-chip[disabled]{opacity:.55;cursor:default}
  .sg-date{flex:0 0 auto;display:flex;align-items:center;gap:6px;min-height:44px;padding:9px 12px;
    border:1px solid var(--line-soft);border-radius:var(--r-md);background:var(--surface);
    font:inherit;color:var(--ink);cursor:pointer;white-space:nowrap}
  .sg-date.open{border-color:var(--accent-deep);background:var(--accent-tint)}
  .sg-nodate,.sg-done{flex:0 0 auto;color:var(--muted);font-size:var(--fs-sm);white-space:nowrap}
  .sg-done{color:var(--ok);display:flex;align-items:center;gap:5px}
  .sg-row.is-sent{opacity:.6}
  .sg-err{color:var(--danger);font-size:var(--fs-sm);margin:-4px 0 0 2px}
  .sg-lessons{display:flex;flex-direction:column;gap:6px;margin:-2px 0 4px 14px}
  .sg-lesson{display:flex;align-items:center;gap:8px;text-align:left;padding:10px 12px;border:1px solid var(--line-soft);
    border-radius:var(--r-md);background:var(--surface);font:inherit;cursor:pointer;min-height:44px}
  .sg-lesson.on{border-color:var(--accent-deep);background:var(--accent-tint)}

  /* blox73 #2 — шторка «Подтянуть задания» (copy-pick.js), макет v5 утверждён владельцем 07.10.2026.
     Шторка — колонка: шапка и низ с кнопкой стоят на месте, едет только список уроков (.cpk-list),
     иначе на телефоне «Подтянуть N тасков» уезжала бы под длинный список. Тема — ТА ЖЕ рамка-скобка,
     что в листе ДЗ (открыта слева, скругление справа, --frame-line), карточка — рельс цвета плашки
     (--rail = itemColor). Отметка — якорь схемы: он тёмный в каждой схеме, белая галочка на нём
     читается везде. Повтор — серым, предупреждение при его отметке — тинт тревоги (не сплошная
     заливка: это «посмотри», а не «сломалось»). */
  .cpk-sheet{display:flex;flex-direction:column;overflow:hidden;padding:0}
  .cpk-head{flex:0 0 auto;align-items:flex-start;gap:12px;padding:16px 16px 6px 20px;margin:0}
  .cpk-h{display:flex;flex-direction:column;gap:4px;min-width:0}
  .cpk-h>b{font-size:var(--fs-lg)}
  .cpk-sub{font-size:var(--fs-sm);color:var(--muted)}
  .cpk-sub b{color:var(--ink)}
  .cpk-head .x{flex:0 0 auto;width:44px;height:44px;border-radius:var(--r-sm);background:var(--fill-soft)}
  .cpk-list{flex:1 1 auto;min-height:0;overflow:auto;display:flex;flex-direction:column;gap:10px;padding:10px 12px 12px}
  .cpk-day{flex:0 0 auto;border:1px solid var(--line-soft);border-radius:var(--r-md);overflow:hidden}
  .cpk-dayh{display:flex;align-items:center;gap:10px;width:100%;min-height:56px;padding:6px 14px;border:0;
    background:var(--fill-soft);font:inherit;color:var(--ink);text-align:left;cursor:pointer}
  .cpk-chev{display:flex;color:var(--muted);transition:transform var(--t-fast) var(--ease)}
  .cpk-chev.open{transform:rotate(90deg)}
  .cpk-dayt{flex:1 1 auto;display:flex;flex-direction:column;gap:1px}
  .cpk-dayn{font-size:var(--fs-sm);color:var(--muted)}
  .cpk-badge{flex:0 0 auto;font-size:var(--fs-xs);font-weight:700;color:var(--on-dark);background:var(--anchor);
    border-radius:var(--r-pill);padding:4px 9px}
  .cpk-pick{appearance:none;-webkit-appearance:none;margin:0;flex:0 0 auto;width:24px;height:24px;
    border:2px solid var(--line);border-radius:var(--r-xs);background:var(--card);display:grid;place-items:center;cursor:pointer}
  .cpk-pick:checked,.cpk-pick.part{background:var(--anchor);border-color:var(--anchor)}
  .cpk-pick:checked::after{content:"";width:6px;height:11px;border:solid var(--on-dark);border-width:0 2px 2px 0;
    transform:translateY(-1px) rotate(45deg)}
  .cpk-pick.part:not(:checked)::after{content:"";width:10px;height:2px;background:var(--on-dark);border-radius:var(--r-2xs)}
  .cpk-frame{margin:12px 10px 6px 4px;padding:2px 12px 12px 0}   /* скобка — общее правило рамок (.theme::before) */
  .cpk-thead{position:relative;display:flex;align-items:center;min-height:48px;padding:4px 0 4px 34px;cursor:pointer}
  .cpk-thead .cpk-pick{position:absolute;left:8px}
  .cpk-tname{flex:1 1 auto;text-align:center;font-weight:800;font-size:var(--fs-md);padding-right:8px}
  .cpk-cards{display:flex;flex-direction:column;gap:8px;padding-left:26px}
  .cpk-loose{margin:12px 22px 6px 4px}
  .cpk-loose .cpk-cards{padding-left:26px}
  .cpk-card{position:relative;background:var(--card);border:1px solid var(--line-soft);border-radius:var(--r-sm);overflow:hidden}
  .cpk-card::before{content:"";position:absolute;left:5px;top:9px;bottom:9px;width:7px;border-radius:var(--r-2xs);background:var(--rail)}
  .cpk-row{display:flex;align-items:center;gap:12px;min-height:56px;padding:8px 10px 8px 20px;cursor:pointer}
  .cpk-txt{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:3px}
  .cpk-title{font-size:var(--fs-md);font-weight:600;line-height:1.3;overflow-wrap:anywhere}
  .cpk-kind{font-size:var(--fs-sm);color:var(--muted)}
  .cpk-note{font-size:var(--fs-xs);font-weight:600;color:var(--muted);display:flex;align-items:center;gap:5px}
  .cpk-card.is-dupe .cpk-title{color:var(--muted)}
  .cpk-card.is-dupe .cpk-pick{border-color:var(--line-soft);background:var(--fill-soft)}
  .cpk-warn{margin:0 10px 10px 52px;padding:8px 10px;border-radius:var(--r-sm);background:var(--alert-tint);
    border:1px solid var(--alert-line);color:var(--alert-ink);font-size:var(--fs-sm);line-height:1.35;
    display:flex;gap:8px;align-items:flex-start}
  .cpk-warn .ic{flex:0 0 auto;margin-top:1px}
  .cpk-foot-note{display:flex;gap:8px;align-items:flex-start;padding:4px 6px;font-size:var(--fs-sm);line-height:1.4;color:var(--muted)}
  .cpk-foot-note .ic{flex:0 0 auto;color:var(--gold)}
  .cpk-err{flex:0 0 auto;margin:0 16px}
  .cpk-bar{flex:0 0 auto;display:flex;gap:10px;padding:12px 16px calc(16px + env(safe-area-inset-bottom, 0px));
    border-top:1px solid var(--line-soft)}
  .sheet .cpk-bar .btn-ghost{width:auto;flex:0 0 auto;margin:0}
  .sheet .cpk-bar .btn-primary{flex:1 1 auto;margin:0}

  /* blox69 #9 — шторка «Отсутствующие» (roll.js): имя в строку, тап = «нет на уроке». Отсутствие —
     это СОСТОЯНИЕ, а не тревога (правило красной заливки), поэтому тинт + рамка, без сплошного
     красного. Список прокручивается сам, «Готово» всегда на виду, как в шторке «Duplicate». */
  .rl-hint{color:var(--muted);font-size:var(--fs-sm);margin:-4px 0 10px}
  .rl-list{display:flex;flex-direction:column;gap:6px;margin:0 0 14px;max-height:56vh;overflow:auto}
  .rl-row{border:1px solid var(--line-soft);border-radius:var(--r-md);background:var(--surface)}
  .rl-row.off{border-color:var(--alert);background:var(--alert-tint)}
  .rl-name{display:flex;align-items:center;gap:8px;width:100%;min-height:44px;padding:9px 12px;
    border:0;background:none;font:inherit;color:var(--ink);text-align:left;cursor:pointer}
  .rl-nm{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
  .rl-st{flex:0 0 auto;color:var(--muted);font-size:var(--fs-sm)}
  .rl-row.off .rl-st{color:var(--alert-ink);font-weight:600}
  .rl-why{display:flex;flex-wrap:wrap;gap:6px;padding:0 10px 10px}
  .rl-rsn{min-height:44px;padding:8px 12px;border:1px solid var(--line-soft);border-radius:var(--r-md);
    background:var(--card);font:inherit;color:var(--ink);cursor:pointer}
  .rl-rsn.on{border-color:var(--accent-deep);background:var(--accent-tint);font-weight:600}
  /* 16px — иначе Safari увеличит страницу при фокусе и не вернёт (правило 3 lingo-ui) */
  .rl-note{flex:1 1 100%;min-height:44px;padding:8px 10px;border:1px solid var(--line-soft);
    border-radius:var(--r-md);background:var(--card);font:inherit;font-size:var(--fs-input);color:var(--ink)}
  .rl-sheet #rl-go{width:100%}
  /* кнопка переклички в шапке урока на мониторе: после «Готово» показывает итог */
  .oroll.done{color:var(--muted)}
  .oroll.done.some{color:var(--alert-ink)}
  /* список пропусков под плитками отчёта родителя и Stats учителя (student.js absListHTML) */
  .pabs{margin:12px 0 0;display:flex;flex-direction:column;gap:4px}
  .pabs-h{font-weight:700;margin-bottom:2px}
  .pabs-row{font-size:var(--fs-sm);overflow-wrap:anywhere}
  .pabs-note{color:var(--muted)}
  @media(min-width:560px){ .overlay{align-items:center} .sheet{border-radius:var(--r-lg)} }
  .sheethead{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;font-size:var(--fs-lg)}
  /* blox68 #6 — ШТОРКА ПЛЕЕРА ЗАДАНИЯ (student.js hwSheet): плеер/диктофон с «Мои фразы».
     Потолок — видимый экран (dvh: у айфона vh больше видимого на панель Safari), прокручивается
     ТЕЛО, а шапка с ✕ стоит на месте: полоса фраз растёт с каждой записью. Свою рамку плеера
     (.lp — карточка с тенью) внутри шторки снимаем: карточка в карточке. */
  /* потолок — ниже часов и выреза (safe-area): иначе на маленьком айфоне ✕ шапки вставал под строку
     состояния, где касания забирает сам айфон; низ — выше полоски «домой» */
  .hwpsheet{display:flex;flex-direction:column;overflow:hidden;
    max-height:calc(100dvh - max(24px, env(safe-area-inset-top) + 8px));
    padding-bottom:max(18px, env(safe-area-inset-bottom))}
  .hwpsheet .sheethead{flex:0 0 auto;gap:10px}
  .hwpsheet .sheethead b{display:flex;align-items:center;gap:6px;min-width:0}
  .hwpsheet .sheethead .x{flex:0 0 auto;width:44px;height:44px;border-radius:50%}   /* палец (WCAG 2.5.5) */
  .hwpbody{flex:1 1 auto;min-height:0;overflow:auto;overscroll-behavior:contain}
  .hwp-what{color:var(--muted-strong);font-size:var(--fs-sm);line-height:1.45;margin:-4px 0 10px;
    white-space:pre-line;overflow-wrap:anywhere;display:-webkit-box;-webkit-line-clamp:3;
    -webkit-box-orient:vertical;overflow:hidden}
  .hwpsheet .lp{box-shadow:none;padding:0;max-width:none;background:none}
  /* blox37 #1 — шторка «Сообщить о проблеме». Своей геометрии почти нет: .sheet уже держит
     max-height:90vh + overflow:auto, поэтому окно не перерастёт экран ни на телефоне, ни на ПК. */
  .supsheet .sheethead .x{width:44px;height:44px;border-radius:50%}   /* палец (WCAG 2.5.5) */
  .sup-where{color:var(--muted);font-size:var(--fs-sm);margin-bottom:8px}
  .sup-where b{color:var(--ink)}
  /* 16px — не вкус: ниже этого Safari на айфоне зумит страницу при фокусе и обратно не отматывает */
  .sup-text{width:100%;box-sizing:border-box;font:16px/1.4 inherit;color:var(--ink);
    border:1px solid var(--line);border-radius:var(--r-md);padding:10px 12px;resize:vertical;
    background:var(--surface-2)}
  .sup-text:focus{outline:none;border-color:var(--accent-deep)}
  .sup-err{color:var(--danger-ink);font-size:var(--fs-md);font-weight:700;margin-top:8px}
  .sup-ok{text-align:center;padding:22px 6px;font-size:var(--fs-lg);font-weight:700;color:var(--ink)}
  .sup-ok span{display:inline-block;margin-top:6px;font-weight:400;font-size:var(--fs-md);color:var(--muted)}
  /* blox70 #8 — «Мои обращения». Вкладки — общий .tabs; список скроллится внутри .sheet (90vh), так
     что длинная переписка не выталкивает шторку за экран ни на телефоне, ни на ПК. */
  .sup-tabs{margin:0 0 12px}
  .sup-cnt{color:var(--accent-deep)}
  .sup-empty{color:var(--muted);font-size:var(--fs-md);text-align:center;padding:22px 6px}
  .sup-t{border:1px solid var(--line);border-radius:var(--r-md);background:var(--card);margin-bottom:8px}
  .sup-t--new{border-color:var(--accent-line)}
  .sup-th{display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px;width:100%;min-height:44px;
    padding:10px 12px;background:none;border:none;text-align:left;cursor:pointer;color:var(--ink)}
  .sup-st{font-size:var(--fs-xs);font-weight:700;border-radius:var(--r-sm);padding:1px 7px;
    background:var(--accent-tint);color:var(--accent-ink)}
  .sup-st--closed{background:var(--fill-soft);color:var(--muted-strong)}
  .sup-td{font-size:var(--fs-xs);color:var(--muted)}
  .sup-nb{font-size:var(--fs-xs);font-weight:700;color:var(--accent-deep)}
  .sup-tt{flex:0 0 100%;font-size:var(--fs-md);overflow-wrap:anywhere;display:-webkit-box;
    -webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
  .sup-th[aria-expanded="true"] .sup-tt{display:none}
  .sup-tb{padding:0 12px 12px}
  .sup-thread{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}
  .sup-m{max-width:88%;align-self:flex-start;background:var(--surface-2);border-radius:var(--r-md);padding:7px 10px}
  .sup-m--staff{align-self:flex-end;background:var(--accent-tint)}
  .sup-m--new{box-shadow:0 0 0 2px var(--accent-line)}
  .sup-mw{display:block;font-size:var(--fs-2xs);color:var(--muted-strong);margin-bottom:2px}
  .sup-mw b{color:var(--accent-deep)}
  .sup-mb{font-size:var(--fs-md);white-space:pre-wrap;overflow-wrap:anywhere}
  .sup-addgo{margin-top:8px}
  /* blox80 #8 — картинка объявления на весь экран: тёмный фон, картинка вписана целиком, ✕ с учётом
     выреза и строки состояния (как у .pdfbox, иначе айфон заберёт касание себе) */
  .overlay.imgov{align-items:center;justify-content:center;background:rgba(0,0,0,.9);z-index:70;
    padding:max(12px,env(safe-area-inset-top)) max(12px,env(safe-area-inset-right))
            max(12px,env(safe-area-inset-bottom)) max(12px,env(safe-area-inset-left))}
  .imgov-img{max-width:100%;max-height:100%;object-fit:contain;display:block}
  .imgov-x{position:absolute;top:max(12px,env(safe-area-inset-top));right:max(12px,env(safe-area-inset-right));
    width:44px;height:44px;border-radius:50%;border:0;background:rgba(255,255,255,.18);color:#fff;
    font-size:var(--ic-lg);cursor:pointer}
  /* PDF-просмотрщик (blox9 #5): почти во весь экран, свой ✕ и запасная ссылка «в браузере» */
  .overlay.pdfov{align-items:stretch;justify-content:stretch}
  /* отступы — с зоной часов и выреза (safe-area): приложение рисуется во весь экран (viewport-fit=cover),
     и при 12px сверху «Сохранить ↓» и ✕ вставали ПОД строку состояния — айфон забирает касания
     там себе, кнопки были недостижимы (владелец, 24.09.2026) */
  .pdfbox{position:relative;background:var(--card);display:flex;flex-direction:column;width:100%;height:100%;
    padding:max(12px,env(safe-area-inset-top)) max(12px,env(safe-area-inset-right)) 0 max(12px,env(safe-area-inset-left))}
  .pdfbox .sheethead{margin-bottom:8px}
  .pdf-head-r{display:flex;align-items:center;gap:12px}
  /* blox18 #5: обе кнопки были еле заметны — «в браузере» это теперь явная кнопка-таблетка,
     а ✕ — заметный круг с фоном и рамкой (крупная область нажатия) */
  /* blox55 #1: в PWA тот же элемент рисуется <button> («Сохранить ↓») — поэтому гасим кнопочные
     умолчания браузера (серая заливка, свой шрифт) и просим палец */
  .pdf-newtab{font-size:var(--fs-sm);font-weight:700;color:var(--accent-dark);text-decoration:none;
    border:1px solid var(--accent);border-radius:var(--r-pill);padding:8px 16px;
    background:none;font-family:inherit;cursor:pointer;justify-content:center;line-height:1.35;
    display:inline-flex;align-items:center;gap:5px;white-space:nowrap}
  .pdf-newtab:disabled{opacity:.6;cursor:default}
  @media (hover:hover){.pdf-newtab:hover{background:var(--accent-tint)}}
  .pdf-newtab{transition:background-color var(--dur-state) var(--ease-standard),transform var(--dur-release) var(--ease-spring)}
  .pdf-newtab:active:not(:disabled){transform:scale(var(--press-scale));transition-duration:var(--dur-press-in)}
  /* blox55 #4: страниц у книги нет (не нарезались / ещё режутся) — показываем сам файл, но
     говорим почему и даём выход. Полоса стоит НАД рамкой в потоке, ничего не перекрывает
     (правило «ничто не висит над содержимым») и не растёт: рамка ужимается сама, flex:1 */
  .pdf-note{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:8px;
    padding:10px 12px;border:1px solid var(--line);border-radius:var(--r-md);
    background:var(--surface-2);color:var(--ink);font-size:var(--fs-sm)}
  .pdf-note span{flex:1 1 200px}
  .pdfframe{flex:1;width:100%;border:none;border-radius:var(--r-md) var(--r-md) 0 0;background:var(--surface)}
  .sheethead .x{border:none;background:none;font-size:var(--ic-lg);cursor:pointer;color:var(--muted);
    /* ✕ ШТОРКИ — общий отклик (redesign 27.10): круглая подложка проявляется под курсором, нажатие
       вдавливает знак, кольцо фокуса — токен. Размер кнопки НЕ меняем: у части шторок ✕ голый знак
       (~24px), у части свои 44×44 — подогнать всех под один размер значило бы сдвинуть шапки
       двадцати шторок. Палец получает своё иначе — ниже. */
    position:relative;border-radius:50%;-webkit-tap-highlight-color:transparent;
    transition:background-color var(--dur-state) var(--ease-standard),color var(--dur-state) var(--ease-standard),
      transform var(--dur-release) var(--ease-spring)}
  @media (hover:hover){.sheethead .x:hover{color:var(--ink);background:var(--fill-soft)}}
  .sheethead .x:active{transform:scale(var(--press-scale-sm));
    transition-duration:var(--dur-press-in);transition-timing-function:var(--ease-standard)}
  .sheethead .x:focus-visible{outline:none;box-shadow:var(--ring)}
  /* ЗОНА КАСАНИЯ 44×44 (G5) вокруг знака любого размера: невидимый круг по центру кнопки.
     Он ничего не раздвигает — шапка шторки остаётся той же высоты, растёт только «куда можно
     попасть пальцем». */
  .sheethead .x::before{content:"";position:absolute;left:50%;top:50%;width:44px;height:44px;
    translate:-50% -50%;border-radius:50%}
  @media (prefers-reduced-motion:reduce){.sheethead .x:active{transform:none}}
  /* ✕ окна сущности (.emodal, вид — style-05) — тот же отклик, что у ✕ шторки: одна кнопка
     закрытия на всю платформу, а не две разные (lingo-ui §7) */
  .emodal .eclose{border-radius:50%;-webkit-tap-highlight-color:transparent;
    transition:background-color var(--dur-state) var(--ease-standard),color var(--dur-state) var(--ease-standard),
      transform var(--dur-release) var(--ease-spring)}
  @media (hover:hover){.emodal .eclose:hover{color:var(--ink);background:var(--fill-soft)}}
  .emodal .eclose:active{transform:scale(var(--press-scale-sm));
    transition-duration:var(--dur-press-in);transition-timing-function:var(--ease-standard)}
  .emodal .eclose:focus-visible{outline:none;box-shadow:var(--ring)}
  .emodal .eclose::before{content:"";position:absolute;left:50%;top:50%;width:44px;height:44px;
    translate:-50% -50%;border-radius:50%}
  @media (prefers-reduced-motion:reduce){.emodal .eclose:active{transform:none}}
  .pdfbox .sheethead .x{width:40px;height:40px;border-radius:50%;border:1px solid var(--line);
    background:var(--surface-2);color:var(--ink);font-size:var(--ic-lg);line-height:1;flex:0 0 auto;
    display:flex;align-items:center;justify-content:center}
  @media (hover:hover){.pdfbox .sheethead .x:hover{background:var(--fill-soft);border-color:var(--accent-deep);color:var(--accent-dark)}}
  .pdfbox .sheethead .x:active{transform:scale(var(--press-scale-sm))}
  /* blox24 #11: перетаскиваемый аудио-плеер поверх страницы учебника (наслушка + чтение вместе).
     Абсолютная плашка внутри .pdfbox; тянется за верхнюю ручку. */
  /* max-height — плашка с записью фраз (blox68 #5) бывает выше учебника: потолок держит её в рамке,
     тело .pdf-fp-body прокручивается */
  .pdf-fplayer{position:absolute;z-index:5;width:min(340px,calc(100% - 12px));max-height:calc(100% - 12px);
    background:var(--card);border:1px solid var(--line);border-radius:var(--r-md);
    box-shadow:var(--sheet-3);overflow:hidden;
    display:flex;flex-direction:column;
    transition:border-color var(--dur-state) var(--ease-standard),box-shadow var(--dur-state) var(--ease-standard),
      opacity var(--dur-panel) var(--ease-enter),translate var(--dur-panel) var(--ease-enter)}
  /* появление плашки — короткий подъём снизу (она и стоит внизу страницы); уйдёт она вместе с окном */
  @starting-style{.pdf-fplayer{opacity:0;translate:0 var(--enter-shift)}}
  .pdf-fplayer.dragging{box-shadow:var(--sheet-4);opacity:.96}
  /* blox68 #5 — на плашке идёт запись (фразы или диктофон, recorder.js ставит data-rec): рамка и
     ручка краснеют, чтобы ребёнок, глядя в текст учебника, краем глаза видел «меня пишут».
     Рамка толще ТЕНЬЮ, а не border-width: толщина бордюра сдвигала всё содержимое на пиксель */
  .pdf-fplayer[data-rec="on"]{border-color:var(--danger);box-shadow:0 0 0 1px var(--danger),var(--sheet-3)}
  .pdf-fplayer[data-rec="on"] .pdf-fp-grip{background:var(--alert-tint);color:var(--alert-ink)}
  /* ручка — ровно палец высотой (44px): за неё тащат, на её краю ✕ */
  .pdf-fp-grip{display:flex;align-items:center;justify-content:space-between;gap:8px;
    box-sizing:border-box;height:44px;flex:0 0 auto;padding:0 0 0 14px;
    background:var(--surface);border-bottom:1px solid var(--line-soft);
    color:var(--muted);font-size:var(--ic-sm);font-weight:700;cursor:grab;
    touch-action:none;user-select:none;-webkit-user-select:none;
    transition:background-color var(--dur-state) var(--ease-standard),color var(--dur-state) var(--ease-standard)}
  .pdf-fp-grip-l{display:inline-flex;align-items:center;gap:8px;min-width:0}
  /* ✕ на дальнем от заголовка краю шапки (blox24 #11): видимый кружок 30px, а нажимается вся
     клетка 44×44 у края — палец ребёнка не обязан целиться в маленький кружок */
  .pdf-fp-x{flex:0 0 auto;position:relative;width:44px;height:44px;border:none;background:none;padding:0;
    color:var(--muted);font-size:var(--ic-sm);line-height:1;cursor:pointer;
    display:flex;align-items:center;justify-content:center;touch-action:manipulation}
  .pdf-fp-x::before{content:'';position:absolute;inset:7px;border-radius:50%;border:1px solid var(--line);
    background:var(--card);z-index:-1;transition:transform var(--dur-release) var(--ease-spring),background-color var(--dur-state) var(--ease-standard)}
  .pdf-fp-x{isolation:isolate}
  .pdf-fp-x:active::before{background:var(--surface);transform:scale(var(--press-scale-sm));transition-duration:var(--dur-press-in)}
  .pdf-fp-x:focus-visible{outline:none}
  .pdf-fp-x:focus-visible::before{box-shadow:var(--ring)}
  .pdf-fplayer.dragging .pdf-fp-grip{cursor:grabbing}
  .pdf-fp-body{padding:8px 10px 10px;overflow-y:auto;min-height:0}
  .pdf-fp-body .lp{gap:8px}
  /* In headphones над учебником: аудио одной строкой, под волоском — диктофон в два ряда */
  .pdf-fp-body > .lp + .lp{margin-top:8px;padding-top:8px;border-top:1px solid var(--line-soft)}
  .sheet .btn-ghost,.sheet .btn-primary{width:100%;margin-top:6px}
  .sheet .toolbar .btn-ghost,.sheet .toolbar .btn-pub{width:auto;flex:1}


  /* ═══ ПЕРЕРИСОВКА B — ШТОРКИ И ОКНА (агент 2). Форма B: шторка снизу — радиус --r-sheet (18)
     сверху, по центру (≥560px) — со всех сторон; окно .emodal — тот же радиус. Материал и тень
     шторки прежние (--sheet-rise: она поднимается над затемнением, тень там — край, а не объём),
     движение — прежняя грамматика (motion.js, @starting-style, reduce гасит). */
  .sheet{border-radius:var(--r-sheet) var(--r-sheet) 0 0}
  @media(min-width:560px){ .sheet{border-radius:var(--r-sheet)} }
  .emodal > .panel{border-radius:var(--r-sheet)}
