/* style-06-buttons.css — часть бывшего style.css (распил 22.10.2026): движок кнопок и волна от касания.
   Файлы style-NN-*.css — ОДИН каскад: index.html подключает их подряд и по номерам, порядок значим
   (правило ниже по каскаду перебивает равное выше). Стережёт tools/check.js. */
  /* ═══════════════════════════════════════════════════════════════════════
     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/.btn-pub → --primary (blox52 #10: ОДИН цвет действия — кнопка-решение
       носит акцент СВОЕЙ схемы, зелёная заливка осталась только «принятию»: .btn--accept) ·
       .btn-ghost/.mgr-btn/.btn-fb → --ghost · .mgr-btn.primary → --fill ·
       .mgr-btn.warn → --danger. */
  .btn,
  .btn-primary,.btn-pub,.btn-ghost,.btn-fb,.mgr-btn{
    /* knobs — a variant / size overrides these */
    /* ПЕРЕРИСОВКА B (агент 2): вторичная кнопка — СЕРАЯ ЗАЛИВКА БЕЗ РАМКИ (--btn-2-*, эскизы
       MonB/ReviewB: Attendance · Post · Chat · Notes), радиус кнопки B 12, кегль 14/600, без
       тени — карточки и кнопки B плоские. Контурная бумага осталась в истории. */
    --btn-r:var(--r-btn); --btn-px:18px; --btn-py:11px; --btn-fs:var(--fs-ctl); --btn-fw:600;
    --btn-face:var(--btn-2-bg); --btn-ink:var(--btn-2-ink); --btn-line:transparent;
    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:none; /* B: плоская — подъём в покое снят */
    /* ДВИЖЕНИЕ КНОПКИ (redesign 27.10, токены theme.css): цвет, рамка и тень меняются на месте
       (--dur-state, --ease-standard); transform — ВОЗВРАТ в покой с пружиной (--dur-release,
       --ease-spring). Само вдавливание короче и суше — его длительность переписывает :active ниже,
       поэтому палец видит «вдавилось сразу — отпустило мягко», а не одинаковую вялую дугу туда-обратно. */
    transition:border-color var(--dur-state) var(--ease-standard),background-color var(--dur-state) var(--ease-standard),
               color var(--dur-state) var(--ease-standard),box-shadow var(--dur-state) var(--ease-standard),
               transform var(--dur-release) var(--ease-spring)}
  /* ОТКЛЮЧЁННАЯ КНОПКА — БЛЁКЛАЯ, А НЕ ВЫЦВЕТШАЯ (blox52 #3, п.1). opacity:.45 гасила предмет
     ЦЕЛИКОМ и вместе с ним подпись: у залитой кнопки («Перевести», «Выдать») получалось мутное
     сиреневое пятно, на котором белые буквы давали 1.9:1 — надпись отключённой кнопки просто
     не читалась, а именно она и объясняет, чего кнопка ждёт. Прозрачность вдобавок протаскивает
     сквозь кнопку всё, на чём она лежит, поэтому вид зависел от подложки.
     Теперь «отключено» говорят РОЛИ ЦВЕТА, а не прозрачность: текст уходит в --faint, рамка —
     в волосок. Курсор и вес 700 сохранены намеренно: предмет должен остаться КНОПКОЙ, просто
     сейчас недоступной, а не превратиться в подпись. */
  .btn:disabled,.btn-primary:disabled,.btn-pub:disabled,
  .btn-ghost:disabled,.btn-fb:disabled,.mgr-btn:disabled{
    --btn-ink:var(--faint);--btn-line:var(--line-soft);
    /* B: серая кнопка гаснет в светлую подложку — на дорожке --btn-2-bg синих схем --faint
       давал 2.41:1 (проба «отключённая кнопка читается»), на --fill-soft — 3.0 */
    --btn-face:var(--fill-soft);
    cursor:default;box-shadow:none;transform:none}
  /* ЗАЛИТЫЕ роли гасят и саму заливку — в нейтральную --fill-soft (--faint на ней 3.0-3.9:1 во
     всех четырёх схемах). Отдельным правилом, потому что background у них написан строкой
     (градиент), и ручкой --btn-face его не перебить. Контурные роли сюда НЕ входят: у них
     заливка и так бумага, гасить нечего — гаснут только текст и рамка. */
  .btn--primary:disabled,.btn-primary:disabled,.btn--fill:disabled,.mgr-btn.primary:disabled,
  .btn--accept:disabled,.btn-pub:disabled{
    --btn-face:var(--fill-soft);background:var(--fill-soft)}
  .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--primary:focus-visible,.btn-primary:focus-visible,.btn-pub:focus-visible,.btn--fill:focus-visible,
  .mgr-btn.primary:focus-visible{box-shadow:var(--bevel),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}
  /* ПАЛЕЦ БОЛЬШЕ КНОПКИ (G5, WCAG 2.5.5 — 44px). Компактная кнопка и квадратная иконка-кнопка
     38px ВИДОМ, но нажиматься обязаны по 44: на тач-экране их ловит невидимый ободок ::before
     в 3px по краю. Рисунок, раскладка и соседи не сдвигаются ни на пиксель — растёт только зона
     касания. Мышь (pointer:fine) целится точно, ей ободок не нужен. */
  @media (pointer:coarse){
    .btn--sm::before,.icon-btn::before{content:"";position:absolute;inset:-3px;border-radius:inherit}
  }
  /* КАЖДОЕ правило с :hover в этом файле заперто в @media (hover:hover). Тач-браузер
     эмулирует наведение после тапа и НЕ снимает его: кнопка оставалась подсвеченной и
     приподнятой, пока не ткнёшь мимо — на планшете это читается как «интерфейс подвис».
     Медиазапрос отдаёт hover только настоящему курсору. Обёрнуто ПО МЕСТУ, а не одним
     блоком в конце: :active объявлен ПОСЛЕ :hover намеренно (на мыши истинны оба, и
     нажатие должно побеждать наведение) — перенос правил вниз сломал бы этот порядок.
     roles (legacy names aliased onto each). Filled roles carry --bevel (a lit top rim + a dark
     bottom edge) ON TOP of their drop shadow, and a gradient fill (--grad-*) instead of a flat colour. */
  /* B: наведение серой кнопки — ступень темнее той же серой, рамки и тени нет */
  @media (hover:hover){.btn--ghost:hover,.btn-ghost:hover,.mgr-btn:hover,.btn-fb:not(.sent):hover{border-color:transparent;background:var(--btn-2-bg-hover);box-shadow:none}}
  /* 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. */
  /* blox51 #3 (заход 3.1): роли .btn--fill и .mgr-btn.primary СХЛОПНУТЫ сюда. Пока
     заливка была синей, а первичная — цветом схемы, это были две разные кнопки. Как
     только обе взяли якорь схемы, они стали одним цветом — держать два имени для
     одного вида значит ждать, пока они разъедутся. Имена оставлены как псевдонимы:
     их пять на весь проект, переименование в разметке ничего не даёт. */
  /* --ripple-blend:normal у ЗАЛИТЫХ ролей (blox52 #8): волна на них белая (currentColor
     = --on-dark), а умножение белым — тождество, то есть на тёмной заливке волны бы
     не стало вовсе. Бумажные роли (ghost/danger/fb) режим схемы не трогают. */
  /* blox52 #10 — ОДИН ЦВЕТ ДЕЙСТВИЯ. .btn-pub переехала из зелёной --accept сюда, в --primary:
     кнопка-РЕШЕНИЕ («Выдать ученикам», «Установить ДЗ», «Сохранить», «Добавить запись»…) носит
     акцент СВОЕЙ схемы (--grad-primary/--anchor), а зелёная заливка вернулась к единственному
     смыслу «сделано/принято» — её носит только .btn--accept («Принять» / «Принять всё»). */
  .btn--primary,.btn-primary,.btn-pub,.btn--fill,.mgr-btn.primary{--btn-line:transparent;--btn-ink:var(--on-dark);
    --ripple-blend:normal;
    background:var(--grad-primary);box-shadow:none}   /* B: без фаски и тени — плоская заливка акцента схемы */
  @media (hover:hover){.btn--primary:hover,.btn-primary:hover,.btn-pub:hover,.btn--fill:hover,.mgr-btn.primary:hover{box-shadow:var(--bevel),0 6px 18px color-mix(in srgb,var(--anchor) 42%,transparent)}}
  /* «✓ ГОТОВО» — ЗЕЛЁНАЯ (владелец, 17.09.2026). Единственная кнопка .btn-pub, которой зелёный
     положен по закону цвета: она не решение и не выдача, а «я закончил править» — то самое
     «сделано», за которым зелёная заливка и закреплена. Класс отдельный намеренно: на черновике
     ту же .btn-pub носит «Выдать ученикам», и она обязана остаться акцентом схемы — покрась мы
     весь класс, зелёный снова значил бы два разных дела. Записана в GREEN_OK (tools/check.js). */
  .btn-pub.btn-done{background:var(--grad-accept)}
  @media (hover:hover){.btn-pub.btn-done:hover{box-shadow:var(--bevel),0 6px 18px color-mix(in srgb,var(--ok-solid) 40%,transparent)}}
  /* blox80 #14 (владелец: «слишком массивная») — «Принять» больше НЕ заливка: насыщенно-зелёная рамка,
     внутри бледно-зелёный (тот же --ok-tint, что у плашки «принято»), текст и галочка — зелёные.
     Зелёный по-прежнему значит только «принято»; сплошную заливку из реестра GREEN_OK она сняла. */
  /* ПЕРЕРИСОВКА B (агент 2, решение оркестратора по эскизу ReviewB): «Approve» снова СПЛОШНАЯ
     зелёная заливка под белым текстом — --ok-solid (5.6:1), а не эскизный #2f8a55 (4.30, под AA).
     Это сознательный возврат против blox80 #14 («слишком массивная»): в B кнопки плоские и
     без рамок, и бледно-зелёная с рамкой выпадала из языка. Зелёный по-прежнему значит только
     «принято» — запись в GREEN_OK (tools/check.js). */
  .btn--accept{--btn-line:transparent;--btn-ink:var(--on-dark);--btn-face:var(--ok-solid);--ripple-blend:normal}
  .btn--accept .ic{color:currentColor}
  @media (hover:hover){.btn--accept:hover{filter:brightness(.92);box-shadow:none}}
  /* blox81 #2 — «Вернуть» — пара к «Approve» тем же рецептом, только красным (владелец): насыщенная
     рамка, бледно-красное нутро, текст тёмно-красный (--danger-ink, 7:1 — --alert текстом под AA).
     Красный здесь законен: возврат и есть «внимание, переделать». Стоит на .btn-ghost, поэтому
     наведение перебиваем той же специфичностью, что у ghost. */
  /* B: «Decline» — красный ТИНТ без рамки (эскиз ReviewB #fbe4e7 / #9b1c31) */
  .btn--return{--btn-line:transparent;--btn-ink:var(--danger-ink);--btn-face:var(--alert-fill)}
  /* blox82 #3 — стрелка возврата красная, как галочка у «Approve» зелёная. Селектор с .btn-ghost: общее
     «.btn-ghost .ic{color:muted}» (style-10) иначе красило её серым, а наведение — в акцент */
  .btn-ghost.btn--return .ic,.btn-ghost.btn--return:hover .ic{color:var(--alert)}
  @media (hover:hover){.btn-ghost.btn--return:hover{border-color:transparent;background:var(--alert-fill);filter:brightness(.95);box-shadow:none}}
  /* Текстом на белом --alert (gf-500) даёт 3.56:1 — под AA. Для разрушающего действия
     семантика уже есть: --danger-ink (gf-700), 7.1:1. Заливка/рамка hover'а остаются --alert. */
  .btn--danger,.mgr-btn.warn{--btn-ink:var(--danger-ink)}
  @media (hover:hover){.btn--danger:hover,.mgr-btn.warn:hover{border-color:var(--alert);background:var(--alert-tint);box-shadow:var(--sh-2)}}
  /* blox52 #10: .btn-fb СНЯЛА янтарь — это был пятый цвет «главной» кнопки и единственный
     янтарный элемент интерфейса, а золото в проекте занято ровно одним смыслом — «приз»
     (см. --gold в theme.css: «Nothing else may use it»). Теперь это бумажная ghost: базовый
     вид движка, hover — в общем ghost-списке выше. От blox27 #3 осталась половина по праву:
     зелёная ✓ после отправки (.btn-fb.sent) — «отправлено» и есть «сделано». */
  /* blox45 #16 — «Edit» в шапке ВЫДАННОГО листа тоже золотая: она единственная на панели зовёт
     тронуть уже живое ДЗ, и золото отличает её от нейтральных ghost-кнопок. Тот же приём, что у
     .btn-fb: knobs объявлены ПОСЛЕ базы, поэтому побеждают .btn-ghost. */
  /* blox51 #4: .oedit СНЯЛА золотые ручки. Золото в проекте занято ровно одним смыслом —
     «приз» (челлендж, см. --gold в theme.css: «Nothing else may use it»); кнопка правки
     одолжила его без права и рядом с соседкой Notes выглядела тревожной. Теперь это
     обычная призрачная кнопка базовой роли — ровно как Notes. Свой размер она держит
     в .opane-hr .btn-ghost (style-04, общий у всех кнопок шапки листа — blox84 #7). */
  /* 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. */
  @media (hover: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(var(--lift))}}
  /* НАЖАТИЕ — вдавилось сразу: своя короткая длительность (--dur-press-in) и ровная кривая без
     перелёта. На отпускании правило снимается, и transform едет назад по базовому переходу —
     с пружиной. Масштаб — токен --press-scale: под «уменьшить движение» он сам становится 1. */
  .btn:active:not(:disabled),.btn-primary:active:not(:disabled),.btn-pub:active:not(:disabled),
  .btn-ghost:active:not(:disabled),.btn-fb:active:not(:disabled),.mgr-btn:active:not(:disabled){
    transform:scale(var(--press-scale));box-shadow:var(--sh-press);
    transition-duration:var(--dur-press-in);transition-timing-function:var(--ease-standard)} /* press = pushed in */
  @media (prefers-reduced-motion:reduce){
    .btn,.btn-primary,.btn-pub,.btn-ghost,.btn-fb,.mgr-btn{transition:border-color var(--dur-state) var(--ease-standard),background-color var(--dur-state) var(--ease-standard),
      color var(--dur-state) var(--ease-standard),box-shadow var(--dur-state) var(--ease-standard)}
    .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,
    .btn:active:not(:disabled),.btn-primary:active:not(:disabled),.btn-pub:active:not(:disabled),
    .btn-ghost:active:not(:disabled),.btn-fb:active:not(:disabled),.mgr-btn:active:not(:disabled),
    .icon-btn:active:not(:disabled){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;transform:none}
  /* подпись не гаснет скачком: color уже в переходе базы (--dur-state), а крутилка проявляется
     следом за ней — кнопка «переходит в ожидание», а не мигает пустой коробкой */
  @starting-style{
    .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{opacity:0}}
  .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;transition:opacity var(--dur-state) var(--ease-standard)}
  @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,.pool-dlall.is-loading::after{animation-duration:2s;
      /* тихая версия для общего правила «уменьшить движение» (theme.css): без неё крутилка встала бы */
      --motion-reduced:btn-spin 2s linear infinite}}
  /* 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:var(--ic-md);line-height:1;
    transition:border-color var(--dur-state) var(--ease-standard),background-color var(--dur-state) var(--ease-standard),
               color var(--dur-state) var(--ease-standard),transform var(--dur-release) var(--ease-spring)}
  @media (hover:hover){.icon-btn:hover{border-color:var(--accent-deep);background:var(--accent-tint);color:var(--accent-ink)}}
  .icon-btn:active:not(:disabled){transform:scale(var(--press-scale-sm));
    transition-duration:var(--dur-press-in);transition-timing-function:var(--ease-standard)}
  .icon-btn:disabled{color:var(--faint);border-color:var(--line-soft);cursor:default}
  .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:var(--ic-sm)}
  @media (hover:hover){.icon-btn--danger:hover{border-color:var(--alert);background:var(--alert-tint);color:var(--alert-ink)}}

  /* ═══════════════════════════════════════════════════════════════════════
     ВОЛНА ОТ КАСАНИЯ — общий отклик всех нажимаемых поверхностей (public/ripple.js).
     Зачем поверх :active: в Safari на iOS :active рисуется непредсказуемо — браузер
     придерживает его, пока не решит, что палец не поедет в скролл. Тап мог не дать
     НИКАКОГО отклика, и ребёнок жал кнопку второй раз. Волна висит на pointerdown
     и отвечает всегда.

     ЦВЕТ — currentColor, то есть цвет текста самой кнопки. Это то, что делает волну
     универсальной: на тёмной заливке (--primary/--accept) текст белый → волна белая;
     на светлой контурной текст тёмный → волна тёмная. Контраст держится на любой
     заливке и во ВСЕХ трёх схемах, без единой строки на схему. Поверхность может
     попросить свой цвет через --ripple-ink. Плоская заливка не годилась: голубая
     волна на тёмно-виноградной кнопке читалась бы как грязное пятно.

     РАДИУС считает ripple.js и намеренно НЕ дотягивает до дальнего угла: полный
     радиус раздувает круг за один кадр, и волна читается как заливка слева направо —
     видно, что нажатие засчитано, но не видно ГДЕ коснулись. А точка касания и есть
     главное, что волна сообщает. Мягкий край (radial-gradient) — по той же причине.

     ОБРЕЗКА — своим слоем, а НЕ overflow:hidden на самой кнопке. Первая версия резала
     кнопку целиком, и вместе с волной срезались угловые индикаторы: кружок непрочитанных
     и тортик дня рождения сидят на `top:-7px;right:-7px`, то есть НАМЕРЕННО торчат за
     край (см. .chatdot). Волна живёт в собственной коробке `.ripple-box`, которая
     повторяет скругление кнопки и обрезает только себя. */
  /* .gchip/.rchip/.oent-live — blox52 #12: чипы групп и учеников (и ожившая плашка-фильтр)
     вошли в SURFACES (public/ripple.js) — решение blox45 #8 «волна на бумажных поверхностях»
     не отменялось, а их в списке не было. position:relative чипам нужен как якорь .ripple-box
     (у .gchip и .oent он уже был по другим причинам — тут для полноты списка). */
  .btn,.btn-primary,.btn-pub,.btn-ghost,.btn-fb,.mgr-btn,.icon-btn,.hub-card,.gchip,.rchip,.oent-live{
    position:relative;-webkit-tap-highlight-color:transparent}
  /* цветные плашки ВНУТРИ чипа — счётчик «к проверке» и бейдж статуса на якорной заливке —
     поднимаются НАД волной (тот же приём, что у .chatdot: z-index:1, .ripple-box лежит на 0):
     чернильная волна умножением делала бы тёмную заливку почти чёрной на время касания. */
  .gchip .gn,.rchip .rrev{position:relative;z-index:1}
  /* ЧЕРНИЛА, А НЕ ПЛЁНКА (blox52 #8). Волна лежала на бумаге ПОВЕРХ неё — обычным
     полупрозрачным пятном, и на светлых поверхностях (плитка кабинета просила
     --accent, а в синих схемах это светлая Maya) читалась как блик стекла, а не как
     след от пальца. Теперь на бумаге она УМНОЖАЕТ: краска уходит В лист, зерно и тон
     бумаги видны сквозь неё, и осветлить бумагу волна больше не может физически.
     Режим держит роль --ripple-blend (theme.css): днём multiply, НОЧЬЮ normal — ночная
     бумага тёмная, а чернила светлые, и умножение светлым по тёмному не даёт ничего.
     Залитые роли (--primary/--accept) гасят режим у себя: у них волна белая, а
     умножение белым — это тождество, то есть волна исчезла бы совсем.
     z-index у коробки СНЯТ намеренно: z-index:0 создавал контекст наложения, и
     смешивание замыкалось внутри пустой коробки — режим стоял, а эффекта не было.
     Порядок отрисовки не изменился: коробка позиционирована и идёт последним ребёнком,
     а угловые индикаторы держат свой z-index:1 (см. .chatdot). */
  .ripple-box{position:absolute;inset:0;overflow:hidden;border-radius:inherit;
    pointer-events:none}
  .ripple{position:absolute;border-radius:50%;pointer-events:none;transform:scale(0);
    mix-blend-mode:var(--ripple-blend,normal);
    background:radial-gradient(circle,var(--ripple-ink,currentColor) 0%,
                               var(--ripple-ink,currentColor) 40%,transparent 72%);
    animation:ripple-grow .45s cubic-bezier(.2,.5,.4,1) forwards,
              ripple-fade .45s linear forwards}
  /* Рост и затухание — ДВЕ анимации с разными кривыми, и это не педантизм. Сначала обе
     ехали на общем --ease: он резко стартует (.32,.72,0,1) и обнулял прозрачность за
     первые ~40 мс — волна честно расходилась, но увидеть её было нельзя. Интерфейсная
     кривая описывает, как вещь ПРИЕЗЖАЕТ на место; затуханием она не управляет. */
  @keyframes ripple-grow{to{transform:scale(1)}}
  @keyframes ripple-fade{0%{opacity:.3}60%{opacity:.24}100%{opacity:0}}
  /* Кому анимации мешают: рост снимаем, отклик НЕ убираем — на тач это единственный
     признак, что нажатие засчитано. Остаётся короткая ровная вспышка. */
  @media (prefers-reduced-motion:reduce){
    .ripple{animation:ripple-flash .2s linear forwards;
      /* тихая версия для общего правила (theme.css): вспышка обязана доиграть — ripple.js
         снимает волну по её animationend, иначе волна осталась бы на кнопке навсегда */
      --motion-reduced:ripple-flash .2s linear forwards}}
  @keyframes ripple-flash{from{transform:scale(1);opacity:.22}to{transform:scale(1);opacity:0}}


  /* ═══════════════════════════════════════════════════════════════════════
     ПЕРЕРИСОВКА B — КНОПКА НА ПОЛОСЕ ГРУППЫ (агент 2). На полосе (.gband, style-01-chrome.css)
     серая кнопка читалась бы дырой в цвете: там вторичная кнопка — прозрачная с белым контуром
     1px и белым текстом (эскиз HwB: «Чат группы», «Для родителя»). Цель касания та же 44px.
     Пункты гамбургера на ТЕЛЕФОНЕ лежат не на полосе, а во всплывающем белом меню — им
     возвращается обычная серая кнопка; на ПК с мышью меню раскрыто прямо в строке полосы. */
  .gband .btn-ghost{--btn-face:transparent;--btn-ink:var(--on-group);--btn-line:var(--on-group-line)}
  .gband .btn-ghost .ic{color:currentColor}
  @media (hover:hover){.gband .btn-ghost:hover{background:var(--on-group-hover);border-color:var(--on-group-line)}}
  .gband .btn-ghost:disabled{--btn-ink:var(--on-group-line);--btn-line:var(--on-group-fill)}
  .gband .gkebab-menu .btn-ghost{--btn-face:var(--btn-2-bg);--btn-ink:var(--btn-2-ink);--btn-line:transparent}
  @media (hover:hover){.gband .gkebab-menu .btn-ghost:hover{background:var(--btn-2-bg-hover);border-color:transparent}}
  @media (min-width:900px) and (hover:hover) and (pointer:fine){
    .gband .gkebab-menu .btn-ghost{--btn-face:transparent;--btn-ink:var(--on-group);--btn-line:var(--on-group-line)}
    .gband .gkebab-menu .btn-ghost:hover{background:var(--on-group-hover);border-color:var(--on-group-line)}
  }
  /* «Долги» у ребёнка на полосе — красный тинт (эскиз HwB «Долги: 1»): долг — факт, не пожар;
     красная точка внутри (.sdebt-dot) остаётся единственной сплошной тревогой */
  .gband .btn-ghost.sdebt{color:var(--danger-ink);border-color:transparent;background:var(--alert-fill)}
  @media (hover:hover){.gband .btn-ghost.sdebt:hover{background:var(--alert-tint);border-color:transparent}}
