  /* Design tokens live in theme.css (loaded first). Legacy aliases kept so the
     older component rules below keep resolving while they migrate. */
  :root{
    --accent-dark:var(--accent-ink);
    --checked:var(--ok);
    --red:var(--danger);
    --green:var(--ok);
  }

  /* ---- app chrome ---------------------------------------------------------- */
  /* blox31 #1: три зоны — «назад» слева, логотип строго по центру, правый кластер (dev-список,
     ник, шестерёнка) справа. Боковые колонки равной ширины (minmax(0,1fr)) держат логотип по центру
     и не дают длинному «назад» распереть шапку — лишнее обрезается многоточием. */
  /* env(safe-area-inset-*) через max() — фон шапки тянется до физических краёв (viewport-fit=cover),
     а содержимое (логотип, ник, шестерёнка) отодвинуто от выреза/скруглённого угла в горизонтали.
     Без выреза env()=0 → max() берёт базовые 14/18px, раскладка не меняется. */
  header{
    position:sticky;top:0;z-index:40;
    background:var(--grad-header);color:var(--on-dark);
    padding:max(14px,env(safe-area-inset-top)) max(18px,env(safe-area-inset-right))
            14px max(18px,env(safe-area-inset-left));
    /* Три колонки: назад · логотип · правый кластер. У ПРАВОЙ минимум = min-content, а не 0
       (blox37 #7). С minmax(0,1fr) её дорожка сжималась уже 375px, но содержимое (выпадашка
       ребёнка + шестерёнка) не сжималось вместе с ней и вылезало ВЛЕВО — прямо на слово «Lingo»
       (перекрытие 30px). Теперь дорожка не бывает уже своего содержимого: тесно — логотип
       уезжает от центра влево, но столкновения не будет никогда. Логотип — украшение,
       переключатель ребёнка и шестерёнка — органы управления; уступать обязано украшение. */
    display:grid;grid-template-columns:minmax(0,1fr) auto minmax(min-content,1fr);
    align-items:center;gap:14px;
    box-shadow:var(--sh-2);
  }
  .hdr-back{justify-self:start;display:flex;align-items:center;min-width:0;overflow:hidden}
  /* в шапке сбрасываем margin-bottom тела-версии .mback — иначе он занимает место снизу и при
     align-items:center кнопка визуально уезжает вверх (blox31 #… — «съехала чуть вверх») */
  /* та же контурная кнопка B, но на тёмной шапке: полупрозрачно-белый контур (язык .set-btn),
     margin сброшен — иначе margin-bottom тела-версии уводит кнопку вверх при align центре. */
  .hdr-back .mback{color:var(--on-dark);border-color:rgba(255,255,255,.35);max-width:100%;margin:0}
  .hdr-back .mback:hover{border-color:rgba(255,255,255,.55);background:rgba(255,255,255,.14);color:var(--on-dark)}
  .hdr-right{justify-self:end;display:flex;align-items:center;gap:10px;min-width:0}
  header .logo{
    font-weight:800;font-size:var(--fs-2xl);letter-spacing:var(--tracking-tight);
    display:flex;align-items:center;gap:9px;justify-self:center;white-space:nowrap;
  }
  header .logo::before{
    content:"";width:11px;height:11px;border-radius:50%;
    background:var(--accent);box-shadow:0 0 0 4px rgba(92,200,255,.22);
  }
  header .who{display:flex;align-items:center;gap:8px;min-width:0}
  header .auth{display:flex;align-items:center;gap:10px}
  /* на телефоне режем отступы/зазоры шапки — иначе правый кластер не влезает в одну строку */
  @media(max-width:430px){
    header{padding:max(12px,env(safe-area-inset-top)) max(10px,env(safe-area-inset-right))
                   12px max(10px,env(safe-area-inset-left));gap:8px}
    header .who{gap:6px}
    header .auth{gap:8px}
    .hdr-right{gap:8px}
    header .who select{max-width:72px}   /* dev-элемент; ужат, чтобы не наезжать на центр-логотип */
    header .who span{display:none}   /* «Кто я:» — dev-подпись, на телефоне прячем ради ширины */
  }
  /* строка выбора шрифта — используется в попапе настроек (settings.js, blox18 #9) */
  .fsw-item{display:grid;grid-template-columns:1fr auto;align-items:center;gap:2px 10px;
    background:none;border:none;border-radius:9px;padding:9px 11px;cursor:pointer;text-align:left;
    color:var(--ink);font-size:16px;font-weight:700}
  .fsw-item:hover{background:var(--accent-tint)}
  .fsw-item.on{background:var(--accent-tint)}
  .fsw-item .fsw-note{grid-column:1;font-size:12px;font-weight:600;color:var(--muted)}
  .fsw-item .fsw-tick{grid-column:2;grid-row:1 / span 2;color:var(--accent-dark);font-weight:800;
    opacity:0}
  .fsw-item.on .fsw-tick{opacity:1}

  /* единый попап настроек (шестерёнка) — шрифт + цветовая схема (blox18 #9).
     Пункты шрифта переиспользуют .fsw-item выше. */
  .settings{position:relative;display:flex;align-items:center}
  .set-btn{width:38px;height:38px;border-radius:10px;border:1px solid rgba(255,255,255,.35);
    background:rgba(255,255,255,.12);color:var(--on-dark);cursor:pointer;
    display:flex;align-items:center;justify-content:center}
  .set-btn:hover{background:rgba(255,255,255,.22)}
  .set-menu{display:none;position:absolute;top:calc(100% + 8px);right:0;z-index:50;
    width:max-content;max-width:min(300px,calc(100vw - 24px));
    max-height:calc(100vh - 84px);overflow-y:auto;   /* не обрезаем «Выйти» на низкой ПК-высоте (blox31 #1) */
    background:#fff;border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--sh-4);
    padding:8px;flex-direction:column;gap:4px}
  .set-menu.open{display:flex}
  .set-sec{display:flex;flex-direction:column;gap:2px}
  .set-sec + .set-sec{margin-top:6px;padding-top:8px;border-top:1px solid var(--line-soft)}
  .set-h{font-size:11px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
    color:var(--muted);padding:2px 11px 4px}
  .set-scheme{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:2px 11px;
    background:none;border:none;border-radius:9px;padding:9px 11px;cursor:pointer;text-align:left;
    color:var(--ink);width:100%}
  .set-scheme:hover{background:var(--accent-tint)}
  .set-scheme.on{background:var(--accent-tint)}
  .set-dots{grid-row:1 / span 2;display:flex;gap:3px}
  .set-dots i{width:13px;height:13px;border-radius:50%;border:1px solid rgba(0,0,0,.08)}
  .set-sbody{grid-column:2;display:flex;flex-direction:column}
  .set-sname{font-size:15px;font-weight:700}
  .set-snote{font-size:12px;font-weight:600;color:var(--muted)}
  .set-scheme .fsw-tick{grid-column:3;grid-row:1 / span 2;color:var(--accent-dark);font-weight:800;opacity:0}
  .set-scheme.on .fsw-tick{opacity:1}
  .auth .auth-me{font-weight:700;font-size:14px;opacity:.95}
  /* переключатель детей в шапке ребёнка: без ограничения ширины нативный select раздувается
     до самого длинного пункта («➕ Добавить») и вся шапка ломается на две строки на телефоне.
     Ограничиваем ширину и обрезаем длинное имя многоточием — как у учителя, одна строка. */
  .auth .kid-switch{max-width:150px;text-overflow:ellipsis;padding:9px 10px}
  /* «Выйти» переехал из шапки вниз всплывашки настроек (blox31 #1). Отдельная секция снизу,
     отбита линией; мягкий красноватый акцент как у деструктивного действия, но негромкий. */
  .set-sec-logout{margin-top:6px;padding-top:8px;border-top:1px solid var(--line-soft)}
  .set-logout{width:100%;background:none;border:none;border-radius:9px;padding:10px 11px;
    cursor:pointer;text-align:left;color:var(--alert-ink);font-weight:700;font-size:15px}
  .set-logout:hover{background:var(--alert-tint)}
  /* вход в код-вику из приложения: без него владелец, войдя, оказывался внутри линго без
     дороги обратно и набирал адрес руками. Видно только персоналу — body.teacher ставится
     в render() для всех ролей кроме ученика, так что отдельной проверки роли не нужно. */
  .set-sec-wiki{display:none}
  body.teacher .set-sec-wiki{display:block;margin-top:6px;padding-top:8px;
    border-top:1px solid var(--line-soft)}
  .set-wiki{display:block;width:100%;border-radius:9px;padding:10px 11px;
    text-decoration:none;text-align:left;color:var(--ink);font-weight:700;font-size:15px}
  .set-wiki:hover{background:var(--surface-2)}
  /* blox37 #1 — «Сообщить о проблеме». Тот же пункт-строка, что «Код-вики» и «Выйти», но виден
     ВСЕМ ролям: застревают чаще родители и дети. min-height 44px — палец (WCAG 2.5.5). */
  .set-sec-support{margin-top:6px;padding-top:8px;border-top:1px solid var(--line-soft)}
  .set-support{display:block;width:100%;min-height:44px;background:none;border:none;border-radius:9px;
    padding:10px 11px;text-align:left;color:var(--ink);font-weight:700;font-size:15px;cursor:pointer}
  .set-support:hover{background:var(--surface-2)}
  /* на телефоне запас по ширине минимальный — режем селект сильнее, чтобы шапка держала одну строку.
     Стоит ПОСЛЕ базового max-width:150px, иначе при равной специфичности победит базовое правило. */
  @media(max-width:430px){ .auth .kid-switch{max-width:120px} }
  .ya-btn{display:inline-flex;align-items:center;gap:7px;background:#fff;color:#000;
    text-decoration:none;font-weight:700;font-size:14px;padding:8px 14px;border-radius:10px;
    border:none;cursor:pointer}
  .ya-btn::before{content:"Я";display:inline-flex;align-items:center;justify-content:center;
    width:20px;height:20px;border-radius:5px;background:#fc3f1d;color:#fff;font-weight:800;font-size:14px}
  .ya-btn-lg{font-size:17px;padding:14px 22px;border-radius:14px;box-shadow:var(--sh-2)}
  .ya-btn-lg::before{width:26px;height:26px;font-size:18px;border-radius:7px}
  .landing{min-height:70vh;display:flex;align-items:center;justify-content:center;padding:24px}
  .landing-card{max-width:380px;text-align:center;background:#fff;border:1px solid var(--line-soft);
    border-radius:var(--r-2xl);padding:38px 30px;box-shadow:var(--sh-3)}
  .landing-logo{font-weight:800;font-size:38px;letter-spacing:var(--tracking-tight);margin-bottom:10px;
    background:var(--grad-wordmark);
    -webkit-background-clip:text;background-clip:text;color:transparent}
  .landing-sub{color:var(--muted-strong);font-size:15px;line-height:1.5;margin:0 0 22px}
  .staff-link{display:block;margin:20px auto 0;background:none;border:none;color:var(--faint);
    font-size:13px;text-decoration:underline;cursor:pointer}
  /* 44px высоты — это ссылка-кнопка на экране входа, палец промахивается по 18-пиксельной строке */
  .switch-link{display:block;margin:8px auto 0;padding:12px 4px;min-height:44px;box-sizing:border-box;
    color:var(--accent-ink);font-size:13px;text-decoration:underline}
  /* Ошибка входа. Живёт НА экране входа, а не в alert: почти всегда причина в том, что браузер уже
     залогинен не в тот Яндекс-аккаунт, и рядом с текстом обязана быть кнопка «выбрать другой». */
  .login-err{text-align:left;border:1px solid var(--alert-line);border-radius:var(--r-lg);
    background:var(--alert-tint);padding:12px 14px;margin:0 0 18px}
  .login-err-msg{display:flex;gap:8px;font-size:14px;line-height:1.45;font-weight:600;color:var(--ink)}
  .login-err-msg svg{width:18px;height:18px;flex:0 0 18px;margin-top:2px;color:var(--alert-ink)}
  .login-err-em{margin-top:8px;font-size:13px;color:var(--muted-strong);overflow-wrap:anywhere}
  select{
    border:1px solid var(--line);border-radius:var(--r-sm);padding:9px 12px;font-size:var(--fs-md);
    background:#fff;color:var(--ink);font-weight:600;
  }
  header select{border-color:transparent}
  /* боковые/нижний отступы контента учитывают вырез (viewport-fit=cover): в горизонтали карточки
     не заезжают под вырез слева/справа, а низ страницы не прячется под home-индикатор. env()=0 без
     выреза → max()/сумма дают базовые значения, обычный экран не меняется. */
  main{max-width:900px;margin:0 auto;
    padding:var(--sp-5) max(var(--sp-4),env(safe-area-inset-right))
            calc(var(--sp-8) + env(safe-area-inset-bottom)) max(var(--sp-4),env(safe-area-inset-left))}
  .sub{color:var(--muted);margin:4px 0 18px;font-size:var(--fs-md)}
  h2{margin:0 0 2px;font-size:var(--fs-2xl);font-weight:800;color:var(--ink-strong)}

  .badge{font-size:22px}

  /* ===== STUDENT HOMEWORK — the flagship list =================================
     A task used to be a saturated colour block with white text. It is now a white
     card carrying a colour RAIL on its left edge (--rail, set by JS from the task
     type: grape = nothing to hand in, sky = a turn-in is expected). Same colour
     code, far better legibility, and it finally rhymes with the teacher's cards
     (.ecard/.srcard), which were already white-with-a-stripe. */
  .hwlist{display:flex;flex-direction:column;gap:10px}
  /* flex-wrap: плеер (.rplayer) переносится на СВОЮ строку во всю ширину плашки — в узкой колонке
     .rbody он не помещался и на телефоне обрезался по краю карточки (blox10 #10) */
  /* padding-right резервирует правую колонку под «сделал» (.rcheck, абсолютная во всю высоту), чтобы
     серый столбец тянулся до низа карточки даже когда плеер удлиняет её (blox18 #7) */
  .rect{--rail:var(--task-plain);
        position:relative;display:flex;flex-wrap:wrap;align-items:stretch;background:var(--card);
        padding-right:66px;
        border:1px solid var(--line-soft);border-left:6px solid var(--rail);
        border-radius:var(--r-lg);overflow:hidden;color:var(--ink);box-shadow:var(--sh-1);
        transition:opacity var(--t-slow) var(--ease),box-shadow var(--t) var(--ease),
                   transform var(--t-fast) var(--ease),background var(--t-slow) var(--ease)}
  .rect:hover{box-shadow:var(--sh-2)}
  /* done = the card quietly recedes: celadon wash + faded rail + no shadow carry the state.
     No blanket opacity — it dragged the title/links below 4.5:1 (axe color-contrast); the green
     wash alone already reads as «done». */
  .rect.done{background:var(--ok-tint);border-color:var(--ok-line);border-left-color:var(--ok-line);
        box-shadow:none}
  .rect.done .rtitle{color:var(--muted)}
  /* blox6: task-type icon on the LEFT edge of the task — now a tinted round chip */
  .rect .ricon{flex:0 0 auto;display:flex;align-items:flex-start;justify-content:center;
        padding:14px 2px 0 12px;font-size:22px;line-height:1}
  .rect .rbody{flex:1;min-width:0;padding:14px 14px 14px 10px;display:flex;flex-direction:column;gap:9px}
  .rect .rtitle{font-weight:700;font-size:var(--fs-lg);line-height:1.3;color:var(--ink);
        letter-spacing:var(--tracking-tight)}
  /* имя прикреплённого файла — под типом таска, тише заголовка, но читаемо */
  .rect .rfile{display:flex;align-items:center;gap:6px;font-size:var(--fs-sm);font-weight:600;
        color:var(--muted);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-top:-3px}
  .rect .rstatus{font-size:var(--fs-sm);font-weight:600;color:var(--muted)}

  /* ---- ЧЕЛЛЕНДЖ (blox15 #6) — та же плашка .rect, тот же рельс, только он ЗОЛОТОЙ.
     blox23 #1: финишный флаг переехал из низа иконочной колонки к верхней строке счётчиков-витрины
     (см. .rp-count ниже). Выполненный челлендж НЕ гаснет как обычный таск: витрина должна быть видна. */
  .rect.chal{border-color:var(--gold-line);background:var(--gold-tint)}
  .rect.chal.done{background:var(--gold-tint);border-color:var(--gold-line);
        border-left-color:var(--gold);opacity:1}
  .rect.chal.done .rtitle{color:var(--ink)}
  .rect .rflag{color:var(--gold-ink);font-size:18px;display:inline-flex;align-items:center;flex:0 0 auto}
  .rect .rdue{font-size:var(--fs-sm);font-weight:700;color:var(--gold-ink);
        background:#fff;border:1px solid var(--gold-line);border-radius:var(--r-pill);padding:1px 8px;
        white-space:nowrap}
  /* витрина: кто сделал, кто нет (у слушательного челленджа — с числом наслушек) */
  .rect .rpeers{display:flex;flex-direction:column;gap:3px;font-size:var(--fs-sm);
        border-top:1px dashed var(--gold-line);padding-top:8px}
  .rect .rp-count{font-weight:800;color:var(--gold-ink);display:flex;align-items:center;gap:6px}
  .rect .rp-did{color:var(--ok-ink);overflow-wrap:anywhere}
  .rect .rp-did b{color:var(--gold-ink)}
  .rect .rp-not{color:var(--muted);overflow-wrap:anywhere}
  /* padding + отрицательный margin: зона нажатия ≥24px (G5), а видимая надпись стоит где стояла */
  .rect .resend{background:none;border:none;cursor:pointer;font-family:inherit;font-size:var(--fs-sm);
    font-weight:700;color:var(--alert-ink);text-decoration:underline;
    padding:8px 6px;margin:-8px -6px -8px 2px}
  .rect .rcomment{font-size:var(--fs-xs);font-weight:600;color:var(--ink);background:var(--surface-2);
        border:1px solid var(--line-soft);border-radius:var(--r-xs);padding:7px 10px}
  /* blox22 #3: свёрнутый «Мой ответ» — ученик пересматривает свою сдачу */
  /* blox27 #4 — «Мой ответ» показан сразу сущностью файла (не тоггл): подпись + кликабельная карточка */
  .rect .myans{margin-top:6px}
  .rect .myans-lbl{display:flex;align-items:center;gap:5px;font-size:var(--fs-sm);font-weight:700;
        color:var(--accent-ink);margin-bottom:6px}

  /* blox26 #3 — ЕДИНАЯ «сущность приложенного файла»: скруглённая карточка [иконка типа · имя · глаз].
     Одна на экране ученика («Мой ответ») и в проверке учителя. Медиа НЕ разворачивается в потоке —
     открывается по клику во встроенном оверлее .mediaov (фото вписано; видео/аудио через наш плеер).
     Это и убрало переполнение на широком экране, и наложение двух плееров. */
  .fileent{display:flex;align-items:center;gap:10px;max-width:100%;padding:9px 11px;border-radius:11px;
        background:var(--surface);border:1px solid var(--line);cursor:pointer;color:var(--ink);
        text-decoration:none;box-shadow:var(--sh-1)}
  .fileent:hover{border-color:var(--accent-line)}
  .fileent-ico{flex:0 0 auto;font-size:19px;line-height:1;display:flex;color:var(--accent-ink)}
  .fileent-nm{flex:1;min-width:0;font-weight:650;font-size:13.5px;line-height:1.3;overflow-wrap:anywhere}
  .fileent-view{flex:0 0 auto;width:34px;height:34px;border:none;border-radius:9px;cursor:pointer;
        background:var(--accent-tint);color:var(--accent-ink);display:flex;align-items:center;justify-content:center}
  .fileent-view:active{transform:scale(.94)}
  .fileent-go{flex:0 0 auto;font-weight:800;color:var(--accent-ink)}

  /* медиа-оверлей сдачи: фото вписано (не вылезает за экран), видео/аудио — через наш плеер */
  .mediaov .mediabox{background:var(--card);border-radius:var(--r-lg);box-shadow:var(--sh-4);
        width:100%;max-width:min(900px,94vw);max-height:92vh;overflow:auto;padding:12px 14px}
  .mediaov .mediabody{padding:8px 0 4px;display:flex;justify-content:center}
  .mediaimg{max-width:100%;max-height:78vh;object-fit:contain;border-radius:8px;display:block}
  /* blox33 #2 — сдача архивирована по сроку хранения: спокойная заглушка вместо плеера */
  .sub-archived{display:flex;align-items:center;gap:10px;color:var(--muted);font-size:var(--fs-sm);
        padding:22px 16px;text-align:center;line-height:1.4}
  .sub-archived svg{width:22px;height:22px;flex:0 0 auto;opacity:.75}
  /* flickering (edited): the teacher changed the task after the pupil did it */
  .edited-flag{font-size:var(--fs-xs);font-weight:800;color:var(--on-dark);background:var(--alert-solid);
        border-radius:var(--r-xs);padding:2px 8px;white-space:nowrap;animation:editblink 1.4s var(--ease) infinite}
  @keyframes editblink{0%,100%{opacity:1}50%{opacity:.35}}
  .rect .ractions{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
  .rect .ract{border:1px solid var(--line);border-radius:var(--r-sm);padding:10px 15px;
        font-size:14px;font-weight:700;background:#fff;color:var(--ink);cursor:pointer;
        text-decoration:none;display:inline-block;text-align:center;
        transition:border-color var(--t) var(--ease),background var(--t) var(--ease),transform var(--t-fast) var(--ease)}
  .rect .ract:hover{border-color:var(--accent);background:var(--accent-tint)}
  .rect .ract:active{transform:scale(.96)}
  /* blox24 #9: файл-таск без файла — нечего слушать/открывать; тихая пометка вместо кнопки */
  .rect .ract-empty{display:inline-flex;align-items:center;gap:6px;font-size:var(--fs-sm);
        font-weight:600;color:var(--muted)}
  .rect audio,.rect video{width:100%;margin-top:2px;border-radius:var(--r-sm)}
  /* строка плеера: во всю ширину карточки, поверх обеих колонок */
  .rect .rplayer{flex:0 0 100%;min-width:0;padding:0 12px 12px}
  .rect .rplayer .lp{margin:0 auto}   /* на десктопе плеер не растягивается, а стоит по центру */
  /* у контрольного файла нет колонки «сделал» (.rcheck), поэтому 66px-резерв справа — мёртвый:
     он сдвигал плеер влево от центра плашки и оставлял пустой столбец. Снимаем — плеер встаёт
     ровно по центру таска, во всю ширину. Под .rcheck заезжать нечему (её тут нет). */
  .rect.ctrltask{padding-right:0}
  /* the check: a tall tap target on the right, ringed circle -> filled celadon tick */
  .rect .rcheck{position:absolute;top:0;right:0;bottom:0;width:66px;border:none;border-left:1px solid var(--line-soft);
        background:var(--surface);cursor:pointer;display:flex;align-items:center;justify-content:center;
        transition:background var(--t) var(--ease)}
  .rect .rcheck:hover{background:var(--fill-soft)}
  .rect .rcheck:active{transform:scale(.94)}
  .rect .rcheck .circ{width:32px;height:32px;border-radius:50%;border:2.5px solid var(--line-mid);
        background:#fff;display:flex;align-items:center;justify-content:center;font-size:18px;
        font-weight:800;color:transparent;
        transition:background var(--t) var(--ease),border-color var(--t) var(--ease),
                   color var(--t) var(--ease),transform var(--t) var(--ease)}
  .rect .rcheck:hover .circ{border-color:var(--ok)}
  .rect .rcheck.on{background:var(--ok-fill);border-left-color:var(--ok-line)}
  .rect .rcheck.on .circ{background:var(--ok-ink);border-color:var(--ok-ink);color:var(--on-dark);
        transform:scale(1.06)}

  /* teacher grid */
  table{border-collapse:separate;border-spacing:8px;width:100%}
  th{font-size:13px;color:var(--muted);font-weight:700;text-align:center;padding:4px}
  th.name,td.name{text-align:left;font-weight:700;white-space:nowrap}
  .cell{
    width:64px;height:48px;border-radius:12px;color:#fff;margin:0 auto;
    display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;
  }
  .empty{color:var(--muted);padding:30px 0;text-align:center}

  /* pool playlist panel */
  /* «Назад» внутри конструктора/расписания = вариант B (контур) на СВЕТЛОМ фоне */
  .back{display:inline-flex;align-items:center;gap:5px;box-sizing:border-box;
        background:none;border:1px solid var(--line);border-radius:var(--r-md);color:var(--muted-strong);
        font-weight:700;font-size:14px;cursor:pointer;padding:7px 13px;margin-bottom:10px;
        transition:border-color var(--t) var(--ease),background var(--t) var(--ease),color var(--t) var(--ease)}
  .back:hover{border-color:var(--accent);background:var(--accent-tint);color:var(--accent-ink)}
  .now{background:var(--card);border-radius:16px;padding:16px;
       box-shadow:var(--sh-2);margin-bottom:16px}
  .now .nt{font-weight:800;font-size:18px;margin-bottom:8px}
  .now audio{width:100%}
  .track{display:flex;align-items:center;gap:12px;padding:10px 12px;border-radius:12px;
         background:var(--card);box-shadow:var(--sh-1);margin-bottom:8px;cursor:pointer}
  .track.playing{outline:2px solid var(--accent)}
  .dot{width:14px;height:14px;border-radius:50%;flex:0 0 auto}
  .track .tt{font-weight:700;flex:1}
  .track .meta{font-size:12px;color:var(--muted)}
  /* ↓ на каждом треке: 44×44 палец-цель (G5), не запускает плеер (stopPropagation) */
  .track .trackdl{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
                  width:44px;height:44px;border-radius:12px;color:var(--muted);text-decoration:none}
  .track .trackdl:hover{color:var(--primary);background:var(--bg)}
  .track .trackdl .ic{width:20px;height:20px}
  /* заголовок пула + кнопка «Скачать песни» (весь аудио-плейлист одним zip) */
  .poolhead{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
  .poolhead h2{margin:0}
  .pool-dlall{flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;
              font-weight:700;font-size:13px;color:var(--primary);--btn-ink:var(--primary);text-decoration:none;
              background:var(--card);border:1px solid var(--line);border-radius:999px;
              padding:9px 15px;cursor:pointer;box-shadow:var(--sh-1);min-height:44px}
  .pool-dlall:hover{border-color:var(--primary);box-shadow:var(--sh-2)}
  .pool-dlall .ic{width:16px;height:16px}

  /* teacher tabs */
  /* segmented control, iOS-style: a soft track with a raised selected pill */
  .tabs{display:flex;gap:4px;margin-bottom:18px;background:var(--fill-soft);
    border-radius:var(--r-md);padding:4px;width:fit-content}
  .tabs button{
    border:none;border-radius:var(--r-sm);padding:9px 18px;font-size:14px;font-weight:700;
    background:transparent;color:var(--muted);cursor:pointer;
    transition:background var(--t) var(--ease),color var(--t) var(--ease);
  }
  .tabs button:hover{color:var(--ink)}
  .tabs button.on{background:#fff;color:var(--ink);box-shadow:var(--sh-1)}

  /* add-file panel */
  .panel{background:var(--card);border:1px solid var(--line-soft);border-radius:var(--r-xl);
         padding:20px;box-shadow:var(--sh-2);margin-bottom:18px}
  .panel h3{margin:0 0 14px;font-size:var(--fs-lg);font-weight:800;color:var(--ink-strong)}
  .field{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}
  .field label{font-size:var(--fs-sm);color:var(--muted);font-weight:700}
  .field input[type=text],.field input[type=url]{
    border:1px solid var(--line);border-radius:var(--r-sm);padding:11px 13px;font-size:var(--fs-md);
    width:100%;background:#fff;color:var(--ink);transition:border-color var(--t) var(--ease),box-shadow var(--t) var(--ease);
  }
  .field input:focus,.field select:focus,.field textarea:focus{
    outline:none;border-color:var(--accent);box-shadow:var(--ring)}
  .field.row{flex-direction:row;align-items:center;gap:8px}
  .field.row label{font-size:15px;color:var(--ink)}
  .src-tabs{display:flex;gap:8px;margin-bottom:14px}
  .src-tabs button{flex:1;border:1px solid var(--line);background:#fff;border-radius:10px;
    padding:9px;font-weight:700;font-size:14px;color:var(--muted);cursor:pointer}
  .src-tabs button.on{border-color:var(--accent);color:var(--accent-dark);background:var(--accent-tint)}
  /* PRIMARY = grape fill + white text (10.6:1). The sky is too light to carry white
     text, so it works as accent/selection instead — see theme.css. */
  /* .btn-primary → folded onto the engine's --primary role (see BUTTON ENGINE). */
  .hint{font-size:12px;color:var(--muted);margin-top:-4px}
  .hint.ok{color:var(--green);font-weight:700}    /* «Выбрано: файл» в листе сдачи */
  .msg{font-size:14px;font-weight:600;margin-top:10px}
  .msg.ok{color:var(--green)} .msg.err{color:var(--red)}

  /* file library list */
  .file{display:flex;align-items:center;gap:12px;padding:12px;border-radius:12px;
        background:var(--card);box-shadow:var(--sh-1);margin-bottom:8px}
  .file .ico{font-size:20px}
  .file .info{flex:1;min-width:0}
  .file .info .ft{font-weight:700}
  .file .info .fm{font-size:12px;color:var(--muted)}
  .pill{font-size:11px;font-weight:700;padding:3px 8px;border-radius:20px;
        background:var(--accent-tint);color:var(--accent-dark)}
  .file .act{background:none;border:none;cursor:pointer;font-size:13px;font-weight:700;
        color:var(--accent-dark);padding:6px}
  .file .act.del{color:var(--red)}

  /* teacher home = the group screen. The old list-of-groups monitor is gone: every group of the
     teacher lives in ONE horizontal strip at the top, where the group's title used to be. A chip
     turns alert-red when that group needs something (задание не выдано / есть что проверять). */
  .gstrip{display:flex;gap:8px;overflow-x:auto;padding:2px 2px 8px;margin-bottom:2px;
    -webkit-overflow-scrolling:touch;scrollbar-width:none}
  .gstrip::-webkit-scrollbar{display:none}
  .gchip{position:relative;flex:0 0 auto;display:flex;align-items:center;gap:7px;cursor:pointer;
    padding:9px 14px;border-radius:999px;border:1px solid var(--line);background:var(--card);
    font-weight:800;font-size:15px;color:var(--muted);white-space:nowrap;box-shadow:var(--sh-1)}
  .gchip:active{transform:scale(.97)}
  .gchip .gdot{width:8px;height:8px;border-radius:50%;background:var(--checked);flex:0 0 auto}
  .gchip.alert{color:var(--alert-ink);border-color:var(--alert-line);background:var(--alert-tint)}
  .gchip.alert .gdot{background:var(--alert)}
  .gchip.on{color:var(--ink);border-color:var(--accent)}
  /* индикатор выбранной группы: короткая толстая полоса-подчёркивание под чипом — тот же приём, что
     недельная полоса (.wkstart), но горизонтальная, покороче и потолще. Цвет = --accent (роль
     «активное») → перекрашивается при смене схемы, как просили. */
  .gchip.on::after{content:'';position:absolute;left:50%;bottom:-5px;transform:translateX(-50%);
    width:56%;height:3px;border-radius:2px;background:var(--accent)}
  .gchip.on.alert{color:var(--alert-ink);border-color:var(--alert)}
  .gchip.on.alert::after{background:var(--alert)}
  .gchip .gn{font-weight:800;font-size:11px;color:#fff;background:var(--alert-solid);
    border-radius:8px;padding:1px 6px;cursor:pointer}
  .gchip .gn:hover{background:var(--alert-strong)}
  /* имя открытой группы — по центру под лентой; ниже мельче отделение и поток (blox14 #4) */
  /* строка инструментов группы (blox31 #1): Notes | имя+гамбургер | Playlist … Post | Chats.
     ПК — одна строка (visily.png): Notes и Playlist примыкают к имени, Post+Chats прижаты вправо.
     Телефон — имя отдельной строкой сверху, кнопки 2×2 (управляется flex order ниже). */
  .ghead-tools{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:4px 0 12px}
  .ghead-tools .btn-ghost{display:flex;align-items:center;justify-content:center;gap:6px}
  /* имя + гамбургер + отделение — своя внутренняя строка (blox16 #7 · gt-par через ·) */
  .gtitle{display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;justify-content:center;
    gap:8px;text-align:center}
  .gtitle .gt-name{font-weight:800;font-size:20px;color:var(--ink);letter-spacing:.2px;
    overflow-wrap:anywhere}
  .gtitle .gt-par{font-size:13px;font-weight:700;color:var(--muted)}
  .gtitle .gt-par::before{content:'·';margin-right:8px;color:var(--muted)}
  /* кнопка-гамбургер рядом с именем — прячет редкие действия (Ученики / Расписание / + Группа), blox31 #1.
     Меню всплывает под ней; [hidden] гасит его через display:none (правило 7 skill). */
  .gkebab-wrap{position:relative;display:inline-flex;align-items:center}
  .gkebab{width:38px;height:38px;border-radius:9px;border:1px solid var(--line);background:var(--card);
    color:var(--muted);font-size:16px;line-height:1;cursor:pointer;
    display:flex;align-items:center;justify-content:center}
  .gkebab:hover{border-color:var(--accent);background:var(--accent-tint);color:var(--accent-ink)}
  .gkebab-menu{position:absolute;top:calc(100% + 6px);left:50%;transform:translateX(-50%);z-index:45;
    display:flex;flex-direction:column;gap:4px;min-width:190px;padding:8px;
    background:#fff;border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--sh-4)}
  .gkebab-menu[hidden]{display:none}
  .gkebab-menu .btn-ghost{width:100%;justify-content:flex-start;gap:8px}
  /* элемент-разрыв строки: на ПК отсутствует, на телефоне (max-width:760px) forces перенос (blox31 #1) */
  .gt-break{display:none}
  /* радужный Playlist — тот же фикс-градиент пула, что у ребёнка в домашке; тёмные чернила поверх */
  .ghead-tools .btn-ghost.gplay-rainbow{background:var(--grad-playlist);color:var(--ink-strong);
    border:none;font-weight:800}
  .ghead-tools .btn-ghost.gplay-rainbow .ic{color:var(--ink-strong)}
  .ghead-tools .btn-ghost.gplay-rainbow:hover{filter:brightness(1.03)}
  /* ПК — одна строка: не переносим, имя между кнопками, Post+Chats уходят вправо через margin-left:auto */
  @media(min-width:761px){
    .ghead-tools{flex-wrap:nowrap}
    .ghead-tools .btn-ghost{flex:0 0 auto;white-space:nowrap}
    .gtitle{flex:0 1 auto;margin:0 4px}
    .ghead-tools .gt-post{margin-left:auto}
  }
  /* телефон (blox31 #1) — строка 1: гамбургер + имя … Playlist справа; строка 2: Notes | Post | Chats.
     Порядок задаём через flex order; .gt-break (flex-basis:100%) принудительно рвёт строку после Playlist. */
  @media(max-width:760px){
    /* строка 1 — имя тянется (grow), прижимая Playlist к правому краю; при длинном имени жмётся Playlist.
       ЖЁСТКАЯ height:40px (border-box) и у имени, и у Playlist: строка 1 держит ровно 40px независимо
       от наличия Playlist И от рендеринга шрифта на устройстве. min-height давал микропрыжок на iPhone,
       где фактическая высота кнопки чуть отличалась от 40 (blox31 #1 follow-up). */
    .ghead-tools .gtitle{order:1;flex:1 0 auto;min-width:0;margin:0;height:40px;box-sizing:border-box;
      justify-content:flex-start;flex-wrap:nowrap}
    .ghead-tools .gt-play{height:40px;box-sizing:border-box}
    .gtitle .gkebab-wrap{order:1}          /* гамбургер первым */
    .gtitle .gt-name{order:2}              /* имя после гамбургера */
    .gtitle .gt-par{display:none}          /* отделение на телефоне убрано */
    /* гамбургер теперь у левого края — центрированное меню уезжало за экран; якорим по левому краю кнопки */
    .gkebab-menu{left:0;transform:none}
    .ghead-tools .gt-play{order:2;flex:0 1 auto;min-width:0;overflow:hidden}
    .gt-break{display:block;order:3;flex:0 0 100%;height:0}
    /* строка 2 — три кнопки в один ряд */
    .ghead-tools .gt-notes{order:4}
    .ghead-tools .gt-post{order:5}
    .ghead-tools .gt-chats{order:6}
    .ghead-tools .gt-notes,.ghead-tools .gt-post,.ghead-tools .gt-chats{
      flex:1 1 calc(33.333% - 6px);font-size:13px;gap:4px;
      padding-left:4px;padding-right:4px}
  }

  /* pupil's "where I study" line: отделение · группа · учитель (blox10 #2) */
  /* строка «где я учусь» не должна липнуть к заголовку — своя воздушная подушка (blox13 #3) */
  .swhere{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:8px 0 16px;
    font-size:13px;color:var(--muted);font-weight:600}
  .swhere-grp{font-weight:800;color:var(--ink)}
  .swhere-sep{color:var(--faint)}

  /* pupil's lesson carousel — flip among days that have homework */
  .lcar{display:flex;align-items:center;justify-content:space-between;gap:10px;
    background:#fff;border:1px solid var(--line-soft);border-radius:var(--r-lg);padding:8px 10px;
    margin:0 0 16px;box-shadow:var(--sh-1)}
  .lcar-arr{width:44px;height:44px;flex:none;border:1px solid var(--line);background:#fff;
    border-radius:var(--r-md);font-size:19px;color:var(--ink);cursor:pointer;
    transition:background var(--t) var(--ease),border-color var(--t) var(--ease),transform var(--t-fast) var(--ease)}
  .lcar-arr:hover:not(:disabled){background:var(--accent-tint);border-color:var(--accent)}
  .lcar-arr:active:not(:disabled){transform:scale(.93)}
  .lcar-arr:disabled{opacity:.28;cursor:default}
  .lcar-mid{text-align:center;line-height:1.25}
  .lcar-date{font-weight:800;font-size:var(--fs-lg);color:var(--ink);letter-spacing:var(--tracking-tight)}
  .lcar-pos{font-size:var(--fs-xs);color:var(--faint);margin-top:2px}

  /* МОНИТОР: Лист ДЗ — entity cards + a turn-in submitter column */
  .lh{margin:14px 0 8px;font-size:15px;color:var(--ink)}
  .elist{display:flex;flex-direction:column;gap:10px}
  .ecard{display:flex;gap:0;background:#fff;border:1px solid var(--line-soft);border-left:5px solid var(--line-mid);
    border-radius:12px;overflow:hidden}
  .ecard.arch{opacity:.65;background:var(--alert-tint)}
  .ecard .ebody{flex:1;padding:12px 14px;min-width:0}
  .ecard .etop{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
  .ecard .eico{font-size:18px}
  .ecard .etitle{font-weight:800;color:var(--ink)}
  .ecard .etype{font-size:11px;font-weight:700;color:var(--muted);background:var(--surface);
    padding:2px 8px;border-radius:8px}
  .ecard .emeta{margin-top:6px;font-size:12px;color:var(--muted)}
  /* the actionable "кто не сделал" line under each entity — small, warm, easy to scan */
  .ecard .enotdone{margin-top:5px;font-size:12px;line-height:1.4;color:var(--alert-ink)}
  .ecard .enotdone.all{color:var(--ok-ink)}
  /* тема = РАМКА, оборачивающая таски; скруглена с ЗАКРЫТОЙ стороны, ОТКРЫТА с другой.
     blox26 #5 — ЕДИНЫЙ визуал на ВСЕХ экранах: одна толщина (1.5px), цвет — токен схемы
     var(--frame-line) (перекрашивается вместе с палитрой), фон ПРОЗРАЧНЫЙ. Три класса
     остаются лишь потому, что различается ВНУТРЕННЯЯ раскладка/плотность:
       .tframe — Домашка ученика + проверка учителем
       .oframe — монитор учителя (плотнее)
       .theme  — постановка/редактирование ДЗ
     поэтому padding/margin и заголовки у каждого свои, а рамка — общая. */
  .tframe,.oframe,.theme{position:relative;background:transparent;--fpad:14px;--frail:6px}
  /* сама скобка — ПСЕВДОЭЛЕМЕНТ, а не border самого блока. Это позволяет «усам» (верхней
     и нижней линиям) КОНЧАТЬСЯ на открытом крае карточек, а не торчать до края блока
     (blox26 #5). --fpad = паддинг рамки с ОТКРЫТОЙ стороны = насколько подтянуть усы.
     Карточки при этом НЕ двигаются — выравнивание с тасками «вне темы» сохраняется. */
  .tframe::before,.oframe::before,.theme::before{content:"";position:absolute;
    top:0;bottom:0;left:0;right:var(--fpad);
    border:1.5px solid var(--frame-line);border-right:none;
    border-radius:var(--r-xl) 0 0 var(--r-xl);pointer-events:none}
  .tframe{padding:4px 14px 14px;margin:0 0 20px}
  .tframe>.tfh{text-align:center;font-weight:800;font-size:var(--fs-lg);color:var(--ink);
    letter-spacing:var(--tracking-tight);padding:11px 0 13px}
  .tframe>.tfcards{display:flex;flex-direction:column;gap:10px}
  /* таск ВНЕ темы — та же ширина, что и в рамке (сдвиг вправо на границу+паддинг рамки).
     Домашка ученика + экран проверки; в черновике ту же роль играет .standarea. */
  .hwlist>.sloose,.srlist>.sloose{margin-left:15px;margin-right:14px}
  .sortbar{display:flex;align-items:center;gap:10px;margin:18px 0 8px}
  .sortbar .sortlbl{font-weight:800;font-size:14px;color:var(--ink)}
  .seg{display:inline-flex;border:1px solid var(--line);border-radius:var(--r-sm);overflow:hidden}
  .seg button{border:0;background:#fff;color:var(--muted);font-weight:700;font-size:12px;
    padding:7px 13px;cursor:pointer;transition:background var(--t-fast) var(--ease)}
  /* selection = sky fill + grape ink (5.7:1) — the app's signature pairing */
  .seg button.on{background:var(--accent);color:var(--ink)}
  .rlist{display:flex;flex-wrap:wrap;gap:8px}
  .rchip{display:flex;align-items:center;gap:10px;border:1px solid var(--line-soft);background:#fff;
    border-radius:10px;padding:8px 12px;cursor:pointer;font-weight:700;color:var(--ink)}
  .rchip:hover{border-color:var(--accent)}
  /* review badge: «🧐 X из Y» (проверено/сдано), иначе Done!/Checked (зел.) / Nope (красн.) */
  .rchip .rrev{font-size:12px;font-weight:800;padding:2px 8px;border-radius:7px}
  .rchip .rrev.pend{color:#fff;background:var(--alert-solid)}   /* «к проверке» = teacher-alert colour (solid stop: white text needs 4.5:1) */
  .rchip .rrev.done{color:var(--ok-ink);background:var(--ok-fill)}
  .rchip .rrev.checked{color:var(--ok);background:var(--ok-tint)}
  .rchip .rrev.nope{color:var(--muted-strong);background:var(--fill-soft)}   /* «ничего не сдал» за прошедший урок — факт, не тревога: серым, не цветом alert (blox22 #1). grape-600, а не --muted: на grape-100 у --muted было 4.34:1 */
  /* GROUP OVERVIEW — two-pane screen (docs/teacher-overview-screen.md) */
  .ostrip{margin:0 0 4px}
  .otwo{display:flex;align-items:stretch;gap:0;margin-top:10px}
  .oarr{flex:0 0 auto;align-self:center;border:1px solid var(--line-soft);background:#fff;border-radius:10px;
    width:38px;height:54px;font-size:18px;cursor:pointer;color:var(--ink)}
  .oarr:disabled{opacity:.3;cursor:default}
  .opane{flex:1 1 0;min-width:0;border:1px solid var(--line-soft);border-radius:14px;padding:12px;margin:0 6px;background:#fff}
  .opane-h{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;
    border-bottom:1px solid var(--line-soft);padding-bottom:6px}
  .opane-d{font-weight:800}
  /* правая часть шапки: кнопка Edit (если есть) + статус «прошло/предстоит» на одной строке.
     Edit стоит ЛЕВЕЕ «предстоит»; отдельной строки .opane-tools больше нет (blox28 #1). */
  .opane-hr{display:flex;align-items:center;gap:8px}
  .opane-hr .oedit{padding:5px 12px;font-size:13px}
  .opane.pub{border-color:var(--ok-line)}                 /* выполнение — холодная зелёная рамка, не заливка */
  .opane.draft{border-color:var(--draft-line);background:var(--draft-tint)}   /* черновик — янтарь */
  .opane.empty{border-style:dashed}
  .opane.next{border-style:dashed;border-color:var(--ink)}       /* ближайшее предстоящее — чёрный пунктир, как act-right на каретке */
  .opane.next.nag{border-color:var(--alert)}                  /* ДЗ ещё не выдано — alert, как nag на каретке */
  .opane.edge{display:flex;align-items:center;justify-content:center;color:var(--faint);background:var(--surface)}
  .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:15px;padding:12px 18px}
  .odraft-t{font-weight:800;color:var(--draft-ink)}
  @keyframes shimmerGold{0%,100%{background-color:#fff}50%{background-color:var(--alert-tint)}}
  .opane.empty.shimmer{animation:shimmerGold 2.6s ease-in-out infinite;border-color:var(--alert)}
  .orow{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px}
  .ochip{padding:6px 9px}
  .oents{display:flex;flex-direction:column;gap:7px}
  .oent{border-left:3px solid var(--line-soft);background:var(--task-plaque);border-radius:8px;padding:7px 9px;font-size:13px}
  .oent.pool{color:var(--muted);position:relative;border-left:none;padding-left:14px;overflow:hidden}
  /* blox9 #7: у плашки плейлиста левый край радужный (как бар ученика), а не серый */
  .oent.pool::before{content:"";position:absolute;left:0;top:0;bottom:0;width:5px;
    background:var(--grad-playlist-v)}
  .oweek{margin-top:4px;font-size:12px;font-weight:700;color:var(--accent-dark)}
  /* blox32 #7 rework — большой таск «Плейлист — прослушка» на панели каретки playlist-группы */
  .opane.plonly .opane-body{padding:2px 2px 4px}
  .oent.plbig{gap:6px}
  .plbig .plb-sec{margin-top:9px;font-size:12.5px;font-weight:800;color:var(--muted);
    display:flex;align-items:center;gap:6px}
  .plbig .plb-sec svg{width:14px;height:14px;opacity:.75}
  .plbig .plb-sec.plb-ctrl{color:var(--accent-dark)}
  .plbig .plb-list{display:flex;flex-direction:column;gap:4px;margin-top:5px}
  .plbig .plb-row{display:flex;align-items:center;justify-content:space-between;gap:10px;
    padding:5px 10px;background:var(--card);border:1px solid var(--line-soft);border-radius:8px}
  .plbig .plb-name{font-weight:700;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .plbig .plb-n{font-weight:800;color:var(--ink);white-space:nowrap}
  .plbig .plb-row.zero .plb-n{color:var(--muted);font-weight:600}
  .plbig .plb-none{padding:6px 2px}
  /* имя прикреплённого файла — в плашке Листа ДЗ и в карточке разбора ученика */
  .ofile{display:flex;align-items:center;gap:5px;margin-top:4px;font-size:12px;font-weight:600;
    color:var(--muted);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* сам текст задания / ссылка / комментарий — в плашке Листа ДЗ и в карточке разбора ученика.
     Тип таска говорит ЧТО делать, а это — что именно задано (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:9px;background:var(--fill-soft);min-width:0;overflow-wrap:anywhere}
  .otext{font-size:13px;line-height:1.45;color:var(--ink);white-space:pre-wrap}
  .ocomment{font-size:12.5px;line-height:1.4;color:var(--muted)}
  .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:12.5px;
    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}
  .omut{font-size:12px;color:var(--muted)}
  .oarch{font-size:11px;background:var(--alert-fill);color:var(--alert-ink);padding:1px 6px;border-radius:6px}
  .opos{font-size:12px;color:var(--ok-ink);margin-top:3px}
  .opos.all{font-weight:700}
  .oneg{font-size:12px;color:var(--alert-ink);margin-top:2px}
  /* blox30 #2 — «открыл, но не дослушал»: промежуточное состояние между прослушал и не открывал */
  .omid{font-size:12px;color:var(--draft-ink);margin-top:2px}
  .omid svg,.opos svg{width:12px;height:12px;vertical-align:-1px}
  .oaxis{flex:0 0 auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:0 4px}
  .oaxis-line{width:2px;flex:1;background:var(--line);border-radius:2px;min-height:60px}
  .oaxis.now .oaxis-line{background:linear-gradient(var(--accent),var(--ok-line))}
  .oaxis-lbl{font-size:11px;color:var(--faint);writing-mode:vertical-rl;transform:rotate(180deg)}
  .oaxis.now .oaxis-lbl{color:var(--draft-ink);font-weight:800}
  .oaxis-now{font-size:11px;border:1px solid var(--line-soft);background:#fff;border-radius:7px;padding:3px 6px;cursor:pointer;white-space:nowrap}
  /* 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}
  .oroster .rchip{width:100%;justify-content:space-between;padding:6px 9px}
  /* 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:14px;color:var(--ink);padding:0 0 8px 2px;text-align:left}
  .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 конструктор (постановка) */
  .opane.build{border-color:var(--ok-line)}
  .opane.build.draft{border-color:var(--draft-line);background:var(--draft-tint)}
  .opane.build.pubedit{border-color:var(--ok-line);box-shadow:0 0 0 2px var(--ok-line) inset}
  .obuild-top{font-size:12px;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:12px;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 .26s cubic-bezier(.22,1,.36,1)}
  .ncar.grabbing{cursor:grabbing}
  .ncar::-webkit-scrollbar{height:6px}
  .ncar::-webkit-scrollbar-thumb{background:var(--line);border-radius:6px}
  .nday{flex:0 0 auto;width:42px;display:flex;flex-direction:column;align-items:center;gap:2px;
    padding:5px 2px;border:1.5px solid transparent;border-radius:10px;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:10px;color:var(--muted)}
  .nday .ndate{font-size:11px;color:var(--muted)}
  /* тонкая недельная полоса на стыке Вс→Пн: рисуется на ячейке понедельника (.wkstart) как
     ::before в зазоре слева. Высота = высоте плашки (top/bottom:0 тянут её на весь рост ячейки,
     ячейки в .ncar растянуты по stretch). Цвет = усам рамки темы (--frame-line) → перекрашивается
     при смене схемы. Не меняет ширину ячейки и не влияет на прокрутку (blox28 #2). */
  .nday.wkstart{position:relative}
  .nday.wkstart::before{content:'';position:absolute;left:-1px;top:0;bottom:0;width:1.5px;
    border-radius:1px;background:var(--frame-line)}
  .nday.lday{cursor:pointer;background:#fff;border-color:var(--line-soft);position:relative}
  /* 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}
  .nday.lday .ndate{font-size:12px;font-weight:700;color:var(--ink)}
  .nday.lday::after{content:'';width:6px;height:6px;border-radius:50%;background:var(--line);margin-top:1px}
  .nday.lday-ok::after{background:var(--ok)}                            /* нечего делать: сдавать нечего / всё проверено */
  .nday.lday-alert::after{background:var(--alert)}                   /* учителю надо что-то сделать */
  .nday.act-left{border-color:var(--ink);border-style:solid;box-shadow:0 0 0 1px var(--ink) inset}
  .nday.act-right{border-color:var(--ink);border-style:dashed}
  .nday.nag{border-color:var(--alert)!important;box-shadow:0 0 0 1px var(--alert) inset}  /* нужно выдать ДЗ */
  .nday.viewing{background:var(--accent-tint)}                                   /* какую пару сейчас смотрим */
  .nday.today{opacity:1}                                             /* сегодня всегда яркий, даже если не день занятия */
  .nday.today .ndate{display:flex;align-items:center;justify-content:center;width:22px;height:22px;
    border:1.5px solid var(--ink);border-radius:50%;color:var(--ink);font-weight:800;font-size:12px}
  /* «Вернуться к сегодня» без кнопки: кольцо ПОД лентой. Оттягивание ленты вниз открывает его
     сверху, а дуга замыкается по мере оттягивания (--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 .12s}
  .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)}}
  @media(max-width:760px){
    .otwo{flex-direction:column-reverse}   /* DOM: earlier, axis, later -> reversed = later on TOP */
    .opane{flex:1 1 100%;margin:6px 0}
    .oaxis{flex:1 1 100%;flex-direction:row;padding:4px 0}
    .oaxis-line{height:2px;width:100%;min-height:0}
    .oaxis-lbl{writing-mode:horizontal-tb;transform:none}
    .oarr{width:100%;height:34px}
    .opane-body{flex-direction:column;align-items:stretch}
    .oroster{flex:1 1 auto;flex-direction:row;flex-wrap:wrap}
    .oroster .rchip{width:auto}
  }
  /* таск в черновике ДЗ = такая же плашка, как у ученика и на проверке: белый прямоугольник,
     тонкая рамка и цветной рельс слева (--rail: серый / синий «нужна сдача») — blox10 #8 */
  .bitem{--rail:var(--task-plain);
    display:flex;align-items:flex-start;flex-wrap:wrap;gap:8px 12px;padding:12px;
    border:1px solid var(--line-soft);border-left:6px solid var(--rail);border-radius:var(--r-lg);
    background:var(--card);box-shadow:var(--sh-1);margin-bottom:8px}
  .bitem .bico{font-size:20px}
  .bitem .binfo{flex:1 1 calc(100% - 44px);min-width:0;overflow-wrap:anywhere}
  /* кнопки действий уходят на свою строку под текстом, вся тройка прижата вправо */
  .bitem .act:first-of-type{margin-left:auto}
  .bitem .bt{font-weight:700}
  .bitem .bm{font-size:12px;color:var(--muted);margin-top:2px}
  .bitem .act{background:none;border:none;cursor:pointer;font-size:13px;font-weight:700;
    color:var(--accent-dark);padding:4px}
  .bitem .act.del{color:var(--red)}
  /* ПОСТАНОВКА: тема = рамка. Рамка — общая (см. .tframe,.oframe,.theme выше), тут только
     свой padding/margin. */
  .theme{padding:4px 14px 14px;margin:0 0 18px}
  .theme .tin{background:transparent;border-radius:0;padding:0}
  .theme .thead{display:flex;align-items:center;gap:8px;margin-bottom:10px}
  .theme .tname{flex:1;min-width:0;width:100%;-webkit-appearance:none;appearance:none;
    text-align:center;border:none;border-bottom:1px dashed var(--line);
    font-weight:800;font-size:16px;padding:4px 0 7px;background:transparent;color:var(--ink)}
  .theme .tname:focus{outline:none;border-bottom-color:var(--accent)}
  .theme .tdel{background:none;border:none;color:var(--red);cursor:pointer;font-size:18px;
    line-height:1;padding:2px 4px}
  /* привязка темы к учебнику: кнопка «Учебник» / название + карусель юнитов */
  .theme .tbook{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin:-2px 0 12px;
    justify-content:center;position:relative}
  .tb-add{background:var(--accent-tint);border:1px solid var(--accent-line);color:var(--accent-dark);font-weight:700;
    border-radius:9px;padding:5px 11px;cursor:pointer;font-size:13px}
  .tb-add:hover{background:var(--accent-fill)}
  .tb-name{font-weight:800;font-size:13px;color:var(--accent-dark);background:var(--accent-tint);
    border-radius:9px;padding:5px 10px}
  .tb-unit{display:inline-flex;align-items:center;gap:6px;background:#fff;border:1px solid var(--accent-line);
    border-radius:9px;padding:2px 4px}
  .tb-unit button{background:none;border:none;color:var(--accent-dark);cursor:pointer;font-size:14px;
    padding:2px 6px;line-height:1}
  .tb-unit button:disabled{color:var(--faint);cursor:default}
  .tb-lbl{font-weight:800;font-size:13px;color:var(--ink);min-width:64px;text-align:center}
  .tb-unbind{background:none;border:none;color:var(--faint);cursor:pointer;font-size:12px;
    text-decoration:underline;padding:4px}
  .tb-unbind:hover{color:var(--red)}
  .tb-menu{position:absolute;top:100%;left:50%;transform:translateX(-50%);z-index:30;background:#fff;
    border:1px solid var(--accent-line);border-radius:11px;box-shadow:var(--sh-3);padding:6px;
    display:flex;flex-direction:column;gap:4px;min-width:190px;margin-top:4px}
  .tb-menu button{background:none;border:none;text-align:left;padding:8px 10px;border-radius:8px;
    cursor:pointer;font-weight:700;font-size:13px;color:var(--ink)}
  .tb-menu button:hover{background:var(--accent-tint)}
  /* пустой шаблон сущности (три точки) — всегда внизу рамки и внизу области вне тем */
  .eslot{display:flex;align-items:center;justify-content:center;gap:10px;border:2px dashed var(--line);
    border-radius:12px;padding:13px;color:var(--muted);font-weight:700;cursor:pointer;background:var(--surface-2)}
  .eslot:hover{border-color:var(--accent);color:var(--accent-dark);background:var(--accent-tint)}
  .eslot .dots{font-size:20px;letter-spacing:3px;line-height:1}
  /* «+ Новая тема» стоит на той же левой линии, что «+ таск вне темы» (blox13 #4) */
  .newtheme{border:2px dashed var(--line-mid);background:#fff;color:var(--accent-dark);font-weight:800;
    border-radius:12px;padding:11px 16px;cursor:pointer;margin:2px 0 16px 15px}
  .newtheme:hover{border-color:var(--accent)}
  .standlbl{font-size:12px;font-weight:700;color:var(--muted);margin:6px 0 6px 15px}
  /* «вне темы» стоит по той же левой линии, что таски внутри рамки (граница 1.25px + падинг 14px) */
  .standarea{margin:0 14px 0 15px}
  /* blox26 #5: ЗЕРКАЛИРОВАНИЕ рамки темы по горизонтали (настройка, тумблер рядом со шрифтом/схемой).
     Скруглённый+закрытый левый край уходит ВПРАВО, открытая грань — влево. Один атрибут
     data-frame="mirror" на :root отражает рамку СРАЗУ НА ВСЕХ экранах.
     Рамка теперь ЕДИНАЯ псевдоэлементом — отражаем все три одним правилом: закрытая
     сторона уходит ВПРАВО, усы (top/bottom) кончаются слева. В зеркалке открытая сторона
     совпадает со стороной цветного рельса таска (он всегда слева), поэтому подтягиваем усы
     ещё внутрь на ШИРИНУ рельса (--frail): ус кончается там, где рельс ЗАКАНЧИВАЕТСЯ, и
     цветная окантовка целиком торчит наружу. (У монитора рельс тоньше — --frail:3px.) */
  :root[data-frame="mirror"] .tframe::before,
  :root[data-frame="mirror"] .oframe::before,
  :root[data-frame="mirror"] .theme::before{
    left:calc(var(--fpad) + var(--frail));right:0;
    border:1.5px solid var(--frame-line);border-left:none;
    border-radius:0 var(--r-xl) var(--r-xl) 0}
  :root[data-frame="mirror"] .oframe>.ofh{text-align:right;padding:0 2px 8px 0}
  /* окно заполнения сущности — модальное поверх экрана */
  .emodal{position:fixed;inset:0;background:rgba(37,26,46,.48);display:flex;align-items:flex-start;
    justify-content:center;padding:36px 14px;z-index:60;overflow:auto}
  /* position:relative — иначе крестик .eclose (position:absolute) цеплялся за .emodal (весь экран)
     и висел над тёмной шапкой на top:10px, а не в углу окна — почти невидимый (blox17 #4). */
  .emodal .panel{max-width:540px;width:100%;margin:0;position:relative}
  .emodal .eclose{position:absolute;top:10px;right:12px;background:none;border:none;font-size:22px;
    color:var(--muted);cursor:pointer;line-height:1;z-index:1}
  .pool-note{background:var(--accent-tint);border:1px solid var(--accent-line);border-radius:12px;padding:12px 14px;
    font-size:13px;color:var(--accent-dark);margin-bottom:8px;font-weight:600}
  /* student: the standing text note ("объявление") — a half-screen scrollable block */
  /* объявление — a standing note. Lavender, so it never reads as a "state" (alert/draft/done). */
  .annc{background:var(--note-bg);border:1px solid var(--note-line);border-radius:var(--r-lg);
    margin:8px 0 14px;overflow:hidden;display:flex;flex-direction:column;box-shadow:var(--sh-1)}
  .annc .ah{font-size:var(--fs-2xs);font-weight:800;letter-spacing:var(--tracking-caps);
    text-transform:uppercase;color:var(--note-ink);padding:12px 16px 6px}
  .annc .abody{max-height:50vh;overflow-y:auto;-webkit-overflow-scrolling:touch;
    padding:0 16px 16px;font-size:var(--fs-sm);line-height:1.55;color:var(--ink);white-space:pre-wrap;
    word-break:break-word;overscroll-behavior:contain}
  .annc .abody a{color:var(--accent-ink);text-decoration:underline;font-weight:600}
  /* blox27 #3 — учительский feedback за урок: ЗОЛОТОЙ блок в бывшем слоте объявления (само
     объявление переехало НАД карусель дней). Форма как у .annc, тема золотая. */
  .fbnote{background:var(--gold-tint);border:1px solid var(--gold-line);border-radius:var(--r-lg);
    margin:8px 0 14px;overflow:hidden;display:flex;flex-direction:column;box-shadow:var(--sh-1)}
  .fbnote .fbh{font-size:var(--fs-2xs);font-weight:800;letter-spacing:var(--tracking-caps);
    text-transform:uppercase;color:var(--gold-ink);padding:12px 16px 6px;display:flex;align-items:center;gap:6px}
  .fbnote .fbh .ic{color:var(--gold)}
  .fbnote .fbbody{max-height:50vh;overflow-y:auto;-webkit-overflow-scrolling:touch;
    padding:0 16px 16px;font-size:var(--fs-sm);line-height:1.55;color:var(--ink);white-space:pre-wrap;
    word-break:break-word;overscroll-behavior:contain}
  .fbnote .fbbody a{color:var(--accent-ink);text-decoration:underline;font-weight:600}
  .toolbar{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}
  /* кнопки конструктора («Выдать ученикам» / «Подтянуть…») по той же левой линии, что «+ Новая
     тема» и «+ таск» (margin-left:15px) — blox16 #8 */
  .toolbar.bbar{margin-left:15px}
  /* .btn-ghost → folded onto the engine's --ghost role (the base look). Modifiers below. */
  /* blox24 #7 — «Очистить» весь лист ДЗ: разрушительное действие, поэтому тревожный акцент */
  .btn-ghost.bdanger{color:var(--alert)}
  .btn-ghost.bdanger:hover{border-color:var(--alert);background:var(--alert-tint)}
  /* счётчик долгов ученика (blox19 #2) — тревожный акцент, чтобы бросался в глаза */
  .btn-ghost.sdebt{color:var(--alert-ink);border-color:var(--alert-line);background:var(--alert-tint);font-weight:800}
  .btn-ghost.sdebt:hover{border-color:var(--alert);background:var(--alert-tint)}
  .btn-ghost.sdebt .ic{color:var(--alert)}
  /* .btn-pub → --accept, .btn-fb base → gold knobs — both in the BUTTON ENGINE block.
     Kept here: fb icon colour + the «sent» (green ✓) state, which override the base. */
  .btn-fb .ic{color:var(--gold)}
  .btn-fb.sent{--btn-line:var(--ok-line);--btn-face:var(--ok-tint);--btn-ink:var(--ok-ink)}
  .btn-fb.sent .ic{color:var(--ok-solid)}

  /* ═══════════════════════════════════════════════════════════════════════
     BUTTON ENGINE (branch design-system) — ONE base + role variants.
     A text button = .btn + one .btn--<role> (+ optional .btn--sm).
     Geometry rides knob tokens (--btn-*) so a variant is a few lines and a
     later convergence is a one-value change, not a rewrite. Colour is ROLE-only
     (skill §11) so every button follows all three schemes automatically.
       Roles: --primary  gradient CTA (the one loud action on a screen)
              --accept   solid green confirm/publish («Выдать»/«Принять»/«Готово»)
              --ghost    outline secondary — the default look of the base
              --fill     solid accent (compact primary for dense cabinets)
              --danger   outline, alert-coloured (destructive)
     Phase 2 (branch design-system) = legacy names are FOLDED onto the engine
     here (aliased into the base + a role), and the geometry duplicated in their
     own rules below is stripped. HTML is untouched — so all their contextual
     overrides (.sheet .btn-ghost{width:100%}, .mgr-top3 .mgr-btn, .bdanger…)
     keep working — but every text button now shares ONE base: unified radius,
     padding, 700 weight, 44px touch target, and role-only colour. Map:
       .btn-primary → --primary · .btn-pub → --accept · .btn-ghost/.mgr-btn →
       --ghost · .mgr-btn.primary → --fill · .mgr-btn.warn → --danger ·
       .btn-fb keeps its gold identity but rides the base. */
  .btn,
  .btn-primary,.btn-pub,.btn-ghost,.btn-fb,.mgr-btn{
    /* knobs — a variant / size overrides these */
    --btn-r:var(--r-md); --btn-px:18px; --btn-py:11px; --btn-fs:14px; --btn-fw:700;
    --btn-face:var(--card); --btn-ink:var(--ink); --btn-line:var(--line);
    display:inline-flex;align-items:center;justify-content:center;gap:6px;
    box-sizing:border-box;min-height:44px;                     /* G5 touch target */
    border:1px solid var(--btn-line);border-radius:var(--btn-r);
    padding:var(--btn-py) var(--btn-px);
    font-family:inherit;font-size:var(--btn-fs);font-weight:var(--btn-fw);line-height:1.2;
    color:var(--btn-ink);background:var(--btn-face);cursor:pointer;box-shadow:var(--sh-1); /* resting lift — even the outline roles float a touch */
    transition:border-color var(--t) var(--ease),background var(--t) var(--ease),
               box-shadow var(--t) var(--ease),transform var(--t-fast) var(--ease)}
  .btn:disabled,.btn-primary:disabled,.btn-pub:disabled,
  .btn-ghost:disabled,.mgr-btn:disabled{opacity:.45;cursor:default;box-shadow:none;transform:none}
  .btn:focus-visible,.btn-primary:focus-visible,.btn-pub:focus-visible,
  .btn-ghost:focus-visible,.btn-fb:focus-visible,.mgr-btn:focus-visible{outline:none;box-shadow:var(--ring)}
  .btn .ic{color:currentColor}
  /* size — compact (a fine-tuning tool; apply .btn--sm where density matters) */
  .btn--sm{--btn-px:14px;--btn-py:8px;--btn-fs:13px;--btn-r:var(--r-sm);min-height:38px}
  /* roles (legacy names aliased onto each). Filled roles carry --gloss (a lit top rim)
     ON TOP of their drop shadow, and a gradient fill (--grad-*) instead of a flat colour. */
  .btn--ghost:hover,.btn-ghost:hover,.mgr-btn:hover{border-color:var(--accent);background:var(--accent-tint);box-shadow:var(--sh-2)}
  /* filled hovers glow in the role's OWN colour (color-mix from the role token → follows
     every scheme on its own), so hover reads as the button lighting up, not a grey shadow. */
  .btn--primary,.btn-primary{--btn-line:transparent;--btn-ink:var(--on-dark);
    background:var(--grad-primary);box-shadow:var(--gloss),var(--sh-1)}
  .btn--primary:hover,.btn-primary:hover{box-shadow:var(--gloss),0 6px 18px color-mix(in srgb,var(--primary) 42%,transparent)}
  .btn--accept,.btn-pub{--btn-line:transparent;--btn-ink:var(--on-dark);
    background:var(--grad-accept);box-shadow:var(--gloss),var(--sh-1)}
  .btn--accept:hover,.btn-pub:hover{box-shadow:var(--gloss),0 6px 18px color-mix(in srgb,var(--ok-solid) 40%,transparent)}
  .btn--fill,.mgr-btn.primary{--btn-line:var(--accent-solid);--btn-ink:var(--on-dark);
    background:var(--grad-fill);box-shadow:var(--gloss),var(--sh-1)}
  .btn--fill:hover,.mgr-btn.primary:hover{filter:brightness(1.04);box-shadow:var(--gloss),0 6px 18px color-mix(in srgb,var(--accent-solid) 40%,transparent)}
  .btn--danger,.mgr-btn.warn{--btn-ink:var(--alert)}
  .btn--danger:hover,.mgr-btn.warn:hover{border-color:var(--alert);background:var(--alert-tint);box-shadow:var(--sh-2)}
  /* .btn-fb keeps its gold identity — set via knobs AFTER the base so they win.
     blox27 #3: gold until sent (calls to write feedback), green ✓ once sent. */
  .btn-fb{--btn-line:var(--gold-line);--btn-face:var(--gold-tint);--btn-ink:var(--gold-ink);--btn-fw:800}
  .btn-fb:hover{border-color:var(--gold);box-shadow:var(--sh-2)}
  /* juicy MOTION — lift on hover, press on active. transform = no reflow (no CLS, G13).
     :active is declared AFTER :hover so a mouse-down (both true on a PC) reads as a press,
     not a stuck lift; :not(:disabled) keeps «Недоступно» from bobbing on hover. */
  .btn:hover:not(:disabled),.btn-primary:hover:not(:disabled),.btn-pub:hover:not(:disabled),
  .btn-ghost:hover:not(:disabled),.btn-fb:hover:not(:disabled),.mgr-btn:hover:not(:disabled){transform:translateY(-1px)}
  .btn:active,.btn-primary:active,.btn-pub:active,
  .btn-ghost:active,.btn-fb:active,.mgr-btn:active{transform:translateY(0) scale(.97);box-shadow:var(--sh-press)} /* press = pushed in */
  @media (prefers-reduced-motion:reduce){
    .btn,.btn-primary,.btn-pub,.btn-ghost,.btn-fb,.mgr-btn{transition:border-color var(--t) var(--ease),background var(--t) var(--ease),box-shadow var(--t) var(--ease)}
    .btn:hover,.btn-primary:hover,.btn-pub:hover,.btn-ghost:hover,.btn-fb:hover,.mgr-btn:hover,
    .btn:active,.btn-primary:active,.btn-pub:active,.btn-ghost:active,.btn-fb:active,.mgr-btn:active{transform:none}
  }

  /* STATE · loading — spinner replaces the label, clicks blocked (guards double-submit).
     The spinner borrows --btn-ink (still set even though `color` is transparent), so it's
     white on filled roles / ink on ghost automatically. reduced-motion slows it, never
     kills it — it's a functional indicator, not decoration (same call as .pl-spinner). */
  .btn.is-loading,.btn-primary.is-loading,.btn-pub.is-loading,.btn-ghost.is-loading,.mgr-btn.is-loading,.pool-dlall.is-loading{
    color:transparent;pointer-events:none;position:relative}
  .btn.is-loading::after,.btn-primary.is-loading::after,.btn-pub.is-loading::after,
  .btn-ghost.is-loading::after,.mgr-btn.is-loading::after,.pool-dlall.is-loading::after{
    content:"";position:absolute;top:calc(50% - 8px);left:calc(50% - 8px);width:16px;height:16px;
    border-radius:50%;border:2px solid var(--btn-ink);border-top-color:transparent;
    animation:btn-spin .7s linear infinite}
  @keyframes btn-spin{to{transform:rotate(360deg)}}
  @media (prefers-reduced-motion:reduce){
    .btn.is-loading::after,.btn-primary.is-loading::after,.btn-pub.is-loading::after,
    .btn-ghost.is-loading::after,.mgr-btn.is-loading::after{animation-duration:2s}}
  /* STATE · done — a brief green «✓ сработало» morph (generalises .btn-fb.sent). The caller
     swaps the label to a ✓ and drops the class after ~1s. Green tint overrides any role fill. */
  .btn.is-done,.btn-primary.is-done,.btn-pub.is-done,.btn-ghost.is-done,.mgr-btn.is-done{
    --btn-line:var(--ok-line);--btn-ink:var(--ok-ink);
    background:var(--ok-tint);filter:none;box-shadow:var(--sh-1)}

  /* ICON BUTTON — square/round, icon-only (✕ close · ← back · ⋯ kebab · remove).
     Collapses the ~15 one-off close/remove classes. Base = square; --circle for
     the close-in-a-ring; --sm for the tiny inline remove; --danger for delete. */
  .icon-btn{
    --ib-size:38px; --ib-r:var(--r-sm); --ib-ink:var(--muted); --ib-face:transparent;
    display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
    box-sizing:border-box;width:var(--ib-size);height:var(--ib-size);
    border:1px solid var(--line);border-radius:var(--ib-r);
    background:var(--ib-face);color:var(--ib-ink);cursor:pointer;font-size:18px;line-height:1;
    transition:border-color var(--t) var(--ease),background var(--t) var(--ease),
               color var(--t) var(--ease),transform var(--t-fast) var(--ease)}
  .icon-btn:hover{border-color:var(--accent);background:var(--accent-tint);color:var(--accent-ink)}
  .icon-btn:active{transform:scale(.94)}
  .icon-btn:focus-visible{outline:none;box-shadow:var(--ring)}
  .icon-btn .ic{color:currentColor}
  .icon-btn--circle{--ib-r:var(--r-pill)}
  .icon-btn--sm{--ib-size:24px;font-size:14px}
  .icon-btn--danger:hover{border-color:var(--alert);background:var(--alert-tint);color:var(--alert-ink)}

  .tchip{display:inline-block;font-size:11px;font-weight:700;padding:2px 8px;border-radius:20px;
    background:var(--draft-tint);color:var(--alert-ink);margin-left:6px}
  /* ЧЕЛЛЕНДЖ (blox15 #6) — золото на учительских плашках: та же плашка, тот же рельс, другой цвет */
  .tchip.gold{background:var(--gold-tint);color:var(--gold-ink);border:1px solid var(--gold-line)}
  .bitem.chal{background:var(--gold-tint);border-color:var(--gold-line)}
  .oent.chal{background:var(--gold-tint)}
  .chal-lbl{color:var(--gold-ink)}
  .chal-lbl svg{vertical-align:-2px}
  .field select,.field textarea{border:1px solid var(--line);border-radius:10px;padding:10px 12px;
    font-size:15px;width:100%;font-family:inherit}
  .field textarea{min-height:70px;resize:vertical}
  /* blox28 #9: у ВИДА «Текст» (read → body) поле ввода втрое выше — учитель пишет туда развёрнутое
     задание, а не короткий комментарий. Только для текста: у остальных видов поле-комментарий
     остаётся обычным, чтобы окно таска не разъезжалось по высоте (см. blox28 #5). */
  .field textarea.tall{min-height:210px}
  /* blox34 #11 — на ТЕЛЕФОНЕ поле «Текст» у видов Файл/Ссылка/Квиз вдвое выше (70→140px). У вида
     «Текст» оно и так втрое выше (.tall), поэтому правило его не касается. Низ окна не двигается:
     середина .taskmid фиксирована, лишнее уходит в её собственную прокрутку.
     ПОРОГ ПО ВЫСОТЕ ЭКРАНА — не украшение. Резерв середины ≈ высота экрана − 505px, а вид «Ссылка»
     держит в нём ДВА поля: удвоенный «Текст» (158 с подписью) + саму ссылку (62). На экране 667px
     (iPhone SE/8) резерв всего 162 — поле ссылки уехало бы под прокрутку, а оно обязательное.
     Поэтому удваиваем там, где место есть (812/844/926 — все актуальные айфоны), на коротком
     экране поле остаётся прежним. */
  @media(max-width:859px) and (min-height:730px){ .emodal .taskmid textarea:not(.tall){min-height:140px} }
  .students-pick{display:flex;flex-wrap:wrap;gap:8px}
  .students-pick label{display:flex;align-items:center;gap:6px;font-size:14px;
    border:1px solid var(--line);border-radius:20px;padding:6px 12px;cursor:pointer;font-weight:600}
  /* blox34 #9 — в окне таска состав учеников СКРОЛЛИТСЯ внутри себя: большой класс иначе растил бы
     окно вниз (кнопка «Сохранить» уходила бы за край — самая переподаваемая беда проекта, G1v). */
  .emodal .students-pick{max-height:132px;overflow-y:auto}
  /* blox34 #9 — строка двух флажков таска: ЧЕЛЛЕНДЖ и ЛИЧНОЕ. На ПК «Личное» прижато к правому
     краю, на телефоне падает под челлендж отдельной строкой (flex-basis:100%), а не дублируется
     второй разметкой под медиазапрос. Каждый флажок с подписью — один неразрывный узел .chkgrp,
     чтобы при переносе галочка не оторвалась от своей подписи (lingo-ui #3). */
  .chkrow{flex-wrap:wrap;row-gap:8px}
  .chkgrp{display:flex;align-items:center;gap:8px}
  .chkgrp input:disabled + label{opacity:.45;cursor:not-allowed}
  .chkrow .indiv-grp{margin-left:auto}
  .excl-hint{margin:-4px 0 10px}
  @media(max-width:859px){ .chkrow .indiv-grp{margin-left:0;flex-basis:100%} }
  optgroup{font-weight:700}

  /* student: full-width standing-playlist bar — the app's one deliberately loud object.
     Was a grape body with a thin rainbow edge; now the palette gradient IS the bar (blox13 #5).
     Text goes dark grape, never white: white on a light sky/veil/grapefruit fill is unreadable. */
  .poolbar{position:relative;display:flex;align-items:center;gap:14px;border-radius:var(--r-xl);
    padding:18px 20px;color:var(--ink-strong);background:var(--grad-playlist);overflow:hidden;
    box-shadow:var(--sh-3);cursor:pointer;margin-bottom:18px;
    transition:transform var(--t-fast) var(--ease),box-shadow var(--t) var(--ease)}
  .poolbar::after{content:"";position:absolute;right:-40px;top:-60px;width:180px;height:180px;
    border-radius:50%;background:radial-gradient(circle,rgba(255,255,255,.45),transparent 65%);pointer-events:none}
  .poolbar:hover{box-shadow:var(--sh-4)}
  .poolbar .pbtext{flex:1;min-width:0;position:relative}
  .poolbar .pbt{font-weight:800;font-size:var(--fs-xl);letter-spacing:var(--tracking-tight)}
  .poolbar .pbm{font-size:var(--fs-sm);color:var(--ink);font-weight:700;margin-top:3px}
  /* the weekly stat is a full sentence, so it reads as a quiet footnote — not a pill */
  .poolbar .pbweek{font-size:var(--fs-xs);font-weight:600;line-height:1.45;margin-top:10px;
    padding-top:9px;border-top:1px solid rgba(73,54,87,.22);color:var(--ink);
    max-width:32ch}
  /* кружок «играть» — тёмный, чтобы на пастельной радуге он был якорем, а не растворялся */
  .poolbar .pbplay{position:relative;width:56px;height:56px;border-radius:50%;background:var(--primary);
    color:#fff;display:flex;align-items:center;justify-content:center;font-size:24px;
    flex:0 0 auto;box-shadow:0 6px 18px rgba(73,54,87,.30);
    transition:transform var(--t) var(--ease)}
  .poolbar:hover .pbplay{transform:scale(1.06)}
  .poolbar:active{transform:scale(.99)}
  /* link / file button (review panel); teacher comment bubble */
  .linkbtn{display:block;text-align:center;text-decoration:none;border-radius:12px;padding:9px;
    font-size:15px;font-weight:700;background:rgba(255,255,255,.92);color:var(--ink)}
  .comment{font-size:12px;font-weight:600;opacity:.95;background:rgba(255,255,255,.18);
    border-radius:8px;padding:5px 8px}
  /* add-square type tiles (the popup) */
  /* blox28 (owner) — зазор между плитками ВИДА и полем «Текст» убран (был 14+10px): освобождённую
     высоту тратим на то, чтобы окно таска на телефоне не уезжало вниз. Отступ снизу теперь даёт
     только сама .field (10px). */
  .typepick{display:flex;gap:8px;margin-bottom:0;flex-wrap:wrap}
  .typepick button{flex:1;min-width:68px;border:1px solid var(--line);background:#fff;border-radius:12px;
    padding:12px 6px;display:flex;flex-direction:column;align-items:center;gap:6px;cursor:pointer;
    font-weight:700;font-size:13px;color:var(--muted)}
  .typepick button.on{border-color:var(--accent);background:var(--accent-tint);color:var(--accent-dark)}
  /* blox25 #10 — вид нельзя сменить (есть сдачи / личное / челлендж): плитки видны, но не жмутся */
  .typepick button:disabled{opacity:.55;cursor:default}
  .typepick button:disabled.on{opacity:1}
  .typepick .ti{font-size:24px}
  /* textbook source inside the entity modal: book carousel + exercise grid */
  .bk-car{display:flex;align-items:center;justify-content:center;gap:10px;margin:0 0 14px}
  .bk-lbl{font-weight:800;font-size:14px;color:var(--accent-dark);min-width:150px;text-align:center}
  .bk-car button{background:var(--accent-tint);border:1px solid var(--accent-line);color:var(--accent-dark);
    border-radius:9px;padding:4px 11px;cursor:pointer;font-size:15px;line-height:1}
  .bk-car button:disabled{color:var(--faint);background:var(--surface);cursor:default}
  .ex-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-bottom:14px}
  .ex-tile{position:relative;text-align:left;border:1px solid var(--line);background:#fff;border-radius:12px;
    padding:10px 30px 10px 12px;cursor:pointer;display:flex;flex-direction:column;gap:3px;min-height:54px}
  .ex-tile:hover{border-color:var(--accent);background:var(--accent-tint)}
  .ex-no{font-weight:800;font-size:13px;color:var(--accent-dark)}
  .ex-tt{font-size:13px;color:var(--ink);line-height:1.25;
    display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
  .ex-aud{position:absolute;top:8px;right:10px;font-size:13px}
  .src-ref{font-weight:700;font-size:13px;color:var(--accent-dark);background:var(--accent-tint);
    border-radius:9px;padding:8px 11px;margin-bottom:12px}
  .manual-sw{width:100%}
  /* blox3: record-mode quick-insert buttons under a "Текст" field */
  /* record-mode single-choice status picker (builder) */
  .recpick{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap;align-items:center}
  /* blox34 #10 — резерв под самый высокий набор типов (см. recPicker в builder.js): настоящий пикер
     и невидимый «призрак» полного набора лежат в ОДНОЙ ячейке грида, поэтому высота стопки = высота
     призрака при любом ВИДЕ и любой ширине телефона. Призрак невидим (visibility), а не display:none
     — иначе он не занимал бы места; для пальца и скринридера его нет. */
  .recstack{display:grid}
  .recstack > .recpick{grid-area:1/1}
  .recghost{visibility:hidden;pointer-events:none}
  .recb{border:1px solid var(--line);background:#fff;border-radius:9px;padding:6px 10px;
    cursor:pointer;font-size:13px;line-height:1;display:flex;align-items:center;gap:4px;font-family:inherit}
  .recb:hover{border-color:var(--accent);background:var(--accent-tint)}
  .recb.on{border-color:var(--accent-dark);background:var(--accent-fill);font-weight:700;
    box-shadow:inset 0 0 0 1px var(--accent-dark)}
  /* На ПК окно таска шире, и все ПЯТЬ типов стоят одной строкой: пять равных колонок, длинная
     подпись переносится ВНУТРИ кнопки, а не роняет кнопку на вторую строку (blox15 #4). */
  @media(min-width:860px){
    .emodal .panel{max-width:840px}
    .recpick{display:grid;grid-template-columns:repeat(5,1fr);gap:8px}
    /* ПОСЛЕ .recpick: у призрака оба класса, и при равной специфичности выигрывает правило ниже —
       иначе он остаётся в потоке и резервирует место там, где резерв не нужен. На ПК разброс уходит
       в высоту окна, а верх окна стоит намертво (blox29 #1 / blox30 #1) — призрак только съел бы
       высоту у окна, которое и так восемь раз не влезало в экран. */
    .recghost{display:none}
    .recb{justify-content:center;text-align:center;line-height:1.2;white-space:normal;padding:8px 8px}
    /* blox28 #5 — на ПК плитки ВИДА (Файл/Текст/Ссылка/Квиз) кладём иконку и подпись В ОДНУ строку
       и жмём по вертикали: высвобождает место, чтобы окно таска влезало в экран целиком. На узких
       экранах остаётся вертикальная раскладка (иконка над подписью) — там ширины на строку нет. */
    .typepick button{flex-direction:row;padding:9px 8px;gap:8px}
    .typepick .ti{font-size:19px}
  }
  /* blox29 #1 — окно таска держит НИЗ на месте при смене ВИДа МЕХАНИЧЕСКИ, без ручной подгонки высот.
     Раньше это делали ПЯТЬ взаимозависимых констант (поле 70/210, резерв 325/340, компенсация 185/200,
     проводник 224/170): меняешь одну — пересчитывай остальные руками, иначе низ окна прыгает, и это
     видно только по скриншоту (история: blox28 #5/#9/#14 — всё ручные перетюнинги этих чисел). Теперь:
     обёртка .taskmid с ФИКСИРОВАННЫМ минимальным резервом, а #a-fields — единственный растущий ребёнок
     (flex:1), который сам добирает/отдаёт разницу. Стало выше поле «Текст» — середина ужалась ровно на
     столько же; сумма (а значит и низ окна) неизменна САМА, без правки резерва. Резерв — ОДНО число на
     ширину (телефон/ПК), заданное под самый высокий вид «Файл»; всё прочее выводится из него. */
  .emodal{padding-top:22px;padding-bottom:22px}
  .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 - 44px)}
  .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}
  /* #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}
  /* вид «Файл»: поле-обёртка и панель проводника растягиваются на всю середину, поэтому карточка
     выбранного файла / сообщение «Забираем файл…» ужимают САМ проводник (он 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) */
  .overlay{position:fixed;inset:0;background:rgba(37,26,46,.52);display:flex;align-items:flex-end;
    justify-content:center;z-index:50}
  .sheet{background:#fff;width:100%;max-width:520px;border-radius:18px 18px 0 0;padding:18px;
    max-height:90vh;overflow:auto;box-shadow:0 -8px 34px rgba(73,54,87,.18)}
  @media(min-width:560px){ .overlay{align-items:center} .sheet{border-radius:18px} }
  /* blox27 #1 — «верхняя» шторка (текст таска): падает от верха под шапкой, а не от низа экрана.
     top задаётся из JS высотой шапки; правило только переключает выравнивание и скругление углов.
     .topsheet перебивает и медиа-правило align-items:center выше (специфичность 0,2,0 > 0,1,0). */
  .overlay.topsheet{align-items:flex-start}
  .overlay.topsheet .sheet{border-radius:0 0 18px 18px}
  @media(min-width:560px){ .overlay.topsheet .sheet{border-radius:0 0 18px 18px} }
  .sheethead{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;font-size:16px}
  /* 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:14px;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:1.5px solid var(--line);border-radius:12px;padding:10px 12px;resize:vertical;
    background:var(--surface-2)}
  .sup-text:focus{outline:none;border-color:var(--accent)}
  .sup-err{color:var(--danger-ink);font-size:14px;font-weight:700;margin-top:8px}
  .sup-ok{text-align:center;padding:22px 6px;font-size:17px;font-weight:700;color:var(--ink)}
  .sup-ok span{display:inline-block;margin-top:6px;font-weight:400;font-size:15px;color:var(--muted)}
  /* PDF-просмотрщик (blox9 #5): почти во весь экран, свой ✕ и запасная ссылка «в браузере» */
  .overlay.pdfov{align-items:stretch;justify-content:stretch}
  .pdfbox{position:relative;background:#fff;display:flex;flex-direction:column;width:100%;height:100%;padding:12px 12px 0}
  .pdfbox .sheethead{margin-bottom:8px}
  .pdf-head-r{display:flex;align-items:center;gap:12px}
  /* blox18 #5: обе кнопки были еле заметны — «в браузере» это теперь явная кнопка-таблетка,
     а ✕ — заметный круг с фоном и рамкой (крупная область нажатия) */
  .pdf-newtab{font-size:14px;font-weight:700;color:var(--accent-dark);text-decoration:none;
    border:1.5px solid var(--accent);border-radius:var(--r-pill);padding:8px 16px;
    display:inline-flex;align-items:center;gap:5px;white-space:nowrap}
  .pdf-newtab:hover{background:var(--accent-tint)}
  .pdf-newtab:active{transform:scale(.96)}
  .pdfframe{flex:1;width:100%;border:none;border-radius:12px 12px 0 0;background:var(--surface)}
  .sheethead .x{border:none;background:none;font-size:20px;cursor:pointer;color:var(--muted)}
  .pdfbox .sheethead .x{width:40px;height:40px;border-radius:50%;border:1.5px solid var(--line);
    background:var(--surface-2);color:var(--ink);font-size:22px;line-height:1;flex:0 0 auto;
    display:flex;align-items:center;justify-content:center}
  .pdfbox .sheethead .x:hover{background:var(--fill-soft);border-color:var(--accent);color:var(--accent-dark)}
  .pdfbox .sheethead .x:active{transform:scale(.94)}
  /* blox24 #11: перетаскиваемый аудио-плеер поверх страницы учебника (наслушка + чтение вместе).
     Абсолютная плашка внутри .pdfbox; тянется за верхнюю ручку. */
  .pdf-fplayer{position:absolute;z-index:5;width:min(340px,calc(100% - 12px));
    background:#fff;border:1.5px solid var(--line);border-radius:16px;
    box-shadow:0 10px 30px rgba(37,26,46,.28);overflow:hidden;
    display:flex;flex-direction:column}
  .pdf-fplayer.dragging{box-shadow:0 16px 40px rgba(37,26,46,.4);opacity:.96}
  .pdf-fp-grip{display:flex;align-items:center;justify-content:space-between;gap:8px;
    padding:8px 10px 8px 14px;min-height:44px;
    background:var(--surface);border-bottom:1px solid var(--line-soft);
    color:var(--muted);font-size:14px;font-weight:700;cursor:grab;
    touch-action:none;user-select:none;-webkit-user-select:none}
  .pdf-fp-grip-l{display:inline-flex;align-items:center;gap:8px;min-width:0}
  /* ✕ на дальнем от заголовка краю шапки — меньше прежнего плеерного, вписан в свою строку (blox24 #11) */
  .pdf-fp-x{flex:0 0 auto;width:30px;height:30px;border-radius:50%;border:1px solid var(--line);
    background:#fff;color:var(--muted);font-size:15px;line-height:1;cursor:pointer;
    display:flex;align-items:center;justify-content:center;touch-action:manipulation}
  .pdf-fp-x:active{background:var(--surface)}
  .pdf-fplayer.dragging .pdf-fp-grip{cursor:grabbing}
  .pdf-fp-body{padding:10px 12px 12px}
  .pdf-fp-body .lp{gap:8px}
  .sheet .btn-ghost,.sheet .btn-primary{width:100%;margin-top:6px}
  .sheet .toolbar .btn-ghost,.sheet .toolbar .btn-pub{width:auto;flex:1}

  /* ---- ДОСЬЕ (blox23 #4): приватная шторка учителя — журнал по датам + цветные ярлыки.
     Вставляем/убираем один узел (никогда не перестраиваем список целиком) — прокрутка шторки
     не прыгает. Поля ≥16px, иначе Safari зумит страницу (lingo-ui rule 3). */
  .dsheet{max-width:560px}
  .dprivate{font-size:var(--fs-sm);color:var(--muted);margin:-4px 0 12px}
  /* авто-сводка группы: НЕ журнал (пересчитывается при каждом открытии) — отделена рамкой+фоном */
  .dsummary:empty{display:none}
  .dsummary{border:1px solid var(--line-soft);border-radius:12px;background:var(--surface-2);
    padding:11px 13px;margin-bottom:14px}
  .dsum-head{font-size:var(--fs-xs);font-weight:700;color:var(--muted);margin-bottom:7px;
    display:flex;align-items:center;gap:5px}
  .dsum-verdict{font-size:var(--fs-md);margin-bottom:8px}
  .dsum-verdict.v-good{color:var(--ok-ink)}
  .dsum-verdict.v-mid{color:var(--gold-ink)}
  .dsum-verdict.v-poor{color:var(--alert-ink)}
  .dsum-verdict.v-progress{color:var(--ink)}   /* предстоящий урок «в процессе» — нейтрально, не тревожно */
  .dsum-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
  .dsum-list li{font-size:var(--fs-sm);color:var(--ink);line-height:1.35;
    padding-left:14px;position:relative;overflow-wrap:anywhere}
  .dsum-list li::before{content:'•';position:absolute;left:2px;color:var(--muted)}
  .dsum-dim{color:var(--muted)}
  .dsum-empty{font-size:var(--fs-sm);color:var(--muted)}
  .dtags{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:10px}
  .dtags:empty{display:none}
  .dtag{display:inline-flex;align-items:center;gap:5px;font-size:13px;font-weight:700;
    padding:4px 6px 4px 12px;border-radius:var(--r-pill);border:1px solid;overflow-wrap:anywhere}
  .dtag .dtag-x{border:none;background:none;cursor:pointer;font-size:13px;line-height:1;
    color:inherit;opacity:.55;padding:0 2px}
  .dtag .dtag-x:hover{opacity:1}
  .dtag.c0{background:var(--fill-soft);border-color:var(--line-mid);color:var(--ink)}
  .dtag.c1{background:var(--ok-tint);border-color:var(--ok-line);color:var(--ok-ink)}
  .dtag.c2{background:var(--gold-tint);border-color:var(--gold-line);color:var(--gold-ink)}
  .dtag.c3{background:var(--accent-tint);border-color:var(--accent);color:var(--accent-dark)}
  .dtag.c4{background:var(--alert-tint);border-color:var(--alert-line);color:var(--alert-ink)}
  .dtagadd{display:flex;gap:8px;margin-bottom:16px}
  .dtagadd input{flex:1;min-width:0;font-size:16px;font-family:inherit;
    border:1px solid var(--line);border-radius:10px;padding:9px 12px;color:var(--ink)}
  .dtagadd input:focus{outline:none;border-color:var(--accent)}
  .dtagadd .btn-ghost{width:auto;flex:0 0 auto;margin-top:0}
  .dnew{margin-bottom:16px}
  .dnew textarea{width:100%;box-sizing:border-box;font-size:16px;font-family:inherit;
    min-height:80px;resize:vertical;border:1px solid var(--line);border-radius:10px;
    padding:10px 12px;color:var(--ink);margin-bottom:8px}
  .dnew textarea:focus{outline:none;border-color:var(--accent)}
  .dnew .btn-pub{width:100%}
  .djournal{display:flex;flex-direction:column;gap:10px}
  .dentry{border:1px solid var(--line-soft);border-radius:12px;padding:10px 12px;background:var(--surface-2)}
  .dwhen{display:flex;align-items:center;justify-content:space-between;
    font-size:var(--fs-xs);font-weight:700;color:var(--muted);margin-bottom:5px}
  .dentry-x{border:none;background:none;cursor:pointer;color:var(--muted);font-size:14px;
    line-height:1;padding:0 2px;opacity:.6}
  .dentry-x:hover{opacity:1;color:var(--alert-ink)}
  .dbody{font-size:15px;line-height:1.45;color:var(--ink);white-space:pre-wrap;overflow-wrap:anywhere}
  .reczone{display:flex;flex-direction:column;align-items:center;gap:6px;padding:6px 0}
  .recbtn{border:none;border-radius:12px;padding:12px 20px;font-weight:700;font-size:16px;cursor:pointer;
    background:var(--alert-tint);color:var(--alert-ink)}
  .recbtn.on{background:var(--alert-strong);color:var(--on-dark)}
  .reviewbody{margin-bottom:12px;display:flex;flex-direction:column;gap:10px}
  .cell.review{cursor:pointer;outline:2px solid rgba(73,54,87,.14)}
  /* teacher HOLLOW cell: white fill, TYPE-colour border, green corner badge (dot -> check) */
  .cell.hollow{background:#fff;border:2.5px solid var(--line);position:relative;color:var(--ink)}
  .cell.hollow .ccount{opacity:.75;font-size:13px}
  /* Угловой значок. Состояния сдачи/открытия он больше НЕ показывает (2026-08-17): в личке это
     дублировало чипы, которые говорят то же словами. Остался один носитель — «снят» (.ret). */
  .bdg{position:absolute;right:-5px;bottom:-5px;min-width:18px;height:18px;border-radius:6px;
    background:var(--ok-tint);color:var(--green);font-size:12px;line-height:18px;text-align:center;
    font-weight:800;box-shadow:0 1px 3px rgba(73,54,87,.28);padding:0 2px}
  .bdg.ret{background:var(--alert);color:#fff}
  /* clickable student name -> opens that kid's individual homework screen */
  td.name.slink{cursor:pointer}
  td.name.slink:hover{color:var(--accent-dark)}
  td.name .schev{color:var(--muted);font-weight:700}

  /* teacher: one kid's homework — review cards (white, TYPE-colour left stripe) */
  .srlist{display:flex;flex-direction:column;gap:10px}
  .srcard{background:#fff;border-radius:12px;box-shadow:var(--sh-1);
    padding:14px 16px;border-left:6px solid var(--line);position:relative}
  .srcard.acc{opacity:.62}
  .srcard .srtitle{font-weight:800;font-size:16px;padding-right:28px}
  .srcard .srstatus{font-size:13px;color:var(--muted);font-weight:600;margin-top:5px;
    display:flex;flex-wrap:wrap;gap:6px;align-items:center}
  /* blox30 #2 — индикаторы таска в личке: каждый факт своей плашкой, все видны одновременно
     (открыл · прослушал N · статус сдачи). Цвет = смысл (роль-токены, не сырые лестницы). */
  .srind{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:700;
    padding:2px 9px;border-radius:20px;border:1px solid var(--line);
    background:var(--surface);color:var(--muted-strong);white-space:nowrap}
  .srind svg{width:13px;height:13px}
  .srind.op{background:var(--accent-tint);color:var(--accent-ink);border-color:var(--accent-line)}
  .srind.sok{background:var(--ok-tint);color:var(--ok-ink);border-color:var(--ok-line)}
  .srind.spend{background:var(--alert-tint);color:var(--alert-ink);border-color:var(--alert-line)}
  .srind.sret{background:var(--draft-tint);color:var(--draft-ink);border-color:var(--draft-line)}
  .srcard .srattach{margin:10px 0}
  /* blox26 #3 — инлайновые стили img/audio/video убраны: сдача теперь рендерится сущностью .fileent
     (см. выше), а не голым медиа в потоке. */
  .srcard .srnote{font-size:13px;background:var(--surface);border-radius:8px;padding:6px 10px;margin-top:8px}
  /* prior (pre-edit) submission version, kept for old+new review */
  .srcard .srhist{margin-top:10px;border:1px dashed var(--line);border-radius:10px;padding:8px 10px;background:var(--surface-2)}
  .srcard .srhist-h{font-size:12px;font-weight:700;color:var(--faint);margin-bottom:6px}
  .srcard .srhist-item{margin-bottom:6px}
  .srcard .srmuted{font-size:13px;color:var(--faint);font-style:italic}
  .srcard .srlink{display:inline-block;background:var(--accent-tint);border:1px solid var(--line);color:var(--accent-dark);
    text-decoration:none;border-radius:10px;padding:8px 14px;font-weight:700;font-size:14px}
  .srcard .sractions{display:flex;gap:8px;margin-top:10px;flex-wrap:wrap}
  .srcard .sractions .btn-pub,.srcard .sractions .btn-ghost{flex:0 0 auto}
  .srcard textarea{border:1px solid var(--line);border-radius:10px;padding:9px 12px;font-size:14px;
    width:100%;font-family:inherit;min-height:54px}
  .srcard .bdg{top:12px;right:12px;bottom:auto;left:auto}
  .locknote{background:var(--alert-tint);border:1px solid var(--alert-line);border-radius:12px;padding:11px 14px;
    font-size:13px;color:var(--alert-ink);margin:6px 0 12px;font-weight:600}

  /* group chat */
  .msgs{display:flex;flex-direction:column;gap:8px;min-height:240px;max-height:60vh;overflow:auto;
    background:#fff;border-radius:14px;padding:12px;box-shadow:var(--sh-1);margin-bottom:10px}
  .msg-row{display:flex}
  .msg-row.mine{justify-content:flex-end}
  .bubble{max-width:80%;background:var(--surface);border-radius:14px;padding:8px 12px}
  .msg-row.mine .bubble{background:var(--accent-fill)}
  .bubble .who{font-size:11px;font-weight:800;color:var(--accent-dark);margin-bottom:2px;word-break:break-word}
  .bubble .txt{font-size:15px;white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere}
  .bubble .time{font-size:10px;color:var(--muted);margin-top:3px;text-align:right}
  .chatbar{display:flex;gap:8px}
  /* страница Chats (blox32 #2): у встроенной панели родит.-канала первый .dprivate без верх. отступа */
  .chats-page>.dprivate:first-child{margin-top:0}
  /* min-width:0 обязателен: без него поле в flex-строке не сжимается уже своей «родной» ширины
     и распирает строку за правый край экрана (blox11 #3) */
  .chatbar input{flex:1;min-width:0;border:1px solid var(--line);border-radius:12px;padding:11px 14px;font-size:15px}
  /* width:auto перебивает «.sheet .btn-primary{width:100%}» (строка 724): в шторке родит.-чата
     эта кнопка «отправить» иначе растягивается на всю строку и давит поле ввода (blox23 #3 fix) */
  .chatbar .btn-primary{padding:11px 16px;flex:0 0 auto;width:auto}

  /* blox32 #2 — кружок-счётчик непрочитанных в углу кнопки/таба. ОДИН класс на все места (кнопка
     «Chats» учителя, «Чат группы» ученика, табы страницы Chats). pointer-events:none — клик проходит
     сквозь кружок на кнопку. Красное-под-белым = --alert-solid; кольцо --card отделяет от фона кнопки. */
  .chatdot{position:absolute;top:-7px;right:-7px;min-width:17px;height:17px;padding:0 4px;
    border-radius:9px;background:var(--alert-solid);color:#fff;font-size:11px;font-weight:800;
    line-height:17px;text-align:center;box-shadow:0 0 0 2px var(--card);pointer-events:none}
  #ochat,#schat,#oannc,.chats-tabs button{position:relative}
  /* blox35 #1 — тортик в том же углу, что и счётчик непрочитанных. Ни заливки, ни белого кольца:
     иконка цветная сама, а кружок-подложка (нужный цифре на красном) только сжимал бы рисунок —
     на 17px свечки в нём превращались в штрихи. Значок висит прямо на углу кнопки. */
  .chatdot--cake{background:none;box-shadow:none;min-width:0;width:auto;height:auto;padding:0;
    top:-9px;right:-7px;line-height:0}
  .chatdot--cake .ic{width:20px;height:20px}
  /* системный 🎂 (техника Apple) вместо нашей иконки: он ТЕКСТ, поэтому размер задаётся кеглем,
     а не width/height. Кегли подобраны так, чтобы оба варианта занимали одинаковое место. */
  .chatdot--cake .cakemoji{font-size:17px;line-height:1;display:block}
  .nday .ncake .cakemoji{font-size:14px;line-height:1;display:block}
  .chats-h{margin-bottom:12px}
  /* детский чат-таб — радужный (как playlist), всегда, чтобы не промахнуться мимо взрослых тредов */
  .chats-tabs button.tab-rainbow,.chats-tabs button.tab-rainbow.on{
    background:var(--grad-playlist);color:var(--ink-strong)}

  /* ===== PARENT CHANNEL (blox23 #3) — poll cards + teacher's reschedule form ===== */
  .pcsheet{max-width:560px}
  .pc-tools{margin:-4px 0 10px}
  .pc-tools .btn-ghost{width:100%}
  .pc-pollform{margin-top:10px;padding:12px;background:var(--surface);border-radius:12px}
  .pc-pollform .mgr-field{margin-bottom:8px}
  .pc-pollform select,.pc-pollform textarea{width:100%;font-size:16px;border:1px solid var(--line);
    border-radius:10px;padding:9px 11px;box-sizing:border-box}
  .pc-pollform textarea{min-height:60px;resize:vertical}
  .pc-lbl{display:block;font-size:var(--fs-sm);font-weight:700;color:var(--muted);margin:6px 0 4px}
  .pc-optrow{display:flex;gap:6px;align-items:center;margin-bottom:6px}
  .pc-optrow input{font-size:16px;border:1px solid var(--line);border-radius:10px;padding:8px 10px;min-width:0}
  .pc-optrow .pc-od{flex:1}
  .pc-oremove{border:none;background:none;color:var(--muted);font-size:16px;cursor:pointer;flex:0 0 auto}
  #pc-addopt{margin:2px 0 10px}
  #pc-poll-send{width:100%}
  /* a poll rendered inside a message bubble */
  .pc-poll{margin-top:6px;display:flex;flex-direction:column;gap:6px}
  .pc-poll-h{font-size:13px;font-weight:800;color:var(--accent-dark)}
  .pc-opt{display:flex;align-items:center;gap:8px;text-align:left;width:100%;
    border:1.5px solid var(--line);background:#fff;border-radius:10px;padding:8px 10px;cursor:pointer}
  .pc-opt:disabled{cursor:default;opacity:.9}
  .pc-opt.mine{border-color:var(--accent);background:var(--accent-tint)}
  .pc-opt.approved{border-color:var(--ok-line);background:var(--ok-tint)}
  .pc-when{font-weight:700;font-size:14px;flex:0 0 auto}
  .pc-count{font-weight:800;color:var(--accent-dark);min-width:20px;text-align:center;flex:0 0 auto}
  .pc-voters{font-size:11px;color:var(--muted);overflow-wrap:anywhere;flex:1}
  .pc-hint{font-size:11px;color:var(--muted)}
  .pc-approved{font-size:12px;font-weight:700;color:var(--ok-ink)}
  /* PIN gate + methodologist «Доступы» */
  .pingate{max-width:360px}
  .pingate-in{width:100%;font-size:22px;letter-spacing:.3em;text-align:center;box-sizing:border-box;
    border:1.5px solid var(--line);border-radius:12px;padding:12px;margin:4px 0 8px}
  .pg-err{color:var(--alert-ink);font-size:var(--fs-sm);font-weight:600;margin-bottom:8px}
  .pingate .btn-pub{width:100%}
  .access-list{display:grid;grid-template-columns:1fr;gap:8px;margin-top:12px}
  @media(min-width:761px){ .access-list{grid-template-columns:1fr 1fr} }  /* группы в две колонки на ПК (blox34 #5) */
  .access-row{display:flex;align-items:center;justify-content:space-between;gap:12px;
    border:1px solid var(--line);border-radius:12px;padding:12px 14px;background:#fff}
  .access-row .ar-name{font-weight:700}
  .access-row .ar-par{font-size:var(--fs-sm);color:var(--muted)}
  .access-row .ar-pin{font-size:22px;font-weight:800;letter-spacing:.12em;color:var(--accent-dark);
    font-variant-numeric:tabular-nums}

  /* ===== TEACHER PC LAYOUT ("big terminal") ===== */
  body.teacher main{max-width:1400px}
  /* group terminal: wide grid on the left, sticky review panel on the right */
  .term{display:flex;gap:18px;align-items:flex-start}
  .term-main{flex:1;min-width:0}
  .term-head{display:flex;align-items:flex-start;justify-content:space-between;
    gap:16px;flex-wrap:wrap;margin-bottom:12px}
  .term-head h2{font-size:24px}
  .term-head .toolbar{margin:0}
  .summary{background:var(--card);border-radius:14px;box-shadow:var(--sh-1);
    padding:14px 16px;margin-bottom:16px}
  .summary .sline{display:flex;align-items:center;gap:14px;flex-wrap:wrap;font-size:15px}
  /* второй счётчик лички — просмотр тасков без сдачи. Свой слой информации, поэтому отдельная
     строка и приглушённый вес: работа учителя — верхняя строка (сдачи), эта только сообщает. */
  .summary .sline2{margin-top:6px;color:var(--muted);font-size:14px}
  .summary .bar{flex:1;min-width:140px;height:10px;border-radius:6px;background:var(--accent-tint);overflow:hidden}
  .summary .bar span{display:block;height:100%;background:var(--green);transition:width .3s}
  .summary .muted{color:var(--muted);font-weight:700}
  .summary .pend{color:var(--alert-ink);font-weight:700}
  /* bigger, more readable grid on a PC */
  body.teacher .cell{width:74px;height:54px;font-size:15px}
  .cell.sel{outline:3px solid var(--accent-dark)}
  /* review side panel */
  .term-side{flex:0 0 350px;width:350px;background:var(--card);border-radius:16px;
    box-shadow:var(--sh-2);padding:18px;position:sticky;top:18px}
  .term-side h3{margin:0 0 4px;font-size:17px}
  .term-side .rsub{color:var(--muted);font-size:13px;margin-bottom:12px}
  .rev-empty{color:var(--muted);font-size:14px;text-align:center;padding:36px 6px;line-height:1.5}
  .rev-attach{display:flex;flex-direction:column;gap:10px;margin-bottom:12px}
  .rev-attach img{max-width:100%;border-radius:10px}
  .rev-attach audio{width:100%}
  .rev-next{font-size:13px;color:var(--accent-dark);font-weight:700;margin-top:10px;text-align:center}
  @media(max-width:900px){
    .term{flex-direction:column}
    .term-side{width:100%;flex:1 1 auto;position:static}
  }

  /* лист точного переноса урока (openMoveSheet): строка «в HH:MM · N мин» */
  .sched-line{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px}
  .sched-line label{display:flex;align-items:center;gap:6px;color:var(--muted-strong)}

  /* Лу — кабинет методиста: hub + группы/ученики (blox7) */
  /* «Назад» = вариант B (контур), edition для СВЕТЛОГО фона (чат, кабинет). На тёмной
     шапке .hdr-back .mback ниже перекрывает цвета для полупрозрачно-белого контура. */
  .mback{display:inline-flex;align-items:center;gap:5px;max-width:100%;box-sizing:border-box;
    background:none;border:1px solid var(--line);border-radius:var(--r-md);
    color:var(--muted-strong);font:inherit;font-weight:700;cursor:pointer;padding:6px 12px;margin-bottom:6px;
    transition:border-color var(--t) var(--ease),background var(--t) var(--ease),color var(--t) var(--ease)}
  .mback:hover{border-color:var(--accent);background:var(--accent-tint);color:var(--accent-ink)}
  .mback .mlabel{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
  .chev{font-size:1.5em;line-height:1}   /* стрелка «‹» в 1,5 раза крупнее подписи */
  .hub-cards{display:flex;flex-direction:column;gap:12px;margin-top:16px}
  .hub-card{display:grid;grid-template-columns:auto 1fr;column-gap:16px;align-items:start;
    width:100%;text-align:left;background:#fff;border:1px solid var(--line-soft);
    border-radius:var(--r-xl);padding:18px 22px;cursor:pointer;box-shadow:var(--sh-1);
    transition:border-color var(--t) var(--ease),box-shadow var(--t) var(--ease),transform var(--t-fast) var(--ease)}
  .hub-card:active{transform:scale(.99)}
  .hub-card:hover{border-color:var(--accent);box-shadow:var(--sh-2)}
  .hub-ic{grid-row:1/span 2;font-size:20px;color:var(--accent-deep);line-height:0;display:flex;
    align-items:center;justify-content:center;width:44px;height:44px;border-radius:var(--r-lg);
    background:var(--surface)}
  .hub-t{font-weight:800;font-size:var(--fs-lg);letter-spacing:var(--tracking-tight)}
  .hub-d{color:var(--muted);font-size:var(--fs-sm);margin-top:4px;line-height:1.5}
  .mgr-toolbar{display:flex;flex-wrap:wrap;gap:10px;margin:12px 0 8px}
  /* .mgr-btn → --ghost, .mgr-btn.primary → --fill, .mgr-btn.warn → --danger (BUTTON ENGINE).
     Was radius 9 / pad 8·14 / font:inherit — now unified via the engine; .mgr-top3 below
     still lays the three top buttons out full-width in a 3-column grid. */
  /* верхние кнопки экрана «Группы и ученики» — одной ширины (blox16 #1). Кнопок стало четыре
     («Перевести группы на год вперёд»), поэтому колонки не фиксированные, а auto-fit: ПК — один
     ряд, планшет — 2×2, телефон — столбиком. Ширина в ряду остаётся равной, как и требовал #1. */
  .mgr-top3{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px}
  .mgr-top3 .mgr-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%}
  @media(max-width:760px){ .mgr-top3{grid-template-columns:1fr} }
  .dept-block{margin:16px 0}
  .dept-head{font-size:16px;font-weight:800;color:var(--accent-ink);border-bottom:2px solid var(--accent-line);
    padding-bottom:4px}
  .pot-row{margin:10px 0}
  .pot-label{font-size:13px;color:var(--muted);font-weight:600;margin-bottom:5px}
  .grp-chips{display:flex;flex-wrap:wrap;gap:8px}
  .grp-chip{text-align:left;background:#fff;border:1px solid var(--line);border-radius:10px;
    padding:9px 13px;cursor:pointer;min-width:120px}
  .grp-chip:hover{border-color:var(--accent)}
  .grp-chip .gc-n{font-weight:700}
  .grp-chip .gc-c{font-size:12px;color:var(--muted);margin-top:2px}
  .dept-empty{color:var(--faint);font-size:13px;margin-top:6px}
  .mgr-panel{background:var(--surface);border:1px solid var(--line-soft);border-radius:12px;padding:16px;margin:12px 0}
  .mgr-panel h3{margin:0 0 10px;font-size:15px}
  .mgr-field{display:flex;flex-direction:column;gap:4px;margin-bottom:10px;max-width:340px}
  .mgr-field label{font-size:13px;color:var(--muted-strong)}
  .mgr-field input,.mgr-field select{padding:7px 9px;border:1px solid var(--line-mid);border-radius:7px;font:inherit}
  /* заполненные данные ребёнка — компактная сводка вместо формы (blox22 #4) */
  .pf-read{display:flex;flex-direction:column;gap:6px;font-size:15px;color:var(--ink);margin-bottom:4px}
  .pf-read .pf-lbl{display:inline-block;min-width:120px;color:var(--muted);font-size:13px}
  /* ФИО + дата рождения: в один ряд на ПК, столбиком на телефоне (blox15 #1) */
  .fio-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:0 12px;max-width:760px}
  .fio-row .mgr-field{max-width:none}
  .mg-plonly{display:grid;grid-template-columns:auto 1fr;gap:2px 10px;align-items:center;
    max-width:620px;margin:0 0 12px;padding:11px 13px;background:#fff;
    border:1px solid var(--line-soft);border-radius:9px;cursor:pointer}
  /* 24px — линия WCAG 2.5.8 для пальца, её же держит правило G5 гео-скана. Промах по этой
     галочке не безобиден: она переключает группе режим выдачи ДЗ (blox37 #8). */
  .mg-plonly input{grid-row:1 / span 2;width:24px;height:24px;accent-color:var(--primary);cursor:pointer}
  .mg-plonly-t{font-weight:700;color:var(--ink)}
  .mg-plonly-d{grid-column:2;font-size:12.5px;font-weight:600;color:var(--muted);line-height:1.35}
  .roster-hd{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin:6px 0}
  .roster-hd h2{margin:0}
  .roster-sub{color:var(--muted);font-size:14px}
  .stu-list{display:flex;flex-direction:column;gap:6px;margin-top:10px;max-width:620px}
  .stu-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px 10px;background:#fff;border:1px solid var(--line-soft);
    border-radius:9px;padding:9px 12px}
  .stu-fio{font-weight:700;flex:1 1 120px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .stu-nick{color:var(--accent-ink);font-size:13px;flex:0 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .stu-bd{color:var(--draft-ink);font-size:12px;flex:0 0 auto;white-space:nowrap}
  .stu-em{font-size:12px;flex:0 0 auto;white-space:nowrap;color:var(--faint)}
  .stu-row .stu-sp{margin-left:auto;display:flex;align-items:center;gap:8px}
  .stu-row select{padding:5px 7px;border:1px solid var(--line-mid);border-radius:6px;font:inherit;font-size:13px}
  .stu-x{border:none;background:none;color:var(--danger);font-size:16px;cursor:pointer;padding:2px 6px}
  /* ПЕРЕВОД НА НОВЫЙ УЧЕБНЫЙ ГОД — экран предпросмотра плана.
     Строка плана — flex-wrap (не grid): на 375px «имя → куда» и счётчик детей переносятся сами,
     ничего не уезжает за край (G1). Слово-подтверждение — input 16px, иначе Safari зумит страницу
     и не возвращает обратно (правило 3). */
  .pm-sum{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:14px 16px;
    margin:10px 0 18px;font-size:15px;line-height:1.6}
  .pm-sec{display:flex;align-items:center;gap:10px;margin:18px 0 8px;font-size:12px;font-weight:800;
    letter-spacing:.06em;text-transform:uppercase;color:var(--muted-strong)}
  .pm-sec::after{content:'';flex:1 1 auto;height:1px;background:var(--line-soft)}
  .pm-sec .pm-cnt{order:3;font-weight:800;color:var(--muted)}
  .pm-sec-skip{color:var(--faint)}
  .pm-row{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;background:var(--card);
    border:1px solid var(--line-soft);border-radius:9px;padding:9px 12px;margin-bottom:6px}
  .pm-name{font-weight:700;flex:1 1 110px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .pm-move{order:2;display:flex;align-items:center;gap:8px;min-width:0}
  .pm-from{color:var(--muted);font-size:13px;white-space:nowrap}
  .pm-arr{color:var(--faint)}
  .pm-to{font-weight:700;color:var(--accent-ink);white-space:nowrap}
  .pm-cross{color:var(--accent-deep)}
  .pm-kids{order:3;margin-left:auto;color:var(--muted);font-size:12px;white-space:nowrap}
  /* телефон: имя и счётчик в первой строке, переход целиком во второй — иначе строки рвутся
     каждая по-своему и список перестаёт читаться колонкой */
  @media(max-width:560px){
    .pm-row{display:grid;grid-template-columns:1fr auto;gap:2px 10px}
    .pm-name{grid-area:1/1} .pm-kids{grid-area:1/2;margin-left:0;text-align:right}
    .pm-move{grid-area:2/1/2/3}
  }
  .pm-row-grad{background:var(--alert-tint);border-color:var(--alert-line)}
  .pm-row-grad .pm-to{color:var(--alert-ink)}
  .pm-row-skip{background:transparent;border-style:dashed}
  .pm-row-skip .pm-to{color:var(--faint);font-weight:400;font-size:13px}
  .pm-names{color:var(--muted-strong);font-size:13px;line-height:1.5;margin:-2px 0 10px;padding:0 12px}
  .pm-legend{color:var(--faint);font-size:12px;margin:6px 0 0}
  .pm-confirm{background:var(--alert-tint);border:1px solid var(--alert-line);border-radius:12px;
    padding:14px 16px;margin:22px 0 8px}
  .pm-warn{color:var(--alert-ink);font-size:14px;line-height:1.5;margin-bottom:10px}
  .pm-go{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
  .pm-go input{font:inherit;font-size:16px;padding:10px 12px;min-height:44px;
    border:1px solid var(--line-mid);border-radius:10px;flex:0 1 180px;min-width:0}
  .pm-go .btn-pub:disabled{opacity:.45;cursor:not-allowed}
  /* STUDENT MOVER — drag board */
  .sm-hint{color:var(--muted-strong);font-size:13px;line-height:1.5;margin:6px 0 12px;max-width:760px}
  .sm-warn{color:var(--danger);font-size:13px;margin:0 0 10px}
  .sm-grad{display:flex;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap;
    background:var(--alert-tint);border:2px dashed var(--alert-line);color:var(--alert-ink);border-radius:14px;
    padding:14px 18px;margin:0 0 14px;font-weight:700;font-size:15px;text-align:center}
  .sm-gradsub{font-weight:400;color:var(--danger);font-size:12px}
  .sm-grad.sm-over{background:var(--alert-fill);border-color:var(--danger);color:var(--alert-ink);box-shadow:0 0 0 2px var(--danger) inset}
  .sm-board{display:flex;gap:12px;align-items:flex-start}
  .sm-col{flex:1 1 0;min-width:0;background:var(--surface);border:1px solid var(--line-soft);
    border-radius:12px;padding:12px}
  .sm-col.sm-over{background:var(--ok-tint);border-color:var(--ok);box-shadow:0 0 0 2px var(--ok) inset}
  .sm-colhd{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-bottom:10px}
  .sm-colhd select{flex:1 1 auto;min-width:0;padding:7px 9px;border:1px solid var(--line-mid);
    border-radius:8px;font:inherit}
  .sm-cnt{color:var(--muted);font-size:12px;white-space:nowrap}
  .sm-swap{align-self:center;cursor:pointer;font-size:22px;color:var(--accent-ink);user-select:none;
    padding:0 2px;flex:0 0 auto}
  .sm-drop{display:flex;flex-direction:column;gap:7px;min-height:60px}
  .sm-empty{color:var(--faint);font-size:13px;text-align:center;padding:14px 0}
  /* touch-action:pan-y — пальцем со списка можно скроллить; драг перехватывает жест сам
     (long-press → touchAction:none на плашке + preventDefault на touchmove) */
  .sm-chip{background:#fff;border:1px solid var(--line);border-radius:9px;padding:8px 11px;
    cursor:grab;touch-action:pan-y;user-select:none}
  .sm-chip:active{cursor:grabbing}
  .sm-chip.sm-lifted{opacity:.35}
  .sm-nick{font-weight:600;font-size:14px}
  .sm-fio{color:var(--muted);font-size:12px;margin-top:1px}
  .sm-clone{position:fixed;z-index:60;pointer-events:none;background:#fff;border:1px solid var(--accent-line);
    border-radius:9px;padding:8px 11px;box-shadow:var(--sh-4);transform:scale(1.03)}

  /* УЧИТЕЛЯ — статистика эффективности */
  .stat-note{color:var(--muted);font-size:13px;margin:6px 0 14px;max-width:720px}
  .tstat-card{background:#fff;border:1px solid var(--line-soft);border-radius:14px;padding:16px 18px;
    margin-bottom:16px;box-shadow:var(--sh-1)}
  .tstat-head{display:flex;align-items:center;gap:16px;cursor:pointer}
  .tsh-l{flex:1}
  /* карточка учителя складывается: на телефоне экран открывается списком, метрики — по тапу */
  .tsh-chev{display:none;color:var(--muted);font-size:22px;font-weight:700;
    transition:transform var(--t) var(--ease)}
  .tstat-card.closed .stat-grid{display:none}
  .tstat-card.closed .tsh-chev{transform:rotate(0deg)}
  .tstat-card .tsh-chev{transform:rotate(90deg)}
  @media(max-width:760px){ .tsh-chev{display:block} }
  .tsh-name{font-weight:800;font-size:17px}
  .tsh-sub{color:var(--muted);font-size:13px;margin-top:2px}
  /* ✎ стоит вплотную к имени/нику учителя — правишь именно того, на кого смотришь */
  .tsh-name{display:flex;align-items:center;gap:8px}
  .tsh-edit{flex:0 0 auto;width:30px;height:30px;border:1px solid var(--line);background:#fff;
    border-radius:9px;color:var(--muted);cursor:pointer;display:flex;align-items:center;
    justify-content:center;transition:background var(--t) var(--ease),color var(--t) var(--ease)}
  .tsh-edit:hover{background:var(--accent-tint);border-color:var(--accent);color:var(--accent)}
  .tsh-new{color:var(--muted)}
  /* журнал неудачных попыток входа с персональной двери */
  .att-box{border:1px solid var(--line);border-radius:12px;padding:10px 14px;margin:0 0 14px;
    background:var(--surface);max-width:720px}
  .att-box summary{cursor:pointer;font-weight:700;font-size:14px;color:var(--muted)}
  .att-row{display:flex;flex-wrap:wrap;gap:8px;align-items:baseline;padding:7px 0;
    border-top:1px solid var(--line-soft);font-size:13px}
  .att-mail{font-weight:600}
  .att-why{color:var(--muted);flex:1}
  .att-when{color:var(--muted)}
  .tsh-index{border:2px solid;border-radius:12px;padding:8px 14px;text-align:center;min-width:78px}
  .tshi-n{font-weight:800;font-size:26px;line-height:1}
  .tshi-c{font-size:11px;color:var(--muted);margin-top:2px;letter-spacing:.5px}
  .stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-top:14px}
  .stat-tile{background:var(--surface);border:1px solid var(--line-soft);border-radius:10px;padding:11px 13px}
  .st-top{display:flex;align-items:center;justify-content:space-between}
  .st-ic{font-size:17px}
  .st-score{font-weight:800;font-size:22px;line-height:1}
  .st-label{font-weight:600;font-size:13px;margin-top:6px}
  .st-detail{color:var(--faint);font-size:12px;margin-top:3px;line-height:1.35}

  /* ДЛЯ РОДИТЕЛЯ — отчёт по ребёнку */
  .p-sub{color:var(--muted);font-size:14px;margin:-4px 0 10px}
  .p-datebar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:14px}
  .p-datebar label{font-size:14px;color:var(--muted-strong)}
  .p-datebar input[type=date]{padding:6px 8px;border:1px solid var(--line-mid);border-radius:7px;font:inherit}
  .pstat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px}
  .pstat{background:var(--surface);border:1px solid var(--line-soft);border-radius:12px;padding:14px 14px 12px;text-align:center}
  .pstat.ok{background:var(--ok-tint);border-color:var(--ok-line)}
  .pstat.warn{background:var(--draft-tint);border-color:var(--draft-line)}
  .pstat.bad{background:var(--alert-tint);border-color:var(--alert-line)}
  .pst-val{font-weight:800;font-size:30px;line-height:1;color:var(--ink)}
  .pst-of{font-size:18px;color:var(--faint);font-weight:700}
  .pst-lbl{font-size:12.5px;color:var(--muted);margin-top:7px;line-height:1.35}

  /* ---- icon tinting -----------------------------------------------------------
     An icon inherits the colour of its text, so most need nothing. These few sit on
     their own and would otherwise come out ink-black. */
  .rect .ricon{color:var(--rail)}                 /* task icon = its type's rail colour */
  .rect.done .ricon{color:var(--ok-ink)}
  .eico,.bico,.st-ic,.ex-aud{color:var(--muted)}
  .ecard .eico{color:var(--accent-deep)}
  .annc .ah .ic{color:var(--note-ink)}
  .btn-ghost .ic,.mgr-btn .ic{color:var(--muted)}
  .btn-ghost:hover .ic{color:var(--accent-ink)}
  .btn-primary .ic,.btn-pub .ic{color:currentColor}
  .srnote .ic{color:var(--muted)}
  .locknote .ic{color:var(--alert-ink)}
  .tb-add .ic,.tb-name .ic{color:var(--accent-ink)}
