/* style-02-hw-card.css — часть бывшего style.css (распил 22.10.2026): карточка задания на Домашке и отклик ребёнка на своё действие.
   Файлы style-NN-*.css — ОДИН каскад: index.html подключает их подряд и по номерам, порядок значим
   (правило ниже по каскаду перебивает равное выше). Стережёт tools/check.js. */
  /* ===== 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. */
  /* ===== ТИСНЁНАЯ ПЛАШКА — ОДИН материал четырёх карточек (blox51 #3, заход 2) =====
     .rect — таск у ученика · .bitem — таск в черновике ДЗ · .oent — таск на мониторе
     · .srcard — таск в личке ученика. Все четыре говорили об одном и том же предмете,
     но были собраны по-разному: своя тень, своя граница, свои цифры. Теперь это один
     ЛИСТ БУМАГИ, в который ВДАВЛЕН рельс, а геометрия (padding, радиус, плотность)
     остаётся у каждой своя — ниже в её собственном блоке.
     Цвет рельса везде приходит одним путём: --rail (JS ставит его из itemColor).
     Материал целиком собран из токенов --emb* — см. theme.css, «ТИСНЕНИЕ».
     Рельс живёт в ::before, а НЕ в border-left: границу нельзя ни скруглить внутри
     плашки, ни утопить — а именно вдавленность и читается как «дорого». */
  /* --rail ставит JS ИНЛАЙНОМ (цвет по типу таска), а инлайн не перебить классом. Поэтому
     светит не он, а --rail-eff: состояния карточки (.done, .chal.done) меняют именно его.
     Раньше ту же роль играл border-left-color, который JS не трогал. */
  .rect,.bitem,.oent,.srcard{--rail:var(--task-plain);--rail-eff:var(--rail);position:relative;
    background:var(--card);box-shadow:var(--sheet-1);
    --emb-rail:linear-gradient(180deg,
      color-mix(in srgb, var(--rail-eff) 86%, var(--emb-deep)) 0%,
      var(--rail-eff) 44%,
      color-mix(in srgb, var(--rail-eff) 90%, var(--emb-lift)) 100%)}
  .rect::before,.bitem::before,.oent::before,.srcard::before{content:"";position:absolute;
    left:var(--rail-x);top:var(--rail-y);bottom:var(--rail-y);width:var(--rail-w);
    border-radius:calc(var(--rail-w) / 2);
    background:var(--emb-rail);box-shadow:var(--emb-groove)}
  /* значения по умолчанию — крупная плашка; плотные (монитор) сужают их у себя */
  .rect,.bitem,.oent,.srcard{--rail-x:5px;--rail-y:9px;--rail-w:7px}

  .hwlist{display:flex;flex-direction:column;gap:10px}
  /* padding-right резервирует правую колонку под «сделал» (.rcheck, абсолютная во всю высоту), чтобы
     серый столбец тянулся до низа карточки при любой её высоте (blox18 #7). Плеера в карточке больше
     нет — он открывается шторкой (.hwpsheet, blox68 #6). */
  .rect{display:flex;flex-wrap:wrap;align-items:stretch;
        padding-right:66px;padding-left:15px;
        border:1px solid var(--line-soft);
        border-radius:var(--r-lg);overflow:hidden;color:var(--ink);
        /* редизайн 27.10: грамматика нажатия общая (журнал redesign, агент 3) — вдавливание сухое
           (см. .rect.ropen:active в task-screen.css), возврат пружиной; цвет и тень — на месте */
        transition:opacity var(--dur-panel) var(--ease-enter),box-shadow var(--dur-state) var(--ease-standard),
                   background-color var(--dur-state) var(--ease-standard),border-color var(--dur-state) var(--ease-standard),
                   transform var(--dur-release) var(--ease-spring)}
  @media (hover:hover){.rect:hover{box-shadow:var(--sheet-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);--rail-eff:var(--ok-line);
        box-shadow:var(--sheet-edge)}
  .rect.done .rtitle{color:var(--muted)}
  /* blox6: иконка типа стояла отдельной КОЛОНКОЙ у левого края таска.
     blox63 #8 — колонки больше нет: иконка уехала внутрь строки с названием типа (.rtico), потому
     что в колонке она вставала на уровень первой строки ЗАДАНИЯ и подписывала собой чужой текст. */
  .rect .rtico{font-size:var(--ic-md);line-height:1;flex:0 0 auto}
  .rect .rbody{flex:1;min-width:0;padding:14px 14px 14px 14px;display:flex;flex-direction:column;gap:9px}
  /* заглавная строка карточки: один размер на всех экранах (--fs-md, 15px), по образцу
     конструктора (blox46 #2). У РЕБЁНКА в ней до первого открытия стоит тип таска — см. blox46 #3 */
  .rect .rtitle{font-weight:700;font-size:var(--fs-md);line-height:1.3;color:var(--ink);
        letter-spacing:var(--tracking-tight);overflow-wrap:anywhere;text-wrap:balance}
  /* ...а после открытия — САМО задание. Не режем его ничем: шторку убрали (blox46 #8), дочитать
     длинный текст больше негде, карточка — единственное место, где он живёт. Переводы строк
     учительского текста сохраняем, как их сохраняла шторка */
  /* И ВЕС ТУТ ДРУГОЙ (blox52 #6). Пока в строке стоит ТИП таска («Read by yourself») — это
     ярлык, 700 на месте. Как только на её месте оказывается само задание, это уже абзац,
     который ребёнок читает, и 700 превращали короткий текст в вывеску: ученик видел афишу
     вместо задания. Переносов здесь нет намеренно — задание учитель чаще всего пишет
     по-английски, а словарь переносов взялся бы русский (язык страницы). text-wrap:pretty
     перебивает balance, унаследованный от .rtitle: абзацу нужно ровное «дно», а не ровные
     строки. */
  /* blox63 #9 — РАЗМЕР тут больше, чем у заголовка-ярлыка, и это намеренное расхождение с
     остальными тремя карточками: у учителя лист — столбик, который он просматривает, а у ребёнка
     это текст, который он ЧИТАЕТ и по которому делает задание. 15px хватало ярлыку, читаемому
     абзацу — нет. */
  .rect .rtitle-body{white-space:pre-wrap;font-weight:var(--fw-read);text-wrap:pretty;
        font-size:var(--fs-lg);line-height:1.4}
  /* ССЫЛКА В ТЕКСТЕ ЗАДАНИЯ (linkify) — чернилами акцента схемы, как в объявлении (.annc .abody a).
     Без правила она шла голым синим браузера (а посещённая — фиолетовым) поверх любой схемы и
     спорила с залитой кнопкой карточки. Подчёркивание остаётся: это признак «нажимается». */
  .rect .rtitle a,.rect .comment a{color:var(--accent-ink);text-decoration:underline;text-underline-offset:2px;
        text-decoration-thickness:1px}
  .rect.done .rtitle a,.rect.done .comment a{color:var(--muted-strong)}
  /* тип таска подписью — появляется, когда заголовок занят самим заданием */
  .rect .rsub{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-top:-5px;
        font-size:var(--fs-sm);font-weight:700;color:var(--muted)}
  /* имя прикреплённого файла — под типом таска, тише заголовка, но читаемо */
  .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}
  /* blox80 #3 — «3 файла · прослушано 1 из 2» обрезать нельзя: обрезанным уходит как раз число;
     строка переносится, а не режется многоточием, как имя одного файла выше */
  .rect .rfile.rfiles{white-space:normal;overflow:visible}
  .rect .rfile.rfiles span{min-width:0;overflow-wrap:anywhere}
  .rect .rstatus{font-size:var(--fs-sm);font-weight:600;color:var(--muted)}
  /* blox70 #2 — «🤔» учителя на несделанном задании: знак тем же видом, что реакция на сдачу
     (.subreact-ro), и подпись в строку — одна тихая строка, не тревога */
  .rect .rwait,.tsc .rwait{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:6px}
  .rect .rwait .subreact,.tsc .rwait .subreact{margin-top:0}
  .rect .rwait-lbl,.tsc .rwait-lbl{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);
        --rail-eff:var(--gold);opacity:1}
  .rect.chal.done .rtitle{color:var(--ink)}
  .rect .rflag{color:var(--gold-ink);font-size:var(--ic-md);display:inline-flex;align-items:center;flex:0 0 auto}
  .rect .rdue,.tsc .rdue{font-size:var(--fs-sm);font-weight:700;color:var(--gold-ink);
        background:var(--card);border:1px solid var(--gold-line);border-radius:var(--r-pill);padding:1px 8px;
        white-space:nowrap}
  /* витрина: кто сделал, кто нет (у слушательного челленджа — с числом наслушек) */
  .rect .rpeers,.tsc .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,.tsc .rp-count{font-weight:800;color:var(--gold-ink);display:flex;align-items:center;gap:6px}
  .rect .rp-did,.tsc .rp-did{color:var(--ok-ink);overflow-wrap:anywhere}
  .rect .rp-did b,.tsc .rp-did b{color:var(--gold-ink)}
  .rect .rp-not,.tsc .rp-not{color:var(--muted);overflow-wrap:anywhere}
  /* padding + отрицательный margin: зона нажатия ≥24px (G5), а видимая надпись стоит где стояла */
  /* «переотправить» живёт ВНУТРИ зелёной сделанной карточки (.rect.done, --ok-tint): тревожные
     чернила в состоянии успеха читались «что-то не так» (blox52 #11). Акцентные чернила схемы —
     это «действие текстом», и на ok-tint они читаются во всех схемах (замер 06.09.2026: 14.1 ·
     5.4 · 5.4 · 11.0). */
  .rect .resend{background:none;border:none;cursor:pointer;font-family:inherit;font-size:var(--fs-sm);
    font-weight:700;color:var(--accent-ink);text-decoration:underline;
    padding:8px 6px;margin:-8px -6px -8px 2px}
  /* ОБЛАЧКО УЧИТЕЛЯ (blox71 #1/#3) — одно на карточке листа и на экране таска (.tsc-ret). Сверху
     вердикт (если он нужен), под ним РЕПЛИКА одной строкой с левого края: «Mrs. Lou: текст 👍».
     Принято — белая бумага с зелёной кромкой поверх зелёной сделанной карточки; вернули — акцентная
     подложка (не тревога: красный — только настоящая беда). */
  .tplate{display:flex;flex-direction:column;gap:6px;padding:9px 12px;border-radius:var(--r-sm);
        background:var(--surface-2);border:1px solid var(--line-soft);color:var(--ink)}
  .tplate.tp-ok{background:var(--card);border-color:var(--ok-line)}
  .tplate.tp-ret{background:var(--accent-tint);border-color:var(--accent-line)}
  .tp-verdict{display:flex;align-items:center;gap:5px;font-size:var(--fs-sm);font-weight:700}
  .tp-ok .tp-verdict{color:var(--ok-ink)}
  .tp-msg{font-size:var(--fs-sm);line-height:1.45;text-align:left;overflow-wrap:anywhere}
  .tp-who{font-weight:700;color:var(--muted)}
  /* КОММЕНТАРИЙ УЧИТЕЛЯ — читаемый текст (blox52 #6): тот же вес, что у самого задания рядом, иначе
     в одном предмете два веса у двух одинаковых по роли текстов. Переносов нет: учитель пишет
     комментарий по-английски не реже, чем по-русски. Переводы строк учителя сохраняем. */
  .tplate .tp-note{font-weight:var(--fw-read);white-space:pre-wrap;text-wrap:pretty}
  /* знак — в строку, сразу после текста: inline-flex перебивает flex общего .subreact */
  .tp-msg .subreact-ro{display:inline-flex;vertical-align:middle;margin:-4px 0 -2px 2px;font-size:var(--fs-xl)}
  /* ПРИНЯТОЕ СВЁРНУТО (blox71 #1–#3): [✓ задание / тип] и под ними облачко учителя. Облачко
     начинается на вертикали ✓ (оба стоят у левого края .rbody), а кончается там же, где облачко
     «вернули»: правая колонка 66px (резерв .rect) остаётся, в ней стрелка «›» вместо галочки —
     того же зелёного цвета, что колонка «сделал» у сделанной карточки. */
  .rect .rfold{gap:8px}
  .rf-top{display:flex;align-items:flex-start;gap:10px}
  .rf-tick{flex:0 0 auto;width:24px;height:24px;border-radius:50%;background:var(--ok-ink);color:var(--on-dark);
        display:flex;align-items:center;justify-content:center;margin-top:-1px}
  .rf-tick .tick{width:15px;height:15px;display:block;fill:none;stroke:currentColor;
        stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
  .rf-main{flex:1 1 0;min-width:0;display:flex;flex-direction:column;gap:5px}
  .rf-title{font-size:var(--fs-md);font-weight:600;line-height:1.3;color:var(--muted);overflow-wrap:anywhere;
        display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;overflow:hidden}
  .rf-type{display:flex;align-items:center;gap:6px;font-size:var(--fs-sm);font-weight:700;color:var(--muted)}
  .rf-go{position:absolute;top:0;right:0;bottom:0;width:66px;display:flex;align-items:center;justify-content:center;
        background:var(--ok-fill);border-left:1px solid var(--ok-line);color:var(--ok-ink);font-size:var(--ic-md)}
  /* blox22 #3: свёрнутый «Мой ответ» — ученик пересматривает свою сдачу */
  /* blox27 #4 — «Мой ответ» показан сразу сущностью файла (не тоггл): подпись + кликабельная карточка */
  .rect .myans,.tsc .myans{margin-top:6px}
  .rect .myans-lbl,.tsc .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:var(--r-sm);
        background:var(--surface);border:1px solid var(--line);cursor:pointer;color:var(--ink);
        text-decoration:none;box-shadow:var(--sh-1)}
  @media (hover:hover){.fileent:hover{border-color:var(--accent-line)}}
  .fileent-ico{flex:0 0 auto;font-size:var(--ic-md);line-height:1;display:flex;color:var(--accent-ink)}
  .fileent-nm{flex:1;min-width:0;font-weight:650;font-size:var(--fs-sm);line-height:1.3;overflow-wrap:anywhere}
  .fileent-dur{flex:none;color:var(--muted);font-size:var(--fs-sm);font-variant-numeric:tabular-nums}   /* длина записи (blox78 #13) */
  .fileent-view{flex:0 0 auto;width:34px;height:34px;border:none;border-radius:var(--r-sm);cursor:pointer;
        background:var(--accent-tint);color:var(--accent-ink);display:flex;align-items:center;justify-content:center}
  /* отклик кнопки плашки — общий язык нажатия (журнал redesign §1): сухо вдавилась, пружиной назад.
     Плашка стоит и у ребёнка («Мой ответ»), и в карточке проверки у учителя — отклик один. */
  .fileent-view{transition:background-color var(--dur-state) var(--ease-standard),transform var(--dur-release) var(--ease-spring)}
  .fileent-view:active{transform:scale(var(--press-scale-sm));
    transition-duration:var(--dur-press-in);transition-timing-function:var(--ease-standard)}
  .fileent-go{flex:0 0 auto;font-weight:800;color:var(--accent-ink)}
  /* blox80 #15 — СДАЧА В КАРТОЧКЕ ПРОВЕРКИ — главная кнопка карточки, ради неё учитель сюда пришёл.
     Выделена двумя ходами, не больше: плашка в тинте схемы с акцентной рамкой и круглая залитая
     кнопка справа (рецепт громкого действия — --grad-primary + --bevel, как «▶ Слушать» у ребёнка).
     Подпись «check» — словом в акценте, без заливки: громкой остаётся одна кнопка, а не вся плашка.
     У ребёнка («Мой ответ») класса нет — там прежняя тихая плашка. */
  .fileent--check{background:var(--accent-tint);border-color:var(--accent-line)}
  .fileent--check .fileent-nm{color:var(--accent-ink);font-weight:750;font-size:var(--fs-md);letter-spacing:.02em}
  /* подпись-смысл у ребёнка («Моя запись») — ТОЧНО ПО ЦЕНТРУ плашки (решение владельца после пробы
     «справа, у кружка»). Центр в потоке гулял бы от соседей, поэтому подпись снята с потока и
     растянута на всю плашку; клики проходят сквозь неё — плашка кликабельна целиком. */
  .fileent--tag{position:relative}
  .fileent--tag .fileent-nm{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
        margin:0;pointer-events:none}
  .fileent--tag .fileent-view{margin-left:auto}   /* кнопка у правого края */
  /* blox81 #1 — плашка CHECK у учителя: «Check» и кружок стоят ПАРОЙ по центру плашки (владелец:
     «встают рядом по центру»). Три колонки 1fr · пара · 1fr — равные края держат пару точно в центре,
     что бы ни стояло слева (значок, длина записи) и справа (поставленный знак, см. .srattach ниже). */
  .fileent--check{display:grid;grid-template-columns:1fr auto 1fr;gap:10px}
  .fileent-lead{display:flex;align-items:center;gap:10px;min-width:0}
  .fileent-mid{display:flex;align-items:center;gap:10px}
  .fileent--check .fileent-nm{flex:none}
  /* поставленный знак учителя — НА ПРАВОМ КРАЮ плашки check (владелец, blox81 #1), в той же клетке
     сетки, что и плашка: по высоте центр сам, без подогнанных пикселей. Это всё тот же ряд реакций —
     удержание отпирает его, и отпертый ряд из пяти знаков уходит под плашку (у него своя строка
     сетки), а выбранный знак снова запирается и возвращается на плашку. Кнопка лежит ПОВЕРХ
     плашки: нажатие достаётся ей, а не плашке, и файл не открывается. */
  .srattach{display:grid}
  .srattach > .fileent--check{grid-area:1/1}
  .srattach > .fileent--check + .subreact.locked{grid-area:1/1;justify-self:end;align-self:center;
        margin:0 8px 0 0;position:relative;z-index:1}
  /* работа принята — вместо слова галочка «принято» (зелёный здесь законен: это и есть «принято») */
  .fileent-ok{display:flex;color:var(--ok-ink);font-size:var(--ic-md)}
  .fileent-ok .ic{--icw:2.4px}
  .fileent--check .fileent-view{width:38px;height:38px;border-radius:50%;background:var(--grad-primary);
        color:var(--on-dark);box-shadow:var(--bevel),var(--sh-1)}
  .fileent[data-kind="audio"] .fileent-view .ic,.fileent[data-kind="video"] .fileent-view .ic{margin-left:2px}   /* треугольник оптически по центру круга */
  .fileent--check .fileent-view .ic{--icw:2.4px}   /* значки icons.svg контурные (заливку их <style> не пускает) — на круге берём толщиной, как ▶ плеера */
  /* Работа ПРИНЯТА — звать больше некуда: плашка возвращается к тихой базе (как «Слушать» в .rect.done),
     вместо «Check» галочка, кружка ▶ нет (blox82 #1) — переслушать принятое можно нажатием на плашку. Иначе синий акцент кричит с зелёной карточки
     и перетягивает взгляд с тех сдач, что ещё ждут. */
  .srcard.acc .fileent--check,.srch.acc .fileent--check{background:var(--surface);border-color:var(--line)}
  /* blox82 #5 — без кружка ▶ (38px) принятая плашка садилась по высоте галочки, и знак учителя (44px)
     упирался в её верх и низ. Держим высоту плашки «на проверке»: кружок 38 + поля 9+9 + рамка 1+1 */
  .fileent--check{min-height:calc(38px + 18px + 2px);box-sizing:border-box}
  /* 24.10.2026 — плашка Check СТАЛА ПЛЕЕРОМ (public/plate-player.js): ▶ · ↺5 · ползунок+время · ↻5 · скорость.
     Сетка «1fr · пара · 1fr» здесь не нужна — ряд в строку, ползунок забирает остаток. Поставленный знак
     учителя на это время уходит ПОД плашку к правому краю (как отпертый ряд): на краю плашки он закрыл
     бы кнопку скорости. Высота плашки та же, что у «Check», — карточка ниже не дёргается. */
  .fileent--check.fileent--playing{display:flex;gap:6px;cursor:default;padding:9px 8px}
  .srattach > .fileent--playing + .subreact.locked{grid-area:2/1;justify-self:end;margin:6px 8px 0 0}
  .pp-play{flex:none;width:38px;height:38px;border-radius:50%;border:none;cursor:pointer;display:flex;
        align-items:center;justify-content:center;background:var(--grad-primary);color:var(--on-dark);
        box-shadow:var(--bevel),var(--sh-1)}
  .pp-play .ic{--icw:2.4px}
  .pp-skip,.pp-rate{flex:none;min-width:36px;height:36px;padding:0 4px;border-radius:50%;border:1px solid var(--accent-line);
        background:var(--surface);color:var(--accent-ink);cursor:pointer;font-weight:750;font-size:var(--fs-sm);
        display:flex;align-items:center;justify-content:center;line-height:1}
  .pp-skip small{font-size:var(--fs-2xs);font-weight:700}
  .pp-rate{border-radius:var(--r-pill);min-width:44px;font-variant-numeric:tabular-nums}
  .pp-rate.on{background:var(--accent-ink);color:var(--on-dark);border-color:var(--accent-ink)}
  .pp-skip,.pp-rate,.pp-play{transition:background-color var(--dur-state) var(--ease-standard),
        color var(--dur-state) var(--ease-standard),border-color var(--dur-state) var(--ease-standard),
        transform var(--dur-release) var(--ease-spring)}
  .pp-skip:active,.pp-rate:active,.pp-play:active{transform:scale(var(--press-scale-sm));
    transition-duration:var(--dur-press-in);transition-timing-function:var(--ease-standard)}
  @media (prefers-reduced-motion:reduce){
    .fileent-view:active,.pp-skip:active,.pp-rate:active,.pp-play:active{transform:none}}
  .pp-track{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
  .pp-range{-webkit-appearance:none;appearance:none;width:100%;height:22px;margin:0;background:transparent;cursor:pointer}
  .pp-range::-webkit-slider-runnable-track{height:6px;border-radius:var(--r-2xs);background:var(--accent-line)}
  .pp-range::-moz-range-track{height:6px;border-radius:var(--r-2xs);background:var(--accent-line)}
  .pp-range::-webkit-slider-thumb{-webkit-appearance:none;width:18px;height:18px;margin-top:-6px;border-radius:50%;
        background:var(--accent-ink);border:none}
  .pp-range::-moz-range-thumb{width:18px;height:18px;border-radius:50%;background:var(--accent-ink);border:none}
  .pp-time{font-size:var(--fs-2xs);color:var(--muted);font-variant-numeric:tabular-nums;text-align:center}

  /* blox64 #6 — РЕАКЦИЯ УЧИТЕЛЯ НА СДАЧУ. Ряд из пяти знаков под сущностью файла и под плеером в
     оверлее. Разметку рисует submission-view.js, набор знаков приходит из public/reactions.js.
     Это ЕДИНСТВЕННОЕ место интерфейса, где эмодзи стоят намеренно (сторож в tools/check.js знает
     про исключение): знак здесь и есть содержание — ребёнок узнаёт «огонь», а не контурную фигуру.
     У ребёнка кнопок нет вовсе, стоит только .subreact-ro с поставленным знаком (флаг от экрана). */
  .subreact{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:8px}
  .subreact-b{flex:0 0 auto;width:44px;height:44px;display:flex;align-items:center;justify-content:center;
        font-size:var(--fs-xl);line-height:1;border:1px solid var(--line);border-radius:var(--r-sm);
        background:var(--surface);cursor:pointer;padding:0}
  /* нажатие кнопки знака — в style-11 (общий язык нажатия, редизайн 27.10): там же и reduce */
  @media (hover:hover){.subreact-b:hover{border-color:var(--accent-line)}}
  /* ПОСТАВЛЕННАЯ — ЗЕЛЁНАЯ (решение владельца 22.09.2026). До этого дня здесь стоял оттенок схемы
     с оговоркой «зелёный значит принято/сделано, а реакция — не приёмка». Владелец решил иначе:
     поставленный знак читается как отметка, и зелёный тут уместен.
     Заливка СПЛОШНАЯ (--ok-solid), и это осознанно: мягкий --ok-fill на карточке принятой работы
     (она сама зеленоватая) читался бледно — сравнили два снимка. Значит смысл зелёного расширен:
     не только «сделано/принято», но и «учитель отметил». Поэтому .subreact-b.on ВПИСАНА в реестр
     GREEN_OK (tools/check.js) — сторож остаётся закрытым списком, а не обходится.
     Эмодзи — собственного цвета, белого текста тут нет, поэтому контраст к AA не применим. */
  .subreact-b.on{background:var(--ok-solid);border-color:var(--ok-solid);box-shadow:var(--sh-1)}
  /* blox80 #12 — знак поставлен: ряд заперт, виден только он и чуть блёкнет; сменить — удержанием.
     callout/выделение гасим: на iPhone удержание иначе открывает лупу и меню вместо нашего отпирания */
  .subreact-b{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}
  .subreact.locked .subreact-b:not(.on){display:none}
  .subreact.locked .subreact-b.on{opacity:.6;box-shadow:none}
  /* показ ребёнку: знак без рамки-кнопки — нажимать нечего. С 22.09.2026 перед знаком стоит имя
     того, кто его поставил («Mrs. Lou: 👍»): у ребёнка это единственное место, где похвала
     ИМЕННАЯ. Имя — обычными подписными чернилами и обычным кеглем, знак крупный: читается «кто»
     и сразу «что», а не строка одинакового размера. У старых знаков имени нет — тогда это просто
     знак, как раньше, и выравнивание не съезжает (gap между отсутствующими узлами не рисуется). */
  .subreact-ro{display:inline-flex;align-items:center;gap:4px;font-size:var(--fs-2xl);line-height:1;
        margin-top:8px}
  .subreact-who{font-size:var(--fs-sm);line-height:1;color:var(--muted);font-weight:600}
  .subreact-sign{line-height:1}

  /* ОТВЕТ НА НАЖАТИЕ (22.09.2026). Раньше ряд отвечал только сменой заливки — на телефоне её
     закрывает палец, и методист, не увидев результата, второй раз СНЯЛА свою же реакцию.
     «Пшик» стоит на обоих узлах: у кнопки (учитель нажал) и у знака ребёнка (знак появился с
     салютом, и он же должен дрогнуть). Анимация трансформом — она не влияет на поток, поэтому
     правило «экран не прыгает» здесь не может быть нарушено в принципе. */
  /* редизайн 27.10: прежний «пшик» subreact-pop (1 → 1.3 → .9 → 1) был прыжком игрушки; кнопки
     учителя агент 5 перевёл на спокойный subreact-ack (1 → 1.08 → 1, --dur-panel, style-11), знак
     ребёнка теперь говорит тем же движением — «засчитано», а не «подпрыгнул». Праздник у ребёнка
     делает салют (он и остаётся), знаку дёргаться вдобавок незачем. Ключевые кадры subreact-ack
     живут в style-11-toasts-review.css (в каскаде keyframes общие, порядок файлов неважен). */
  .subreact-ro.pop{animation:subreact-ack var(--dur-panel) var(--ease-standard)}

  /* САЛЮТ. Слой общий и один на приложение (public/salute.js его создаёт при первой надобности):
     position:fixed поверх всего, pointer-events:none — сквозь него нажимают, он ничего не ловит и
     ничего не сдвигает. z-index 130 — выше шторок (50), тостов (70) и «нет связи» (120): салют
     стреляет и из оверлея со сдачей, значит обязан лежать над ним. */
  .salute-layer{position:fixed;inset:0;z-index:130;pointer-events:none;overflow:hidden}
  /* --dx/--dy/--rot ставит на каждую частицу salute.js (свой угол и своя дальность). Объявлены
     ЗДЕСЬ со значениями по умолчанию: без объявления правило читается как сломанное — и сторож
     «переменная используется, но нигде не задана» справедливо ругался. Ноли значат «никуда не
     полетела»: частица без скрипта просто гаснет на месте, а не прыгает в угол экрана. */
  .salute-p{--dx:0px;--dy:0px;--rot:0deg;
        position:absolute;display:block;width:7px;height:9px;border-radius:var(--r-2xs);
        margin:-4px 0 0 -3px;opacity:0;animation:salute-fly .78s ease-out forwards}
  /* до 70% — разлёт, дальше частица ПАДАЕТ и гаснет: без этого хвоста салют читается как
     расползающееся пятно, а не как хлопушка */
  @keyframes salute-fly{
    0%{opacity:1;transform:translate(0,0) rotate(0) scale(1)}
    70%{opacity:1;transform:translate(var(--dx),var(--dy)) rotate(var(--rot)) scale(1)}
    100%{opacity:0;transform:translate(calc(var(--dx) * 1.1),calc(var(--dy) + 26px)) rotate(var(--rot)) scale(.85)}
  }
  /* человек попросил покой — гасим ОБА знака внимания: «пшик» замирает, частицы не рождаются
     вовсе (это делает сам модуль, здесь — страховка на случай уже созданного слоя) */
  @media (prefers-reduced-motion:reduce){
    .subreact-ro.pop{animation:none}
    .salute-layer{display:none}
  }

  /* медиа-оверлей сдачи: фото вписано (не вылезает за экран), видео/аудио — через наш плеер */
  .mediaov .mediabox{background:var(--card);border-radius:var(--r-lg);box-shadow:var(--sheet-4);
        width:100%;max-width:min(900px,94vw);max-height:92vh;overflow:auto;padding:12px 14px}
  /* колонка, а не строка: под плеером встаёт ряд реакций (blox64 #6), и в строке он оказался бы
     сбоку от плеера, а не под ним */
  .mediaov .mediabody{padding:8px 0 4px;display:flex;flex-direction:column;align-items:center}
  .mediaimg{max-width:100%;max-height:78vh;object-fit:contain;border-radius:var(--r-xs);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}
  /* position:relative — якорь коробки волны .ripple-box (blox52 #13: .ract вошёл в SURFACES);
     без него волна вписалась бы в ближайшего позиционированного предка — во всю карточку .rect */
  .rect .ract{border:1px solid var(--line);border-radius:var(--r-sm);padding:10px 15px;
        font-size:var(--fs-sm);font-weight:700;background:var(--card);color:var(--ink);cursor:pointer;
        text-decoration:none;display:inline-block;text-align:center;position:relative;
        transition:border-color var(--dur-state) var(--ease-standard),background var(--dur-state) var(--ease-standard),
                   box-shadow var(--dur-state) var(--ease-standard),transform var(--dur-release) var(--ease-spring)}
  @media (hover:hover){.rect .ract:hover{border-color:var(--accent-deep);background:var(--accent-tint)}}
  .rect .ract:active{transform:scale(var(--press-scale));transition-duration:var(--dur-press-in);
        transition-timing-function:var(--ease-standard)}
  /* ГЛАВНОЕ ДЕЙСТВИЕ КАРТОЧКИ ЗАЛИТО акцентом схемы (blox52 #13). «Открыть задание» /
     «▶ Слушать» — то, ради чего карточка существует, а была самым тихим её элементом
     (белая контурная). Рецепт — тот же, что у .btn--primary (движок кнопок): --grad-primary
     под белым текстом (AA замерен в blox52 #10, включая верхний стоп), блик+фаска --bevel,
     hover — свечение своим цветом. Волна на тёмной заливке — normal (умножение белым —
     тождество, blox52 #8). Вторая, тихая кнопка «Страница» (.page) остаётся контурной базой
     выше — у карточки одно громкое действие. */
  .rect .ract.open:not(.opened):not(.sec),.rect .ract.play{border-color:transparent;background:var(--grad-primary);
        color:var(--on-dark);box-shadow:var(--bevel),var(--sh-1);--ripple-blend:normal;
        display:inline-flex;align-items:center;justify-content:center;gap:6px}
  @media (hover:hover){.rect .ract.open:not(.opened):not(.sec):hover,.rect .ract.play:hover{border-color:transparent;
        background:var(--grad-primary);box-shadow:var(--bevel),0 6px 18px color-mix(in srgb,var(--anchor) 42%,transparent)}}
  /* СДЕЛАННЫЙ ТАСК ГАСИТ СВОЮ ГРОМКУЮ КНОПКУ. Заливка акцентом — знак «вот ради чего карточка
     существует»; когда задание сдано и плашка позеленела, звать больше некуда, а кнопка всё так же
     кричит с зелёного листа и перетягивает взгляд с тех тасков, что ещё ждут. Поэтому в .rect.done
     (и в золотом .rect.chal.done — он тоже .done) «Слушать» / «Открыть задание» возвращаются к
     ТИХОЙ контурной базе: фон прозрачный, чтобы сквозь кнопку читался тинт самой карточки, и она
     сидела В листе, а не поверх него. Кнопка остаётся ЖИВОЙ — переслушать сданное законно, — и
     этим отличается от «Открыто ✓» (.opened), которая disabled. Волна возвращается к режиму схемы
     (--ripple-blend:inherit): normal нужен только тёмной заливке, а её тут больше нет.
     Специфичность равна громкому правилу — побеждает порядком, поэтому стоит ПОСЛЕ него. */
  .rect.done .ract.open:not(.opened):not(.sec),.rect.done .ract.play{
        background:transparent;border-color:var(--line-mid);color:var(--muted-strong);
        box-shadow:none;--ripple-blend:inherit}
  @media (hover:hover){.rect.done .ract.open:not(.opened):not(.sec):hover,.rect.done .ract.play:hover{
        background:var(--card);border-color:var(--line-strong);box-shadow:none}}
  /* «Открыто ✓» (blox52 #13) — кнопка «Открыть задание» после тапа НЕ исчезает (удаление
     меняло высоту карточки под пальцем), а становится контурной disabled-меткой ТЕХ ЖЕ
     размеров (min-width/height фиксирует JS). Нарочно НЕ зелёная: зелёный занят смыслом
     «сделано/принято» (§11), а «открыто» — тихий факт. Стоит ПОСЛЕ правила заливки:
     специфичность равная, побеждает порядок. */
  .rect .ract.opened{background:var(--card);border-color:var(--line-mid);color:var(--muted);
        cursor:default;box-shadow:none}
  .rect .ract.opened:active{transform:none}
  /* blox74 #3 — у голосового вида громкая кнопка — «▶ Сделать»; его «Открыть ссылку / квиз / PDF»
     остаётся рядом тихой контурной базой (.sec), как «Скан»: громкое действие у карточки одно */
  .rect .ract.open.sec{display:inline-flex;align-items:center;justify-content:center;gap:6px}
  /* blox24 #9: файл-таск без файла — нечего слушать/открывать; тихая пометка вместо кнопки */
  .rect .ract-empty{display:inline-flex;align-items:center;gap:6px;font-size:var(--fs-sm);
        font-weight:600;color:var(--muted)}
  /* у контрольного файла нет колонки «сделал» (.rcheck), поэтому 66px-резерв справа — мёртвый
     пустой столбец. Снимаем: под .rcheck заезжать нечему (её тут нет). */
  .rect.ctrltask{padding-right:0}
  /* the check: a tall tap target on the right, ringed circle -> filled celadon tick */
  /* ВОЛНА ОТ КАСАНИЯ (blox52 #9). Это ГЛАВНАЯ кнопка ребёнка — десятки нажатий в неделю, и
     именно на телефоне обычное «нажатое» рисуется непредсказуемо, ради чего волна и заведена
     (см. public/ripple.js, список SURFACES). Поверхность бумажная, поэтому чернила берём
     зелёные — цвет самой отметки: волна показывает, ЧЕМ станет кружок. Режим (умножение днём,
     обычный ночью) приезжает от схемы сам, как у плитки кабинета. Колонка уже позиционирована
     (position:absolute), так что коробке волны есть во что вписаться. */
  .rect .rcheck{position:absolute;top:0;right:0;bottom:0;width:66px;border:none;border-left:1px solid var(--line-soft);
        background:transparent;cursor:pointer;display:flex;align-items:center;justify-content:center;
        -webkit-tap-highlight-color:transparent;--ripple-ink:var(--ok-ink);
        transition:background var(--dur-state) var(--ease-standard)}
  @media (hover:hover){.rect .rcheck:hover{background:var(--fill-soft)}}
  /* вдавливается КРУЖОК, а не вся колонка во всю высоту карточки (раньше колонка сжималась вместе
     со своей левой кромкой — дрожал край карточки, а не кнопка). Сделанный кружок стоит в покое на
     1.06, поэтому его вдавливание — от 1.06, и под «уменьшенным движением» он остаётся на 1.06 */
  .rect .rcheck:active .circ{transform:scale(var(--press-scale-sm));transition-duration:var(--dur-press-in);
        transition-timing-function:var(--ease-standard)}
  .rect .rcheck.on:active .circ{transform:scale(calc(1.06 * var(--press-scale-sm)))}
  /* КОЛЬЦО ДО НАЖАТИЯ — акцент схемы, а не серый волосок (blox52 #13). --line-mid в синих
     схемах давал ~1.7:1 к белой карточке: главный «щёлк» дня выглядел заглушкой. Берём
     --anchor-ink (якорь ЧЕРНИЛАМИ): в светлых схемах это сам якорь, ночью — светлый
     виноград, иначе тёмный якорь на тёмной бумаге сам дал бы 2.9:1 (замер 06.09.2026,
     цель ≥3:1 для нетекстового индикатора: 10.2 · 13.8 · 6.0 · 7.6). Толщина остаётся
     2px — ступень «сигнал» лестницы линий. Наведение (--ok) и анимации пачки 8 не тронуты. */
  .rect .rcheck .circ{width:32px;height:32px;border-radius:50%;border:2px solid var(--anchor-ink);
        background:var(--card);display:flex;align-items:center;justify-content:center;font-size:var(--ic-md);
        font-weight:800;color:transparent;
        transition:background var(--dur-state) var(--ease-standard),border-color var(--dur-state) var(--ease-standard),
                   color var(--dur-state) var(--ease-standard),transform var(--dur-release) var(--ease-spring)}
  @media (hover:hover){.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)}
  /* галочка — ШТРИХ, а не буква: путь, который можно прочертить (см. TICK в student.js) */
  .rect .rcheck .circ .tick{width:19px;height:19px;display:block;fill:none;stroke:currentColor;
        stroke-width:3;stroke-linecap:round;stroke-linejoin:round}

  /* ═══════════════════════════════════════════════════════════════════════
     ОТКЛИК РЕБЁНКА НА СВОЁ ДЕЙСТВИЕ (blox52 #9, восьмая пачка судьи).
     Всё, что ниже, живёт на ОДНОМ факте: тик «сделал» перерисовывает лист целиком
     (toggleDone → renderStudent → view.innerHTML). Значит анимацию нельзя вешать на
     нажатую кнопку — её через миг не станет; карточка РОЖДАЕТСЯ с классом (.just-done /
     .just), и браузер играет анимацию на вставке нового узла.
     Имена анимаций начинаются с hw- сознательно: по этому признаку сторож npm run check
     находит их все и требует, чтобы каждая гасилась под «уменьшенным движением» — иначе
     новая анимация здесь появилась бы мимо чужой воли и никто бы не заметил.
     Ни одна из них НЕ меняет раскладку: только прозрачность, transform, цвет и штрих. */
  /* кружок СХЛОПЫВАЕТСЯ в отметку: начинает шире кольца, проваливается ниже цели и
     возвращается с перелётом (--ease-spring) — движение читается физическим, а не «включили
     картинку». Конец кадра равен статике .rcheck.on (scale 1.06), поэтому fill не нужен:
     анимация кончилась — состояние уже на месте, стыка не видно. */
  /* редизайн 27.10 («плавно-средне»): было 1.3 → .82 → 1.06 за .34s — кружок раздувался шире
     колонки и проваливался, читалось как прыжок игрушки. Теперь одна дуга: из .8 в покой 1.06 по
     пружине --ease-spring (её перелёт сам даёт мягкое «щёлк» ≈1.09), --dur-panel. */
  .rect .rcheck.just .circ{animation:hw-circ-pop var(--dur-panel) var(--ease-spring)}
  @keyframes hw-circ-pop{from{transform:scale(.8)}to{transform:scale(1.06)}}
  /* галочка ПРОРИСОВЫВАЕТСЯ: pathLength="1" делает длину пути единицей, поэтому dash-набор
     пишется числами, не зависящими от размера фигуры. Штрих живёт ТОЛЬКО внутри кадров —
     вне анимации у пути нет ни dasharray, ни offset, и галочка сплошная сама собой (в том
     числе когда анимация выключена по просьбе человека).
     КРИВАЯ — linear, а не общая --ease, по той же причине, по которой линейно затухает волна
     (см. ripple-fade): --ease описывает, как вещь ПРИЕЗЖАЕТ на место, и стартует резко —
     на замере она прочерчивала 80% галочки за первые 30 мс, то есть штриха никто бы не
     увидел. Прочерчивание и заливка — не приезд, у них своя ровная скорость. */
  /* 80мс задержки + --dur-panel = 360мс: штрих кончается раньше потолка --dur-max (400мс) */
  .rect .rcheck.just .circ .tick{animation:hw-tick-draw var(--dur-panel) linear 80ms both}
  @keyframes hw-tick-draw{from{stroke-dasharray:1;stroke-dashoffset:1}
                          to{stroke-dasharray:1;stroke-dashoffset:0}}
  /* ВОЛНА УСПЕХА: на полсекунды карточка становится заметнее — гуще заливка, рамка в полный
     --ok — и оседает в спокойный зелёный статики (.rect.done). Громко МГНОВЕНИЕ, а не всегда:
     галочка ставится десятки раз в неделю, и постоянный праздник обесценился бы за день.
     background-COLOR, а не background: короткая запись снесла бы фон-картинку, если она
     появится у карточки. */
  /* редизайн 27.10: .5s с полкой на ярком зелёном → в потолок --dur-max (400мс) и без полки:
     заливка набегает за первую треть и сразу оседает. Колонка галочки набегает вместе с листом
     (hw-col-wash) — раньше она вставала зелёной разом, пока карточка рядом ещё только зеленела. */
  .rect.just-done{animation:hw-done-wash var(--dur-max) var(--ease-standard)}
  @keyframes hw-done-wash{
    0%{background-color:var(--card);border-color:var(--line-soft)}
    30%{background-color:var(--ok-fill);border-color:var(--ok)}
    100%{background-color:var(--ok-tint);border-color:var(--ok-line)}}
  .rect.just-done .rcheck.just{animation:hw-col-wash var(--dur-max) var(--ease-standard)}
  @keyframes hw-col-wash{from{background-color:transparent;border-left-color:var(--line-soft)}}
  /* «ИДУ» — между нажатием и перерисовкой. На медленной связи там была пустота, и ребёнок
     жал второй раз. Тонкая полоса по ВЕРХНЕЙ кромке карточки: 2px, внутри overflow:hidden
     самой плашки, ничего не сдвигает и никуда не лезет. Снимается перерисовкой (нового узла
     класса нет) или руками, если запрос отказал — см. toggleDone. */
  .rect.busy::after{content:'';position:absolute;left:0;top:0;height:2px;width:38%;
    background:var(--ok-ink);border-radius:0 var(--r-2xs) var(--r-2xs) 0;
    animation:hw-busy-sweep 1.05s linear infinite}
  @keyframes hw-busy-sweep{from{transform:translateX(-100%)}to{transform:translateX(263%)}}
  /* РАСКЛАДКА ПРИ ПЕРВОМ ПОКАЗЕ: карточки въезжают по очереди. Класс .intro ставит JS ровно
     на первой отрисовке листа (см. hwIntroShown в student.js) — на обновлениях его нет, и
     список не шевелится вовсе. Только прозрачность и сдвиг 6px: раскладка не меняется, значит
     «въезд» физически не может ничего подвинуть (правило «экран не прыгает»). */
  .hwlist.intro .rect{--i:0;   /* ступень задержки; настоящий номер ставит JS инлайном */
    animation:hw-card-in var(--dur-panel) var(--ease-enter) both;
    animation-delay:calc(var(--i) * 45ms)}
  @keyframes hw-card-in{from{opacity:0;transform:translateY(var(--enter-shift))}to{opacity:1;transform:none}}
  /* ЧУЖАЯ ВОЛЯ. Попросил человек систему не анимировать — движения не остаётся: отметка
     появляется готовой, лист показывается сразу, волна успеха не идёт. Полоса «иду» —
     единственная, что ОСТАЁТСЯ: это не украшение, а признак «запрос летит»; она просто
     перестаёт ездить и стоит во всю ширину. Тот же размен, что у волны от касания. */
  @media (prefers-reduced-motion:reduce){
    .rect .rcheck.just .circ,
    .rect .rcheck.just .circ .tick,
    .rect.just-done,
    .rect.just-done .rcheck.just,
    .hwlist.intro .rect{animation:none}
    .rect .ract:active,.rect .rcheck:active .circ{transform:none}
    .rect .rcheck.on:active .circ{transform:scale(1.06)}
    .rect.busy::after{animation:none;width:100%}
  }

  /* teacher grid */
  table{border-collapse:separate;border-spacing:8px;width:100%}
  th{font-size:var(--fs-sm);color:var(--muted);font-weight:700;text-align:center;padding:4px}
  th.name,td.name{text-align:left;font-weight:700;white-space:nowrap}
