/* Svitlo Admin Panel — брендові токени зі Svitlo brandbook.
   Стиль: Data-Dense Dashboard (щільні відступи, максимум даних на екрані). */

/* Editor's Note (ліцензія куплена окремо, файли self-hosted, не з превью-
   сайту) — лише вага Hairline, лише для тег-лайну "Converting Svitlo into
   Energy". Дві грані під ОДНИМ font-family: справжній курсивний файл
   (HairlineItalic), а не CSS-синтезований italic на прямій грані — так
   штрихи лишаються тонкими й чистими, як у справжнього шрифту. */
@font-face {
    font-family: 'EditorsNoteHairline';
    src: url('/static/fonts/EditorsNote-Hairline.otf') format('opentype');
    font-style: normal;
    font-weight: 100;
    font-display: swap;
}

@font-face {
    font-family: 'EditorsNoteHairline';
    src: url('/static/fonts/EditorsNote-HairlineItalic.otf') format('opentype');
    font-style: italic;
    font-weight: 100;
    font-display: swap;
}

:root {
    /* Нативні контроли (календар, чекбокси, скролбари) слідують за темою. */
    color-scheme: light;

    /* --- Бренд (brandbook) --- */
    --sv-primary: #1A393F;
    --sv-primary-dark: #10262A;
    --sv-accent: #DCAF42;
    --sv-sage: #97BBA7;
    --sv-maroon: #912627;

    /* --- Нейтральні --- */
    --sv-bg: #F4F6F5;
    --sv-surface: #FFFFFF;
    --sv-surface-alt: #FAFBFB;
    --sv-text: #1A393F;
    /* Затемнено з #5C7A80 — той не дотягував до WCAG AA (4.23:1) на нашому фоні */
    --sv-text-muted: #4A6469;
    --sv-border: #E1E7E5;
    /* Чорнило плиток у рядах чисел (.sv-stat-row): ним підфарбовують тло
       плитки — сильніше для важливішої. На світлій темі це primary (сіро-
       бірюзовий відтінок, як у макеті), на темній — sage: primary там
       злився б із поверхнею. */
    --sv-stat-ink: var(--sv-primary);
    /* «Де гальмують»: порядкова шкала одного відтінку — швидше = темніше,
       + нейтральний сірий «ще не пройшли». Перевірено валідатором dataviz:
       --ordinal (монотонна світлота, крок ΔL >= 0.06, найсвітліший >= 2:1 до
       поверхні) і сірий проти найсвітлішого кроку (ΔE 17 при нормальному
       зорі). У темній темі — свої кроки, не автоматична інверсія. Текст
       усередині сегмента — за світлотою заливки (--sv-velo-ink-*). */
    --sv-velo-1: #3E6B58;
    --sv-velo-2: #5A7D6D;   /* було #658979: білий підпис 3.9:1 -> 4.6:1 */
    --sv-velo-3: #85A296;
    --sv-velo-4: #A0B6AD;
    --sv-velo-pending: #E3E8E6;
    --sv-velo-ink-dark: #FFFFFF;
    --sv-velo-ink-light: #10262A;

    /* --- Семантичні --- */
    /* Затемнено з #2E7D5B на прохання користувача: апрув-кнопки мали
       виглядати темнозеленими, а не середньо-зеленими. #1F5C42 дає ще
       вищий контраст з білим текстом (7.86:1 проти 5.00:1). */
    --sv-success: #1F5C42;
    /* Кольори кнопок Telegram (style у Bot API) — кружечки «Колір кнопки» форми;
       це кольори самого Telegram, тож однакові в обох темах. */
    --sv-tg-success: #3FA75A;
    --sv-tg-primary: #3A8FD9;
    --sv-tg-danger: #E0524B;
    /* Затемнено з #B07D12: той давав лише 3.63:1 на білому й 3.34:1 на --sv-bg,
       а використовується на дрібному тексті (0.72rem) — це провал WCAG AA.
       #8C620D дає 5.43:1 / 5.00:1 і лишається в бренд-палітрі бурштину. */
    --sv-warning: #8C620D;

    /* --- Фонові заливки (бренд лише як ФОН: золото й сейдж як текст
       на світлому провалюють контраст — 2.05:1 і 2.10:1) --- */
    --sv-tint-sage: #EEF4F0;
    --sv-tint-amber: #FFF8E6;
    --sv-tint-red: #FBEDED;
    --sv-tint-green: #EAF6F0;
    --sv-row-hover: #FFFBF0;

    /* --- Ховер-відтінки --- */
    --sv-maroon-hover: #781F20;
    --sv-accent-hover: #C99C33;
    /* Затемнено з #7CA28D: той на .sv-btn-approve:hover давав лише 4.36:1 з
       темним текстом (провал AA). #83AB96 — 4.84:1 і все ще помітно темніший
       за базовий --sv-sage (#97BBA7), тож ховер читається. */
    --sv-sage-hover: #83AB96;

    /* --- Рольові токени, РІЗНІ між світлою й темною темами. Компоненти нижче
       посилаються лише на них, тож тему правимо ТІЛЬКИ в :root-блоках. --- */
    --sv-link: var(--sv-primary);         /* .sv-link — текст-посилання */
    --sv-on-tint: var(--sv-primary);      /* текст на сейдж-тінті (обрані опції, бейдж документа) */
    --sv-strong-text: #FFFFFF;            /* текст на суцільній заливці success / maroon */
    --sv-accent-text: #86651A;            /* золото як ТЕКСТ на світлому (5.2:1); бренд-#DCAF42 там лише 2:1 */
    --sv-accent-on-dark: #E3BE62;         /* дрібне золото на темній шапці модалки (5.2:1 замість 4.49) */
    --sv-maroon-on-dark: #F2A7A8;     /* бордовий як текст на темній шапці (--sv-primary), ~6:1 */
    --sv-focus: var(--sv-primary);        /* кільце фокуса на світлих поверхнях (accent давав ~2:1) */
    --sv-check: var(--sv-primary);        /* відмітка радіо й чекбокса (input[type=radio|checkbox] нижче) */
    --sv-check-ink: #FFFFFF;              /* галочка поверх --sv-check */
    --sv-funnel-bar: var(--sv-primary);   /* тонкий прогрес-бар «Швидкість воронки» */
    --sv-fbar-base: var(--sv-primary);    /* найтемніший крок воронки; далі рампа до сейджу */
    --sv-fbar-ramp: 10%;                  /* крок рампи; у темній темі менший — див. нижче */
    /* Лінія спарклайна. НЕ --sv-sage напряму: сейдж на білому дає 2.10:1, а
       спарклайн несе інформацію, тож підпадає під SC 1.4.11 з порогом 3:1
       для графіки. Затемнений сейдж дає 3.91:1 і лишається впізнаваним.
       У темній темі повертаємось до світлого сейджу (7.19:1) — теми
       проєктуються окремо, а не інверсією одна одної. */
    --sv-spark-line: #5E8A76;
    /* «По тижнях»: стовпчик — «склянка». Контейнер (почали заявку) —
       тонований із тонким контуром, заливка всередині (стали учнями) —
       суцільна. Один тон: контур і заливка — той самий сейдж різної сили.
       Заміряно: заливка проти фону 4.75:1, число на ній 4.75:1; контур
       проти фону 1.9:1 — тихий навмисно, над кожним стовпчиком є число. */
    --sv-trend-total: #EEF4F0;
    --sv-trend-edge: #B7CFC1;
    --sv-trend-part: #4A7D66;
    --sv-trend-part-ink: #FFFFFF;
    --sv-trend-band: #E3ECE6;
    /* Смуга гілки вебінару у воронці вступу — світліший крок ТОГО САМОГО
       відтінку, що й основна смуга (primary). Заміряно валідатором
       dataviz (ordinal): розкид відтінку 0°, 3.96:1 до поверхні, 3.65:1
       до доріжки. Затемнений сейдж (#5E8A76), узятий спершу, провалив
       перевірку «один відтінок»: 47° від primary — це вже друга серія,
       а не крок тієї самої воронки. У темній темі основна смуга — сейдж,
       тож і гілка там сейджева (див. нижче). */
    --sv-ef-branch: #5A8790;
    /* Заливка застосованої підбірки. Окремий токен, а не --sv-primary:
       сусідні чіпи спільних підбірок носять --sv-tint-sage, і в ТЕМНІЙ
       темі primary (#1A393F) проти того тінту (#1E3A33) дає 1.00:1 —
       активний стан зникає рівно там, де він потрібен. Тому в темній
       темі беремо світлий сейдж (5.84:1 проти тінту), а в світлій
       лишається primary. Теми проєктуються окремо, не інверсією. */
    --sv-view-active-bg: var(--sv-primary);
    --sv-view-active-ink: #FFFFFF;
    --sv-overlay: rgba(16, 38, 42, 0.55); /* затемнення під модалкою */
    /* Заливка полів вводу замість рамки. Значення залежить від ПІДКЛАДКИ,
       а не від теми: на білій панелі чи в модалці поле має бути темнішим
       за неї (заглиблення), а на сірому тлі сторінки — світлішим
       (піднесення). Обидва варіанти читаються як поле, бо форму й курсор
       ніхто не скасовував.

       Дефолт — САМЕ --sv-surface, і напрям тут важить більше за значення.
       Було навпаки: --sv-bg за замовчуванням, а кожен контрол поверх
       сторінки доводилось перелічувати поіменно. Ціна пропуску в тому
       списку — заливка кольору тла проти тла, тобто 1.00:1: контрол не
       «трохи не того відтінку», а невидимий. Так і сталося з рядком
       сортування Students і пошуком на дошці бадді.

       Тепер перелічуємо ПІДКЛАДКИ (їх мало, і нова з'являється рідше за
       новий рядок контролів), а ціна пропуску — поле трохи світліше, ніж
       мало б бути. Помітно, але не зникає. */
    --sv-field-fill: var(--sv-surface);
    /* Стрілка кастомного <select> як data-URI; у темній темі — світліший stroke. */
    --sv-select-caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%234A6469' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

    /* --- Щільна шкала відступів (density 8/10) ---
       s05/s15 — мікрокроки для щільних компонентів (бейджі, чіпи, рядки
       таблиць), як 050/150 у Polaris. Усе інше — лише основна шкала;
       сирих px у відступах не буває (tools/ui-qa: stylelint sv/spacing-tokens). */
    --sv-s05: 2px;
    --sv-s1: 4px;
    --sv-s15: 6px;
    --sv-s2: 8px;
    --sv-s3: 12px;
    --sv-s4: 16px;
    --sv-s5: 24px;
    --sv-s6: 32px;
    /* Піделемент перемикача (.sv-fs-dep, .sv-qb-reveal-body): лінія ліворуч
       з-під центру перемикача — половина ширини доріжки (36px). */
    --sv-sub-indent: 18px;
    /* «Таблетки» (тип питання, статус, «скоро»): відступ згори й знизу
       однаковий. Було 1px/3px (компенсація малих літер Manrope), але текст
       сидів вище центру — помітив Паша 14.10; заміряно на рендері ×4: від
       верху великих літер до краю на ~1px менше, ніж від базової лінії до
       низу. Змінні лишились, щоб не шукати всі таблетки, якщо знову правити. */
    --sv-pill-pad-top: 2px;
    --sv-pill-pad-bottom: 2px;

    /* --- Висота контролів (3 рівні) --- інакше кожен .sv-btn/.sv-select/
       .sv-chip по коду рахував свою висоту сам (padding + line-height),
       і сусідні контроли в одному рядку (напр. чіпси періоду й дропдаун
       семестру на дашборді) розходились на 1-2px, хоч мали б бути врівень. */
    --sv-ctrl-h: 40px;      /* основні контроли: .sv-btn, .sv-input, .sv-select */
    --sv-ctrl-h-chip: 36px; /* чіпси/пігулки в ряд: .sv-chip, .sv-custom-range */
    --sv-ctrl-h-sm: 34px;   /* компактні inline-дії: .sv-btn-sm, .sv-select-sm */

    /* --- Значки поруч із текстом --- з них же рахується центрування за
       висотою рядка: calc((1lh - розмір) / 2), будь-який розмір шрифту. */
    --sv-radio-size: 20px;  /* input[type=radio] */
    --sv-check-size: 18px;  /* input[type=checkbox] */
    --sv-info-size: 16px;   /* «i» — .sv-info */
    --sv-switch-h: 20px;    /* доріжка перемикача — .sv-fs-switch-track */
    --sv-tap-h: 44px;       /* ціль дотику рядка з контролом — .sv-fs-switch */
    --sv-option-h: 48px;    /* варіант відповіді у формі учня — .sv-fp-option */

    /* --- Типографічна шкала (--sv-fs-lg / --sv-fs-2xl прибрано — не вживались) --- */
    --sv-fs-xs: 0.72rem;
    --sv-fs-sm: 0.8rem;
    --sv-fs-base: 0.875rem;
    --sv-fs-md: 1rem;
    --sv-fs-xl: 1.5rem;
    --sv-fs-3xl: 2.5rem;

    --sv-radius-sm: 6px;
    --sv-radius: 10px;
    --sv-radius-lg: 14px;
    --sv-radius-pill: 999px;

    /* Тіні багатошарові й з ВІД'ЄМНИМ spread. Це не косметика: один шар
       на кшталт `0 4px 12px` розтікається рівномірно на всі 12px, і на
       нашому сіро-зеленому фоні (--sv-bg, не білому) це читається не як
       м'яка глибина, а як рівна сіра смуга по краю — «пляма», а не
       розмиття. Від'ємний spread підтягує кожен шар усередину, тож біля
       самого елемента тінь щільна й вузька, а далі швидко згасає — саме
       так поводиться справжня тінь. Кілька шарів із наростаючим blur
       дають плавний спад замість одного різкого. */
    --sv-shadow-sm:
        0 1px 1px rgba(26, 57, 63, 0.04),
        0 2px 6px -2px rgba(26, 57, 63, 0.06);
    --sv-shadow-md:
        0 1px 2px rgba(26, 57, 63, 0.05),
        0 6px 14px -6px rgba(26, 57, 63, 0.10),
        0 18px 36px -14px rgba(26, 57, 63, 0.12);
    --sv-shadow-lg:
        0 2px 6px rgba(16, 38, 42, 0.10),
        0 14px 30px -10px rgba(16, 38, 42, 0.20),
        0 44px 90px -28px rgba(16, 38, 42, 0.38);

    --sv-dur-fast: 0.15s;
    --sv-dur: 0.18s;
    --sv-dur-enter: 0.26s;                /* поява вікна (Solar design «Рух»: ~260 мс; зникнення — --sv-dur) */
    --sv-dur-spin: 0.8s;                  /* один оберт спінера (індикатор запиту) */
    --sv-dur-toast: 4.5s;                 /* скільки видно коротке повідомлення вгорі (.sv-top-toast) */

    --sv-z-sticky: 10;
    --sv-z-modal: 50;

    --sv-topbar-h: 56px;
    /* Топбар темний в обох темах — свої значення, без перевизначення в темній.
       Пункт меню: напівпрозорий білий, ховер — 10% білого; розмір шрифту
       спільний для пунктів і кнопки «Ще», щоб вона не випадала з ряду. */
    --sv-topbar-ink: rgba(255, 255, 255, 0.75);
    /* Текст на суцільному --sv-primary (він темний в обох темах): смуга
       сторінки форми для учня. */
    --sv-on-primary: #FFFFFF;
    /* Логотип школи на сторінках для учнів — прозорий файл зі static/brand
       (свій у кожної інсталяції школи), у темній темі — світлий варіант.
       Cloudflare кешує статику без огляду на ?v=, тож нова версія логотипа —
       нове ім'я файла. */
    --sv-brand-logo: url("/static/brand/logo.webp");
    /* Банер сторінки форми: білий логотип на фірмовому градієнті (у будь-якій
       темі) — відблиски сейджу й золота на --sv-primary, як на сторінці входу. */
    --sv-brand-banner: url("/static/brand/banner-logo.webp");
    --sv-glow-sage: rgba(151, 187, 167, 0.26);
    --sv-glow-gold: rgba(220, 175, 66, 0.14);
    /* Готові градієнти банера (налаштування форми «Вигляд», Паша 13.10.2026):
       палітра школи, усі темні — білий логотип читається. Ті самі градієнти
       намальовано в прев'ю посилання static/brand/og-banner*.png. */
    --sv-banner-brand:
        radial-gradient(circle at 0% 0%, var(--sv-glow-sage), transparent 55%),
        radial-gradient(circle at 100% 100%, var(--sv-glow-gold), transparent 50%),
        var(--sv-primary);
    --sv-banner-dawn:
        radial-gradient(ellipse 90% 80% at 50% 115%, rgba(220, 175, 66, 0.7), transparent 70%),
        linear-gradient(180deg, #10262A 0%, #1A393F 100%);
    --sv-banner-meadow:
        radial-gradient(circle at 15% 10%, rgba(220, 175, 66, 0.16), transparent 45%),
        linear-gradient(135deg, #1F4A41 0%, #3E6B58 60%, #5E8A76 100%);
    --sv-banner-night:
        radial-gradient(circle at 85% 0%, rgba(220, 175, 66, 0.2), transparent 45%),
        linear-gradient(160deg, #0A161A 0%, #10262A 50%, #1A393F 100%);
    --sv-banner-deep:
        radial-gradient(circle at 100% 0%, rgba(151, 187, 167, 0.22), transparent 50%),
        linear-gradient(135deg, #10262A 0%, #1F4E5A 55%, #2F6F7A 100%);
    /* Зірочка обов'язкового питання — фірмове золото (Паша, 13.10.2026). На
       білому воно й так м'яке, тож повне; у темній темі — 85%, щоб не різало. */
    --sv-required: var(--sv-accent);
    --sv-topbar-hover: rgba(255, 255, 255, 0.1);
    --sv-fs-nav: 0.85rem;
    --sv-ease: cubic-bezier(0.4, 0, 0.2, 1);
}

/* ============ Темна тема ============
   Перевизначаємо ЛИШЕ значення токенів — жоден компонент нижче про тему не
   знає. Два однакові набори: під @media (немає явного вибору → як в ОС) і
   під [data-theme="dark"] (явний вибір, cookie sv_theme). Кожну пару
   колір/фон перевірено за тим самим порогом WCAG AA, що й світлу палітру.

   --sv-primary/-dark, --sv-accent, --sv-sage лишаються — вони працюють
   заливкою з білим АБО темним текстом у будь-якій темі (топбар, бейджі,
   апрув-кнопка). Змінюється те, що в темній інвертує роль: нейтралі,
   тінти, семантичні кольори-як-текст, тіні. Кільце фокуса, посилання,
   стрілка select, accent-color чекбоксів, затемнення модалки — через
   окремі рольові токени, тож правило компонента лишається одне. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        color-scheme: dark;
        --sv-brand-logo: url("/static/brand/logo-dark.webp");
        --sv-required: rgba(220, 175, 66, 0.85);

        --sv-bg: #0E1C1F;
        --sv-surface: #15292E;
        --sv-surface-alt: #1B333A;
        --sv-text: #E9EEEC;
        --sv-text-muted: #93A8AC;   /* 6.1:1 на --sv-surface */
        --sv-stat-ink: var(--sv-sage);
        --sv-velo-1: #97BBA7;
        --sv-velo-2: #7A9B8C;
        --sv-velo-3: #638177;
        --sv-velo-4: #516C66;
        --sv-velo-pending: #2C3E43;
        --sv-velo-ink-dark: #0E1C1F;
        --sv-velo-ink-light: #FFFFFF;
        --sv-border: #33474D;

        --sv-success: #64C99A;      /* як текст 7.5:1 на --sv-surface; заливку .sv-badge-success бере темний --sv-strong-text */
        --sv-warning: #E3B25E;      /* 7.8:1 на --sv-surface */
        --sv-maroon:  #E88A8B;      /* як текст 6.1:1; як заливка .sv-btn-danger — з темним --sv-strong-text (6.6:1) */
        --sv-maroon-hover: #F0A0A0;

        --sv-tint-sage:  #1E3A33;
        --sv-tint-amber: #33280F;
        --sv-tint-red:   #3A1E1F;
        --sv-tint-green: #14312A;
        --sv-row-hover:  #23201A;

        --sv-accent-hover: #E4BC5C;
        --sv-sage-hover: #A6C6B4;   /* сейдж-кнопка світла → на темному ховер СВІТЛІШАЄ */

        --sv-link: var(--sv-accent);
        --sv-on-tint: var(--sv-text);
        --sv-strong-text: var(--sv-primary-dark);
        --sv-accent-text: var(--sv-accent);
        --sv-focus: var(--sv-accent);
        --sv-check: var(--sv-sage);
        --sv-check-ink: var(--sv-primary-dark);
        --sv-funnel-bar: var(--sv-sage);
        /* На темній поверхні світлий сейдж дає 7.19:1 — тут він і лишається,
           на відміну від світлої теми (див. --sv-spark-line угорі). */
        --sv-spark-line: var(--sv-sage);
        --sv-trend-total: rgb(151 187 167 / 0.08);
        --sv-trend-edge: rgb(151 187 167 / 0.34);
        --sv-trend-part: var(--sv-sage);
        --sv-trend-part-ink: #10262A;
        --sv-trend-band: rgb(151 187 167 / 0.12);
        /* Гілка воронки вступу: темніший крок сейджу — 3.87:1 до поверхні, розкид відтінку 4°. */
        --sv-ef-branch: #5E8A76;
        --sv-view-active-bg: var(--sv-sage);
        --sv-view-active-ink: var(--sv-primary-dark);
        --sv-overlay: rgba(0, 0, 0, 0.66);
        --sv-select-caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%2393A8AC' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

        --sv-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.40), 0 2px 8px -2px rgba(0, 0, 0, 0.50);
        --sv-shadow-md: 0 2px 4px rgba(0, 0, 0, 0.40), 0 8px 20px -6px rgba(0, 0, 0, 0.55);
        --sv-shadow-lg: 0 4px 10px rgba(0, 0, 0, 0.45), 0 24px 60px -18px rgba(0, 0, 0, 0.70);
    }

    /* Логін: у темній темі бренд-градієнт на --sv-primary виходив СВІТЛІШИЙ
       за сторінку — прибираємо підкладку, лишаємо ледь помітні відблиски. */
    :root:not([data-theme="light"]) .sv-login-page {
        background:
            radial-gradient(circle at 15% 15%, rgba(151, 187, 167, 0.10), transparent 45%),
            radial-gradient(circle at 85% 80%, rgba(220, 175, 66, 0.10), transparent 45%),
            var(--sv-bg);
    }

    :root:not([data-theme="light"]) .sv-nav-card-active:hover {
        box-shadow: var(--sv-shadow-md);
    }

    /* Кнопка входу Google — єдина дія на екрані логіну; --sv-primary на
       темній картці майже зливається, тож у темній темі вона золота. */
    :root:not([data-theme="light"]) .sv-google-btn {
        background: var(--sv-accent);
        color: var(--sv-primary);
    }
    :root:not([data-theme="light"]) .sv-google-btn:hover {
        background: var(--sv-accent-hover);
    }

    /* Бар «Конверсія воронки» — темно-зелений на майже чорному фоні зливався б;
       окремий видимий teal (білий текст усередині — 7.6:1). */
    /* Темна тема: основа бара вже світла (#2F5A63), тож крок рампи вдвічі
       менший. Заміряно: при 10% пʼятий крок дає білому тексту 3.85:1 —
       провал AA. При 5% найсвітліший крок тримає 4.99:1. */
    :root:not([data-theme="light"]) {
        --sv-fbar-base: #2F5A63;
        --sv-fbar-ramp: 5%;
    }

    :root:not([data-theme="light"]) .sv-custom-range.is-active {
        border-color: var(--sv-accent);
        box-shadow: 0 0 0 1px var(--sv-accent);
    }
}

:root[data-theme="dark"] {
    color-scheme: dark;
    --sv-brand-logo: url("/static/brand/logo-dark.webp");
    --sv-required: rgba(220, 175, 66, 0.85);

    --sv-bg: #0E1C1F;
    --sv-surface: #15292E;
    --sv-surface-alt: #1B333A;
    --sv-text: #E9EEEC;
    --sv-text-muted: #93A8AC;
    --sv-stat-ink: var(--sv-sage);
    --sv-velo-1: #97BBA7;
    --sv-velo-2: #7A9B8C;
    --sv-velo-3: #638177;
    --sv-velo-4: #516C66;
    --sv-velo-pending: #2C3E43;
    --sv-velo-ink-dark: #0E1C1F;
    --sv-velo-ink-light: #FFFFFF;
    --sv-border: #33474D;

    --sv-success: #64C99A;
    --sv-warning: #E3B25E;
    --sv-maroon:  #E88A8B;
    --sv-maroon-hover: #F0A0A0;

    --sv-tint-sage:  #1E3A33;
    --sv-tint-amber: #33280F;
    --sv-tint-red:   #3A1E1F;
    --sv-tint-green: #14312A;
    --sv-row-hover:  #23201A;

    --sv-accent-hover: #E4BC5C;
    --sv-sage-hover: #A6C6B4;

    --sv-link: var(--sv-accent);
    --sv-on-tint: var(--sv-text);
    --sv-strong-text: var(--sv-primary-dark);
    --sv-accent-text: var(--sv-accent);
    --sv-focus: var(--sv-accent);
    --sv-check: var(--sv-sage);
    --sv-check-ink: var(--sv-primary-dark);
    --sv-funnel-bar: var(--sv-sage);
    --sv-spark-line: var(--sv-sage);
    --sv-trend-total: rgb(151 187 167 / 0.08);
    --sv-trend-edge: rgb(151 187 167 / 0.34);
    --sv-trend-part: var(--sv-sage);
    --sv-trend-part-ink: #10262A;
    --sv-trend-band: rgb(151 187 167 / 0.12);
    --sv-ef-branch: #5E8A76;
    --sv-view-active-bg: var(--sv-sage);
    --sv-view-active-ink: var(--sv-primary-dark);
    --sv-overlay: rgba(0, 0, 0, 0.66);
    --sv-select-caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%2393A8AC' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

    --sv-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.40), 0 2px 8px -2px rgba(0, 0, 0, 0.50);
    --sv-shadow-md: 0 2px 4px rgba(0, 0, 0, 0.40), 0 8px 20px -6px rgba(0, 0, 0, 0.55);
    --sv-shadow-lg: 0 4px 10px rgba(0, 0, 0, 0.45), 0 24px 60px -18px rgba(0, 0, 0, 0.70);
}

:root[data-theme="dark"] .sv-login-page {
    background:
        radial-gradient(circle at 15% 15%, rgba(151, 187, 167, 0.10), transparent 45%),
        radial-gradient(circle at 85% 80%, rgba(220, 175, 66, 0.10), transparent 45%),
        var(--sv-bg);
}

:root[data-theme="dark"] .sv-nav-card-active:hover {
    box-shadow: var(--sv-shadow-md);
}

:root[data-theme="dark"] .sv-google-btn {
    background: var(--sv-accent);
    color: var(--sv-primary);
}
:root[data-theme="dark"] .sv-google-btn:hover {
    background: var(--sv-accent-hover);
}

:root[data-theme="dark"] {
    --sv-fbar-base: #2F5A63;
    --sv-fbar-ramp: 5%;
}

:root[data-theme="dark"] .sv-custom-range.is-active {
    border-color: var(--sv-accent);
    box-shadow: 0 0 0 1px var(--sv-accent);
}

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    font-family: "Manrope", -apple-system, "Segoe UI", sans-serif;
    font-size: 15px;
    /* Вага 400 підвантажується явно: раніше її не було в наборі Google Fonts,
       а браузер усе одно брав 400 за замовчуванням і синтезував накреслення. */
    font-weight: 400;
    line-height: 1.5;
    background: var(--sv-bg);
    color: var(--sv-text);
    -webkit-font-smoothing: antialiased;
}

/* Поки відкрите висувне меню (.sv-drawer), сторінка позаду не гортається —
   немає вкладеного скрол-контейнера (сторінка й так гортається сама, див.
   .sv-app нижче), тож блокуємо саме body. */
body.sv-no-scroll {
    overflow: hidden;
}

h1, h2, h3, h4 {
    font-weight: 800;
    letter-spacing: -0.01em;
    margin: 0;
}

/* Видимий фокус для клавіатурної навігації (WCAG 1.4.11, severity High).
   Колір — --sv-focus (у світлій темі це --sv-primary, ~12:1 на світлому;
   акцентне золото давало лише ~2:1 і саме провалювало SC 1.4.11). На темних
   поверхнях, що є в ОБОХ темах (топбар, шапка модалки, підсумкова колонка,
   headline) — золото: там --sv-primary невидиме. У темній темі --sv-focus
   уже золотий, тож ті ж правила просто збігаються. */
:focus-visible {
    outline: 2px solid var(--sv-focus);
    outline-offset: 2px;
    /* border-radius тут НЕ задаємо: обвідка й так повторює заокруглення
       елемента, а спільні 4px міняли кутики кожної кнопки (8–14px) у
       момент фокуса — кнопка «стрибала» формою. */
}

/* .sv-headline звідси прибрано: картка більше не темна, вона стоїть на
   --sv-surface, а золоте кільце фокуса на світлому дає ~2:1 — рівно те, від
   чого застерігає коментар нижче про .sv-drawer. Тепер там діє загальний
   --sv-focus. */
.sv-topbar :focus-visible,
.sv-modal-header :focus-visible {
    outline-color: var(--sv-accent);
}

/* ...але вміст висувного меню — на СВІТЛІЙ панелі (--sv-surface), тож там
   кільце назад до --sv-focus (золоте на білому = ~2:1). */
.sv-drawer :focus-visible {
    outline-color: var(--sv-focus);
}

/* Після кліку мишкою кільця немає — лише після Tab (позначку ставить
   panel.js). !important — бо локальні :focus-visible нижче мають вищу
   специфічність і власний outline. */
html[data-input="pointer"] :focus-visible,
html[data-input="pointer"] .sv-fb-point input:focus-visible + span {
    outline: none !important;
}

button {
    font-family: inherit;
    font-size: inherit;
    cursor: pointer;
}

/* Радіокнопка — одна на весь продукт (Solar design.md «Радіокнопка й чекбокс»): тонке
   кільце, у вибраної — крапка всередині з проміжком до кільця, як у Google
   Forms і на сторінці учня (.sv-fp-option). Штатна (accent-color) малює
   суцільну заливку й темне кільце. Колір — --sv-radio, типово --sv-check.
   Сховані радіокнопки сегментів і шкал (opacity: 0) так і лишаються схованими. */
input[type="radio"] {
    /* Розмір — один токен на весь продукт; компонент, якому треба інший,
       ставить --sv-control-size на собі (успадковується), а не px на input. */
    --sv-ctl: var(--sv-control-size, var(--sv-radio-size));

    flex-shrink: 0;
    display: inline-grid;
    place-items: center;
    width: var(--sv-ctl);
    height: var(--sv-ctl);
    margin: 0;
    /* Шрифт і висота рядка — від підпису: 1lh і cap нижче рахуються від
       справжнього тексту поруч, а не від системного шрифту поля. Без цього
       кружечок з'їжджав на кожному іншому шрифті чи розмірі (Паша 21.10). */
    font: inherit;
    /* Усередині тексту — середина контрола на середині малих літер, від
       шрифту підпису (формула з cap промахувалась: базова лінія порожнього
       квадратика береться від галочки всередині). */
    vertical-align: middle;
    border: 2px solid var(--sv-text-muted);
    border-radius: 50%;
    background: var(--sv-surface);
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    transition: border-color var(--sv-dur) var(--sv-ease);
}

input[type="radio"]::before {
    content: "";
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--sv-radio, var(--sv-check));
    transform: scale(0);
    transition: transform var(--sv-dur) var(--sv-ease);
}

input[type="radio"]:checked {
    border-color: var(--sv-radio, var(--sv-check));
}

input[type="radio"]:checked::before {
    transform: scale(1);
}

input[type="radio"]:focus-visible {
    outline: 2px solid var(--sv-radio, var(--sv-check));
    outline-offset: 2px;
}

input[type="radio"]:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Чекбокс — той самий принцип (Solar design.md «Радіокнопка й чекбокс»):
   тонка рамка, у вибраного — заливка кольором відмітки й галочка
   контрастним до неї кольором (--sv-check-ink: біла на темному, темна на
   світлій шавлії). Галочка — маска, щоб колір брався з теми. Перемикачі
   (role=switch) і сховані чекбокси компонентів мають свої стилі. */
input[type="checkbox"]:not([role="switch"]) {
    --sv-ctl: var(--sv-control-size, var(--sv-check-size));

    flex-shrink: 0;
    display: inline-grid;
    place-items: center;
    width: var(--sv-ctl);
    height: var(--sv-ctl);
    margin: 0;
    font: inherit;
    vertical-align: middle;
    border: 2px solid var(--sv-text-muted);
    border-radius: 4px;
    background: var(--sv-surface);
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    transition: border-color var(--sv-dur) var(--sv-ease), background-color var(--sv-dur) var(--sv-ease);
}

input[type="checkbox"]:not([role="switch"])::before {
    content: "";
    width: 80%;
    height: 80%;
    background: var(--sv-check-ink);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5l3 3 6-7' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5l3 3 6-7' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
    transform: scale(0);
    transition: transform var(--sv-dur) var(--sv-ease);
}

input[type="checkbox"]:not([role="switch"]):checked {
    border-color: var(--sv-check);
    background: var(--sv-check);
}

input[type="checkbox"]:not([role="switch"]):checked::before {
    transform: scale(1);
}

input[type="checkbox"]:not([role="switch"]):focus-visible {
    outline: 2px solid var(--sv-check);
    outline-offset: 2px;
}

input[type="checkbox"]:not([role="switch"]):disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Повага до системного налаштування «зменшити рух» */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        transition-duration: 0.01ms !important;
    }
}

/* Гортається завжди сама сторінка (не вкладений контейнер): min-height, а
   не фіксована висота. Колись .sv-app був height:100vh; overflow:hidden з
   прокруткою всередині .sv-board-wrap/.sv-table-wrap — на дошці це "з'їдало"
   колесо миші над короткою колонкою. Мобільний @media нижче лише дублює
   те саме (100dvh) — історична відмінність, яку можна буде звести. */
.sv-app {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

/* ============ Topbar ============ */

.sv-topbar {
    /* Grid із ДВОМА симетричними 1fr-колонками по краях (лого+нав зліва,
       юзер-кластер справа) і тег-лайном між ними: при однаковій ширині
       обох 1fr-треків їхня різниця скорочується математично, і середина
       треку тег-лайну ЗБІГАЄТЬСЯ зі справжнім центром бару — незалежно від
       того, наскільки різняться за шириною самі лого+нав і юзер-кластер.
       Раніше тег-лайн центрувався в залишку між ДВОМА окремими auto-
       колонками (лого, нав) і однією auto-колонкою юзера, і той залишок
       зсувався туди, де кластер був вужчий — на скріншоті 18.09 це було
       видно неозброєним оком. Абсолютне позиціювання тут навмисно НЕ
       беремо: geometric-guarantee grid-треків надійніший за left:50% +
       transform, якому довелось би окремо гарантувати відсутність
       наїзду на нав/юзер-кластер при кожній комбінації їхньої ширини. */
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--sv-s5);
    background: var(--sv-primary);
    color: white;
    padding: 0 var(--sv-s5);
    height: var(--sv-topbar-h);
    flex-shrink: 0;
    /* Сторінка гортається сама (див. .sv-app вище), тож топбар мусить бути
       липким — інакше поїхав би вгору разом з рештою. */
    position: sticky;
    top: 0;
    z-index: var(--sv-z-sticky);
}

/* Лого й нав в ОДНОМУ grid-треку (1) — flex усередині, той самий gap, що
   раніше давав grid трека між ними. min-width:0 дозволяє треку стискатись
   нижче суми природних ширин дітей: саме це лишає nav-links його власний
   overflow-x:auto робочим, а не форсує ввесь топбар ширшим за viewport. */
.sv-topbar-left {
    grid-column: 1;
    display: flex;
    align-items: center;
    gap: var(--sv-s5);
    min-width: 0;
}

.sv-topbar .sv-logo {
    flex-shrink: 0;
    font-weight: 800;
    font-size: var(--sv-fs-md);
    letter-spacing: 0.02em;
    text-decoration: none;
    color: white;
    white-space: nowrap;
}

.sv-logo-accent {
    color: var(--sv-accent);
}

.sv-topbar .sv-tagline-slot {
    /* Трек auto — рівно за шириною напису, симетрично центрований між
       двома 1fr-колонками по краях (див. .sv-topbar вище). Сама НЕ
       клікабельна: зона натиску нижче лишається впритул до тексту. */
    grid-column: 2;
    justify-self: center;
}

.sv-topbar .sv-tagline {
    /* --sv-accent тут безпечний як текст (на відміну від світлого фону,
       див. .sv-login-card .sv-tagline нижче): це той самий брендовий
       golden з брендбуку (#DCAF42), що й у бейджі ролі/активній мовній
       вкладці на цьому ж тлі.

       Це <button> (відкриває попап місії), не <span> — скидаємо всю
       браузерну "кнопковість", щоб виглядало так само, як звичайний текст.
       Жодного flex:1/stretch тут навмисно: розмір — рівно за контентом,
       зона натиску має відповідати видимому напису, а не всій обгортці. */
    white-space: nowrap;
    font-family: 'EditorsNoteHairline', Georgia, 'Times New Roman', serif;
    font-size: 0.95rem;
    font-style: italic;
    font-weight: 100;
    letter-spacing: 0.01em;
    color: var(--sv-accent);
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    cursor: pointer;
    transition: opacity var(--sv-dur-fast) var(--sv-ease);
}

.sv-topbar .sv-tagline:hover {
    opacity: 0.8;
}

.sv-nav-links {
    display: flex;
    gap: var(--sv-s1);
    font-weight: 700;
    font-size: var(--sv-fs-nav);
    /* Завжди стискуваний і скролиться сам, а не лише <768px: flex-дитина
       інакше ніколи не стискається нижче max-content нав-пунктів (типовий
       "min-size:auto" у flex), і на середніх ширинах (де є ще й центрована
       .sv-tagline) увесь топбар вилазив за viewport і тягнув горизонтальний
       скрол сторінки. */
    flex-shrink: 1;
    min-width: 0;
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.sv-nav-links::-webkit-scrollbar {
    display: none;
}

.sv-nav-links a {
    padding: var(--sv-s15) var(--sv-s3);
    border-radius: 6px;
    text-decoration: none;
    color: var(--sv-topbar-ink);
    transition: background var(--sv-dur) var(--sv-ease), color var(--sv-dur) var(--sv-ease);
}

.sv-nav-links a:hover {
    color: white;
    background: var(--sv-topbar-hover);
}

.sv-nav-links a.is-active {
    color: var(--sv-primary);
    background: var(--sv-accent);
}

/* System — шестерня в кластері користувача (не пункт рядка розділів):
   той самий вигляд, що й пункти меню на темній шапці. */
.sv-topbar-gear {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--sv-ctrl-h-sm);
    height: var(--sv-ctrl-h-sm);
    border-radius: 6px;
    color: var(--sv-topbar-ink);
    transition: background var(--sv-dur) var(--sv-ease), color var(--sv-dur) var(--sv-ease);
}

.sv-topbar-gear:hover {
    color: white;
    background: var(--sv-topbar-hover);
}

.sv-topbar-gear.is-active {
    color: var(--sv-primary);
    background: var(--sv-accent);
}

/* «Ще» — пункти, що не влізли в рядок (panel.js переносить їх сюди). Кнопка
   виглядає як ще один пункт рядка; список — як випадний список dselect:
   світла поверхня з тінню під кнопкою. */
.sv-nav-more {
    position: relative;
    flex-shrink: 0;
    font-weight: 700;
    font-size: var(--sv-fs-nav);
}

.sv-nav-more > summary {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s1);
    padding: var(--sv-s15) var(--sv-s3);
    border-radius: 6px;
    color: var(--sv-topbar-ink);
    list-style: none;
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--sv-dur) var(--sv-ease), color var(--sv-dur) var(--sv-ease);
}

.sv-nav-more > summary::-webkit-details-marker {
    display: none;
}

.sv-nav-more > summary:hover,
.sv-nav-more[open] > summary {
    color: white;
    background: var(--sv-topbar-hover);
}

.sv-nav-more.is-active > summary {
    color: var(--sv-primary);
    background: var(--sv-accent);
}

.sv-nav-more-list {
    position: absolute;
    top: calc(100% + var(--sv-s2));
    left: 0;
    z-index: var(--sv-z-sticky);
    min-width: 200px;
    display: flex;
    flex-direction: column;
    padding: var(--sv-s1);
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    box-shadow: var(--sv-shadow-md);
}

.sv-nav-more-list a {
    padding: var(--sv-s2) var(--sv-s3);
    border-radius: var(--sv-radius-sm);
    color: var(--sv-text);
    text-decoration: none;
    white-space: nowrap;
}

.sv-nav-more-list a:hover {
    background: var(--sv-bg);
}

.sv-nav-more-list a.is-active {
    background: var(--sv-tint-sage);
    color: var(--sv-on-tint);
}


/* Кластер перемикачів (тема/мова/ім'я/вийти) — на десктопі звичайний
   grid-елемент топбару в колонці 3 (симетричній лівій, .sv-topbar-left).
   На мобільному (media нижче) цей кластер узагалі не рендериться: той
   самий вміст (theme_switch/lang_switch/ім'я/вийти) живе в підвалі
   .sv-drawer — повноцінної висувної панелі, а не спливного попапу. */
.sv-user {
    grid-column: 3;
    display: flex;
    align-items: center;
    justify-self: end;
    gap: var(--sv-s3);
    font-size: 0.85rem;
    white-space: nowrap;
}

/* Тригер висувного меню — лише мобільний (сама панель — нижче). */
.sv-drawer-btn {
    display: none;
}

/* «Вийти» — кнопка у формі (POST, аудит 12.10 F17), а виглядає так само,
   як було посиланням: форма не займає місця в розкладці, у кнопки — жодних
   рамок, фону й шрифту браузера. :where дає нульову вагу, тож класи
   (.sv-logout, .sv-drawer-logout, .v2-acct-item) і далі задають колір,
   відступи й шрифт. */
.sv-logout-form {
    display: contents;
}

:where(.sv-logout-form) > button {
    appearance: none;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: inherit;
    cursor: pointer;
}

.sv-logout {
    color: var(--sv-sage);
    text-decoration: none;
    font-weight: 700;
    padding: var(--sv-s15) 10px;
    border-radius: 6px;
    transition: color var(--sv-dur) var(--sv-ease), background var(--sv-dur) var(--sv-ease);
}

.sv-logout:hover {
    color: var(--sv-accent);
    background: rgba(255, 255, 255, 0.08);
}

/* --- Перемикач мови --- */

.sv-lang {
    display: flex;
    gap: var(--sv-s05);
    background: rgba(255, 255, 255, 0.1);
    border-radius: 7px;
    padding: var(--sv-s05);
}

.sv-lang-btn {
    font-size: var(--sv-fs-xs);
    font-weight: 800;
    line-height: 1;
    letter-spacing: 0.03em;
    color: rgba(255, 255, 255, 0.7);
    text-decoration: none;
    /* inline-flex + фіксований min-height: і текстова кнопка (UA/EN), і
       іконкова (тема) отримують ОДНАКОВУ висоту, тож обидва контейнери
       .sv-lang однакові. Без цього текст жив за line-height, а іконка —
       за своїми 13px, і смужки виходили різної висоти.
       20px, а не менше: box-sizing:border-box, тож min-height включає
       padding, і 13px-іконці треба 13+3+3=19 — з 18px вона розпирала б
       кнопку на 1px вище за текстову. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 20px;
    padding: 3px var(--sv-s2);
    border-radius: 5px;
    transition: background var(--sv-dur) var(--sv-ease), color var(--sv-dur) var(--sv-ease);
}

.sv-lang-btn:hover {
    color: white;
}

.sv-lang-btn.is-active {
    background: var(--sv-accent);
    color: var(--sv-primary);
}

/* Перемикач теми — той самий сегментований контрол, лише іконки замість
   тексту (system / світла / темна). Висоту й вирівнювання бере з .sv-lang-btn
   вище (спільні для обох); тут — лише трохи вужчий горизонтальний padding
   під іконку (13px замість «UA»), той самий вертикальний → та сама висота. */
.sv-theme .sv-lang-btn {
    padding: 3px var(--sv-s15);
}

.sv-theme .sv-lang-btn:not(.is-active) {
    color: rgba(255, 255, 255, 0.65);
}

.sv-theme .sv-lang-btn svg {
    display: block;
}

/* На сторінці входу перемикач стоїть на світлому тлі */
.sv-login-lang {
    display: flex;
    justify-content: center;
    margin-top: var(--sv-s5);
}

.sv-login-lang .sv-lang {
    background: var(--sv-bg);
}

/* На сторінці входу перемикач мови — головна (і майже єдина) дія на
   телефоні: ціль дотику 44px завширшки, а не 32 за шириною «UA». */
.sv-login-lang .sv-lang-btn {
    min-width: 44px;
    justify-content: center;
    color: var(--sv-text-muted);
}

.sv-login-lang .sv-lang-btn:hover {
    color: var(--sv-text);
}

.sv-login-lang .sv-lang-btn.is-active {
    background: var(--sv-accent);
    color: var(--sv-primary);
}

/* ============ Головна ============ */

.sv-shell {
    max-width: 940px;
    margin: var(--sv-s6) auto;
    padding: 0 var(--sv-s5);
}

.sv-welcome {
    font-size: var(--sv-fs-xl);
    font-weight: 800;
    margin-bottom: var(--sv-s1);
}

.sv-welcome-sub {
    color: var(--sv-text-muted);
    margin-bottom: var(--sv-s6);
}

.sv-nav-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
    gap: var(--sv-s4);
}

.sv-nav-card {
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-lg);
    padding: var(--sv-s5);
    opacity: 0.55;
    display: block;
    text-decoration: none;
    color: inherit;
}

.sv-nav-card h3 {
    font-size: var(--sv-fs-md);
    margin-bottom: var(--sv-s1);
}

.sv-nav-card p {
    margin: 0;
    font-size: 0.87rem;
    color: var(--sv-text-muted);
}

.sv-nav-card-active {
    opacity: 1;
    border-color: transparent;
    box-shadow: var(--sv-shadow-md);
    transition: transform var(--sv-dur) var(--sv-ease), box-shadow var(--sv-dur) var(--sv-ease);
}

.sv-nav-card-active:hover {
    transform: translateY(-2px);
    box-shadow:
        0 2px 4px rgba(26, 57, 63, 0.07),
        0 10px 20px -8px rgba(26, 57, 63, 0.14),
        0 26px 50px -18px rgba(26, 57, 63, 0.16);
}

.sv-nav-card-icon {
    width: 34px;
    height: 34px;
    border-radius: 9px;
    background: var(--sv-primary);
    color: var(--sv-accent);
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: var(--sv-s3);
}


/* ============ Логін ============ */

.sv-login-page {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background:
        radial-gradient(circle at 15% 15%, rgba(151, 187, 167, 0.16), transparent 45%),
        radial-gradient(circle at 85% 80%, rgba(220, 175, 66, 0.16), transparent 45%),
        var(--sv-primary);
    padding: var(--sv-s5);
}

.sv-login-card {
    background: var(--sv-surface);
    border-radius: 18px;
    padding: var(--sv-s6) var(--sv-s6);
    text-align: center;
    width: 100%;
    max-width: 370px;
    box-shadow: var(--sv-shadow-lg);
}

.sv-login-mark {
    width: 52px;
    height: 52px;
    border-radius: 14px;
    background: var(--sv-primary);
    color: var(--sv-accent);
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto var(--sv-s4);
}

.sv-login-card .sv-logo {
    display: block;
    font-weight: 800;
    font-size: 1.25rem;
    color: var(--sv-text);
    letter-spacing: 0.02em;
}

.sv-login-card .sv-logo-accent {
    /* Той самий випадок, що .sv-login-card .sv-tagline нижче: світлий фон,
       тож --sv-warning замість --sv-accent, щоб не провалити WCAG. */
    color: var(--sv-warning);
}

.sv-login-card .sv-tagline {
    /* Тут фон світлий (--sv-surface), тож НЕ --sv-accent — той провалює
       WCAG AA як текст-на-білому (2.05:1, той самий гейт, що для .sv-conv
       і решти golden-як-текст випадків у цьому файлі). --sv-warning —
       затемнений золотий, спеціально заведений під дрібний текст на
       світлому (5.43:1) — тут той самий випадок. Контраст-число не
       залежить від ваги шрифту (WCAG рахує лише яскравість кольору), тож
       Hairline тут так само проходить AA, як і попередня системна вага. */
    display: block;
    margin-top: var(--sv-s05);
    /* На логіні тег-лайн тепер останнє перед кнопкою Google (підпис-опис
       під ним прибрано на прохання користувача) — той самий відступ, що
       раніше давав margin-bottom самого підпису (.sv-login-card p нижче). */
    margin-bottom: var(--sv-s5);
    font-family: 'EditorsNoteHairline', Georgia, 'Times New Roman', serif;
    font-size: var(--sv-fs-md);
    font-style: italic;
    font-weight: 100;
    color: var(--sv-warning);
}

.sv-login-card p {
    color: var(--sv-text-muted);
    font-size: 0.9rem;
    margin: var(--sv-s2) 0 var(--sv-s5);
}

.sv-google-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sv-s2);
    background: var(--sv-primary);
    color: white;
    text-decoration: none;
    font-weight: 700;
    font-size: 0.9rem;
    padding: var(--sv-s3) 22px;
    border-radius: 9px;
    transition: background var(--sv-dur) var(--sv-ease), transform var(--sv-dur) var(--sv-ease);
}

.sv-google-btn:hover {
    background: var(--sv-primary-dark);
    transform: translateY(-1px);
}

.sv-unauthorized-reason {
    color: var(--sv-maroon) !important;
    font-weight: 600;
}

/* Техробіти — не помилка, тож без бордового .sv-unauthorized-reason:
   спокійний заголовок і приглушене пояснення. Через .sv-login-card —
   інакше загальні відступи й колір абзаців картки перебивають ці. */
.sv-login-card .sv-maint-title {
    margin: 1.25rem 0 0.35rem;
    font-size: var(--sv-fs-md);
    font-weight: 700;
    color: var(--sv-text);
}

.sv-login-card .sv-maint-text {
    margin: 0;
    color: var(--sv-text-muted);
    line-height: 1.5;
}

/* Технічна причина помилки — дрібним, щоб не домінувала над зрозумілим текстом */
.sv-error-detail {
    font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
    font-size: 0.72rem !important;
    color: var(--sv-text-muted) !important;
    background: var(--sv-bg);
    border-radius: 6px;
    padding: var(--sv-s2);
    word-break: break-word;
    text-align: left;
    max-height: 140px;
    overflow-y: auto;
}

/* ============ Pipeline board ============ */

/* flex: 1 — дошка займає всю висоту під топбаром (.sv-app — flex-колонка
   на весь екран). Горизонтальна смуга прокрутки належить САМЕ цьому
   контейнеру й малюється по його нижньому краю; поки контейнер мав висоту
   свого вмісту, згорнуті колонки підтягували смугу під себе, майже під
   пошук. Тепер її місце — низ вікна, як на будь-якому сайті. */
.sv-board-wrap {
    flex: 1 0 auto;
    overflow-x: auto;
    padding: var(--sv-s4) var(--sv-s5);
}

/* align-items: flex-start, а не stretch: інакше кожна колонка розтягується
   на всю висоту дошки, і порожній етап малюється високою білою плитою
   з одним рядком тексту вгорі. Тепер колонка має висоту свого вмісту. */
.sv-board {
    display: flex;
    gap: var(--sv-s3);
    align-items: flex-start;
}

.sv-column {
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-lg);
    width: 252px;
    min-width: 252px;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    /* Порожня колонка все одно лишається помітною ціллю, а не тонкою смужкою */
    min-height: 108px;
    /* Обрізає кольорову смужку (.sv-column-accent) по скругленню колонки.
       Без цього її прямі кути стирчали поверх заокруглених кутів картки —
       смужка лежить абсолютно, тож власного радіуса не має. Раніше цей
       overflow стояв тут заради обмеження висоти; висоту вже не обмежуємо,
       але саме для скруглення він потрібен так само. */
    overflow: hidden;
}

/* Колонки в РЯДУ дошки (CRM і Buddy) — гнучкої ширини замість жорстких
   252px: ділять ширину екрана між собою й стискаються до 200px, перш ніж
   з'явиться горизонтальна прокрутка. Шість колонок по 252px просили
   ~1620px — на ноутбуці дошка завжди виїжджала за край. max-width — щоб на
   широкому моніторі колонка не ставала смугою на півекрана. Лише всередині
   .sv-board: пул Buddy — теж .sv-column, але стоїть у вертикальному
   контейнері, де той самий flex-базис став би висотою. */
.sv-board > .sv-column {
    flex: 1 1 252px;
    width: auto;
    min-width: 200px;
    max-width: 320px;
}

/* Кольорова смужка згори: сейдж — етапи бота, золото — потребує уваги,
   бордовий — заблоковані. Колір дублюється текстом заголовка, тож
   не є єдиним носієм інформації. */
.sv-column-accent {
    height: 3px;
    background: var(--sv-sage);
    flex-shrink: 0;
}

/* Смужка лежить усередині <summary> (див. коментар у _pipeline_board.html),
   тож виймаємо її з потоку — інакше вона стала б флекс-елементом заголовка
   і з'їла б місце поруч із назвою етапу. */
.sv-column > summary {
    position: relative;
}

.sv-column > summary > .sv-column-accent {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
}

.sv-column[data-stage="admin_review"] .sv-column-accent {
    background: var(--sv-accent);
}

.sv-column[data-stage="blocked"] .sv-column-accent {
    background: var(--sv-maroon);
}

.sv-column-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--sv-s2);
    padding: var(--sv-s3) var(--sv-s4);
    border-bottom: 1px solid var(--sv-border);
    font-weight: 800;
    font-size: var(--sv-fs-sm);
    flex-shrink: 0;
}

/* Назва тягнеться, решта тулиться праворуч — інакше space-between розкидав
   би лічильник і стрілку по різних краях. */
.sv-column-header > span:first-of-type {
    flex: 1 1 auto;
    min-width: 0;
}

/* Шеврон-індикатор згортання. Був лише в мобільному медіа-запиті, бо доти
   на десктопі колонки не згортались; тепер згортаються скрізь, тож правило
   переїхало сюди. Донизу — згорнуто, догори — розгорнуто. */
.sv-column > summary {
    cursor: pointer;
    list-style: none;
}

.sv-column > summary::-webkit-details-marker {
    display: none;
}

.sv-column > summary::after {
    content: "";
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg) translate(-2px, -2px);
    transition: transform var(--sv-dur) var(--sv-ease);
    opacity: 0.6;
}

.sv-column[open] > summary::after {
    transform: rotate(-135deg) translate(-2px, -2px);
}

/* Згорнута колонка не тримає min-height: інакше під заголовком лишалась
   порожня коробка на 108px, і виглядало це як зламаний блок, а не як
   свідомо прихований вміст. */
.sv-column:not([open]) {
    min-height: 0;
}

.sv-column-collapsed {
    display: none;
    font-weight: 600;
    font-size: 0.7rem;
    color: var(--sv-text-muted);
}

.sv-column:not([open]) .sv-column-collapsed {
    display: inline;
}

.sv-column-count {
    background: var(--sv-bg);
    color: var(--sv-text-muted);
    font-weight: 700;
    font-size: var(--sv-fs-xs);
    padding: var(--sv-s05) 9px;
    border-radius: var(--sv-radius-pill);
    flex-shrink: 0;
}

.sv-column[data-stage="admin_review"] .sv-column-count {
    background: var(--sv-accent);
    color: var(--sv-primary);
}

.sv-column-body {
    padding: var(--sv-s3);
    display: flex;
    flex-direction: column;
    gap: var(--sv-s2);
}

.sv-column-empty {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    text-align: center;
    padding: var(--sv-s3) 0;
}

.sv-card {
    background: var(--sv-surface-alt);
    border: 1px solid var(--sv-border);
    border-left: 3px solid transparent;
    border-radius: var(--sv-radius);
    padding: var(--sv-s3);
    cursor: pointer;
    text-align: left;
    width: 100%;
    font-family: inherit;
    transition: border-color var(--sv-dur) var(--sv-ease), box-shadow var(--sv-dur) var(--sv-ease), transform var(--sv-dur) var(--sv-ease);
}

/* Ховер картки — сейдж, не золото: картка веде на інший екран, вона не
   дія над даними. Те саме рішення, що й на .sv-tile:hover. */
.sv-card:hover {
    border-color: var(--sv-sage);
    border-left-color: var(--sv-accent);
    box-shadow: var(--sv-shadow-md);
    transform: translateY(-1px);
}

.sv-card.is-stalled {
    border-left-color: var(--sv-warning);
}

.sv-card-name {
    font-weight: 700;
    font-size: 0.88rem;
    margin-bottom: var(--sv-s1);
}

/* Кожен факт — окремим рядком. Раніше це був flex-рядок із переносом,
   і група/місто зливались в одну стрічку, яку важко сканувати очима. */
.sv-card-meta {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s05);
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

/* Довгі email не ріжемо трьома крапками — куратору потрібна вся адреса */
.sv-card-meta span {
    overflow-wrap: anywhere;
}

/* Не пігулка й не суцільна заливка сейджем: сейдж як фон під темним текстом
   дає 5.87:1, а світлий тінт — 11.07:1. Форма прямокутна зі скругленням 6px. */
.sv-card-badge {
    display: inline-block;
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    color: var(--sv-on-tint);
    background: var(--sv-tint-sage);
    padding: 3px var(--sv-s2);
    border-radius: var(--sv-radius-sm);
    margin-top: var(--sv-s2);
}

/* Той самий accent-колір, що й у .sv-claim-badge в модалці профілю —
   один візуальний сигнал "хтось уже працює" на дошці й усередині картки. */
.sv-card-claim {
    display: flex;
    align-items: center;
    gap: var(--sv-s1);
    color: var(--sv-accent-text);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    margin-top: var(--sv-s2);
    overflow-wrap: anywhere;
}

.sv-card-claim svg {
    flex-shrink: 0;
}

/* Тримають понад добу (core/pipeline.CLAIM_LONG_HOURS) — привід спитати
   куратора, тож бордовим, як і застряглі картки; текст і так це каже. */
.sv-card-claim.is-long {
    color: var(--sv-maroon);
}

.sv-card-time {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    margin-top: var(--sv-s2);
}

.sv-card.is-stalled .sv-card-time {
    color: var(--sv-warning);
    font-weight: 700;
}

.sv-column-other .sv-column-accent {
    background: var(--sv-maroon);
}

/* --- Біжучий рядок «STUDENTS N» поруч із пошуком --- */

/* Та сама поверхня, що й поле пошуку ліворуч (--sv-field-fill, радіус
   8px), — щоб рядок читався як частина того ж ряду, а не як банер, що
   ріже очі. Займає всю решту ширини після поля. Краї тануть маскою: без
   неї текст обривався б на рамці посеред літери. */
.sv-ticker {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    align-items: center;
    overflow: hidden;
    background: var(--sv-field-fill);
    border-radius: 8px;
    /* Той самий сірий, що в плейсхолдера пошуку «Ім'я, прізвище…» поруч
       (стандартний сірий браузера, окремого кольору в панелі немає) —
       вибір користувача 26.09.2026 із варіантів «сірий / зелений». */
    color: #757575;
    text-decoration: none;
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    white-space: nowrap;
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 32px, #000 calc(100% - 32px), transparent);
    mask-image: linear-gradient(90deg, transparent, #000 32px, #000 calc(100% - 32px), transparent);
    transition: color var(--sv-dur) var(--sv-ease);
}

.sv-ticker:hover {
    color: var(--sv-text);
}

.sv-ticker:focus-visible {
    outline: 2px solid var(--sv-focus);
    outline-offset: 2px;
}

/* Дві однакові групи підряд; доріжка їде рівно на одну групу (−50%) і
   починає знову з того самого кадру — тому стику не видно. Кожен пункт
   несе свій відступ праворуч, тож обидві групи однакові до пікселя. */
.sv-ticker-track {
    display: flex;
    width: max-content;
    animation: sv-ticker 60s linear infinite;
}

.sv-ticker-group {
    display: flex;
}

.sv-ticker-item {
    padding-right: 2.5em;
}

/* Наведення зупиняє рух: прочитати й клікнути по тому, що біжить, важко. */
.sv-ticker:hover .sv-ticker-track,
.sv-ticker:focus-visible .sv-ticker-track {
    animation-play-state: paused;
}

@keyframes sv-ticker {
    from { transform: translateX(0); }
    to { transform: translateX(-50%); }
}

/* «Зменшити рух» у системі — рядок стоїть. Постійний рух для когось не
   питання смаку, а те, від чого нудить. */
@media (prefers-reduced-motion: reduce) {
    .sv-ticker-track {
        animation: none;
    }
}

/* ============ Модалка профілю ============ */

.sv-modal-overlay {
    position: fixed;
    inset: 0;
    background: var(--sv-overlay);
    backdrop-filter: blur(2px);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: var(--sv-z-modal);
    padding: var(--sv-s4);
    animation: sv-fade var(--sv-dur) var(--sv-ease);
}

@keyframes sv-fade {
    from { opacity: 0; }
    to { opacity: 1; }
}

.sv-modal {
    background: var(--sv-surface);
    border-radius: var(--sv-radius-lg);
    width: 100%;
    max-width: 540px;
    max-height: 88vh;
    display: flex;
    flex-direction: column;
    box-shadow: var(--sv-shadow-lg);
    overflow: hidden;
}

.sv-modal-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--sv-s4);
    padding: var(--sv-s5);
    border-bottom: 1px solid var(--sv-border);
    background: var(--sv-primary);
    color: white;
    flex-shrink: 0;
}

.sv-modal-header h3 {
    font-size: 1.1rem;
}

/* Ім'я й «скопіювати посилання на картку» (Паша 20.10) — одним рядком;
   іконка без тла, тихіша за ім'я, по центру його рядка. */
.sv-modal-title-row {
    display: flex;
    align-items: center;
    gap: var(--sv-s1);
    min-width: 0;
}

.sv-modal-title-row > h3 {
    min-width: 0;
    overflow-wrap: anywhere;
}

.sv-card-link {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    padding: var(--sv-s1);
    border: 0;
    border-radius: var(--sv-radius-sm);
    background: none;
    color: inherit;
    opacity: 0.6;
    cursor: pointer;
    transition: opacity var(--sv-dur) var(--sv-ease);
}

.sv-card-link:hover,
.sv-card-link:focus-visible {
    opacity: 1;
}

.sv-modal-stage {
    font-size: 0.75rem;
    color: var(--sv-sage);
    font-weight: 700;
    margin-top: var(--sv-s05);
    /* flex, а не інлайновий текст + inline-flex бейдж поруч: той варіант
       вирівнювався по baseline інлайн-потоку, і пігулка бейджа виглядала
       "нерівно" — трохи вище/нижче за текст стадії (скарга 08.09).
       align-items:center рівняє обидва рядки по центру власної висоти. */
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sv-s2);
}

/* Бейдж "у роботі" в шапці модалки — окремий колір (accent, не sage), щоб
   не зливався зі стадією поруч і читався як окремий сигнал, а не її частина. */
.sv-claim-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s1);
    padding: 1px var(--sv-s2) 1px var(--sv-s15);
    border-radius: var(--sv-radius-pill);
    background: rgba(220, 175, 66, 0.16);
    color: var(--sv-accent-on-dark);
    font-size: 0.7rem;
}

.sv-claim-badge svg {
    flex-shrink: 0;
}

.sv-claim-badge.is-long {
    background: color-mix(in srgb, var(--sv-maroon-on-dark) 16%, transparent);
    color: var(--sv-maroon-on-dark);
}

/* «Тестовий акаунт» у шапці картки — нейтральний, не тривожний: це не
   проблема з заявкою, а пояснення, чому людини немає в числах дашборда. */
.sv-test-badge {
    display: inline-flex;
    align-items: center;
    padding: 1px var(--sv-s2);
    border-radius: var(--sv-radius-pill);
    background: var(--sv-bg);
    border: 1px solid var(--sv-border);
    color: var(--sv-text-muted);
    font-size: 0.7rem;
}

/* Перемикач тестового акаунта — тихий напис у кінці ряду дій. Два класи:
   інакше .sv-linklike (золоте посилання) перебиває колір. */
.sv-modal-actions .sv-test-toggle {
    margin-left: auto;
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

/* ID документа Firestore в шапці модалки — лише OWNER бачить (запит 09.09).
   Той самий шрифт (Manrope, успадкований), що й "статус · час" поруч —
   моно-стек виглядав як стороння технічна вставка (скарга 09.09), а не
   частина того самого рядка. Приглушений колір і трохи менший розмір
   лишають його візуально другорядним, не змагаючись із сейджевим текстом. */
.sv-doc-id {
    font-size: var(--sv-fs-xs);
    font-weight: 600;
    color: rgba(255, 255, 255, 0.55);
    user-select: all;
}

.sv-modal-close {
    background: rgba(255, 255, 255, 0.12);
    border: none;
    color: white;
    width: 28px;
    height: 28px;
    border-radius: 7px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: background var(--sv-dur) var(--sv-ease);
}

.sv-modal-close:hover {
    background: rgba(255, 255, 255, 0.24);
}

.sv-modal-body {
    padding: 0 var(--sv-s5);
    overflow-y: auto;
}

.sv-modal-section {
    padding: var(--sv-s4) 0;
    border-bottom: 1px solid var(--sv-border);
}

.sv-modal-section:last-child {
    border-bottom: none;
}

.sv-modal-section h4 {
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: var(--sv-s2);
}

.sv-modal-row {
    display: flex;
    justify-content: space-between;
    gap: var(--sv-s4);
    padding: var(--sv-s1) 0;
    font-size: 0.86rem;
}

.sv-modal-row > span {
    color: var(--sv-text-muted);
    flex-shrink: 0;
}

.sv-modal-row > b {
    text-align: right;
    font-weight: 600;
    word-break: break-word;
}

.sv-flag-warn {
    color: var(--sv-maroon);
}

.sv-ai-compare {
    display: block;
    margin-top: var(--sv-s05);
    font-size: 0.76rem;
    font-weight: 400;
    color: var(--sv-text-muted);
}

.sv-link {
    color: var(--sv-link);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.sv-link:hover {
    color: var(--sv-success);
}

/* Ім'я-посилання в таблиці буває з однієї-двох літер («Я Ї») — тоді ціль
   натискання вужча за палець і курсор. Мінімум 24px ширини (WCAG 2.5.8). */
td .sv-link {
    display: inline-block;
    min-width: 24px;
}

/* ============ Попап місії (тег-лайн у топбарі) ============ */

/* На вузьких екранах (≤480px, нижче) .sv-modal-overlay/.sv-modal самі
   стають bottom sheet (align-items:flex-end, плаский низ) — доречно для
   форм-модалок профілю/баді, але цей попап завжди має лишатись компактною
   центрованою карткою з тінню з усіх боків, як на десктопі. Два класи в
   селекторі (не лише .sv-mission-overlay/.sv-mission-modal) — вища
   специфічність за той mobile-правила, перебиває його незалежно від
   порядку в файлі. */
.sv-modal-overlay.sv-mission-overlay {
    align-items: center;
    padding: var(--sv-s4);
}

.sv-modal.sv-mission-modal {
    border-radius: var(--sv-radius-lg);
}

/* Шапка тут лише з заголовком, без .sv-modal-stage (як у профілі) — той
   самий var(--sv-s5) padding, розрахований на ДВА рядки, для одного
   виглядав непропорційно високим. Звужуємо лише тут, .sv-modal-header
   у профілі/картці баді лишається як є.

   Заголовок — по центру ВСІЄЇ шапки, а не лише простору зліва від
   хрестика: justify-content:space-between (успадкований) центрував би
   його лише в half між лого й кнопкою, візуально зсунутим ліворуч.
   position:relative тут + position:absolute на хрестику (нижче) —
   заголовок рахує центр від повної ширини, хрестик виходить із потоку
   й сидить у кутку незалежно від довжини напису. */
.sv-mission-modal .sv-modal-header {
    padding: var(--sv-s3) var(--sv-s4);
    justify-content: center;
    align-items: center;
    position: relative;
}

/* Хрестик без підкладки-пігулки — просто іконка в кутку, не кнопка-пляшка
   (як .sv-modal-close скрізь інде: той стиль тут виглядав важким для
   такого маленького попапу). position:absolute — щоб заголовок центрувався
   по всій ширині шапки (вище), а не по залишку простору поруч із кнопкою. */
.sv-mission-modal .sv-modal-close {
    position: absolute;
    right: var(--sv-s3);
    top: 50%;
    transform: translateY(-50%);
    background: none;
    width: auto;
    height: auto;
    padding: var(--sv-s05);
}

.sv-mission-modal .sv-modal-close:hover {
    background: rgba(255, 255, 255, 0.12);
    border-radius: var(--sv-radius-sm);
}

.sv-mission-body {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--sv-s4);
    padding-top: var(--sv-s4);
    padding-bottom: var(--sv-s5);
}

.sv-mission-statement,
.sv-mission-explain {
    /* text-align успадковується від .sv-mission-body (center) навмисно —
       не перезаписуємо тут. */
    font-size: 0.92rem;
    line-height: 1.5;
    color: var(--sv-text);
}

.sv-mission-explain {
    color: var(--sv-text-muted);
}

.sv-mission-tagline {
    /* --sv-warning, не --sv-accent: .sv-modal лежить на --sv-surface
       (світлій в обох темах панелі), той самий випадок, що й
       .sv-login-card .sv-tagline вище — accent-золото як текст на
       світлому провалює WCAG AA (2.05:1), warning — та сама гарнітура,
       затемнена саме під це тло (5.43:1 світла / 7.8:1 темна). */
    font-family: 'EditorsNoteHairline', Georgia, 'Times New Roman', serif;
    font-style: italic;
    font-weight: 100;
    font-size: var(--sv-fs-xl);
    color: var(--sv-warning);
}

/* ============ Дії куратора ============ */

.sv-modal-actions {
    display: flex;
    gap: var(--sv-s2);
    padding: var(--sv-s4) var(--sv-s5);
    border-top: 1px solid var(--sv-border);
    background: var(--sv-surface-alt);
    flex-shrink: 0;
    flex-wrap: wrap;
}

/* Штовхає все ПІСЛЯ себе до правого краю .sv-modal-actions, лишаючи те, що
   ДО (напр. "Видалити") — притиснутим до лівого. Клас, а не inline style:
   inline тут був би єдиним у всьому шаблонному шарі. */
.sv-modal-actions-spacer {
    flex: 1 1 auto;
}

.sv-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sv-s15);
    border: 1px solid transparent;
    border-radius: 8px;
    padding: 10px var(--sv-s4);
    font-weight: 700;
    font-size: 0.85rem;
    min-height: var(--sv-ctrl-h);
    /* .sv-btn іноді рендериться як <a> (напр. кнопка "Зв'язатися") */
    text-decoration: none;
    color: inherit;
    transition: background var(--sv-dur) var(--sv-ease), border-color var(--sv-dur) var(--sv-ease), transform var(--sv-dur) var(--sv-ease);
}

.sv-btn:hover {
    transform: translateY(-1px);
}

/* Брендбук не має окремого "зеленого" — сейдж є єдиним зеленим у палітрі,
   тож апрув-кнопки (єдине справді "зелене" в UI) уніфіковано під нього.
   Білий текст на сейджі провалює контраст (2.10:1), тож текст темно-тіловий,
   як і скрізь, де сейдж використовується як фон (5.87:1, AA). */
.sv-btn-approve {
    background: var(--sv-sage);
    color: var(--sv-primary);
}

.sv-btn-approve:hover {
    background: var(--sv-sage-hover);
}

.sv-btn-danger {
    background: var(--sv-maroon);
    color: var(--sv-strong-text);
}

.sv-btn-danger:hover {
    background: var(--sv-maroon-hover);
}

.sv-btn-ghost {
    background: transparent;
    border-color: var(--sv-border);
    color: var(--sv-text);
}

.sv-btn-ghost:hover {
    background: var(--sv-bg);
    border-color: var(--sv-text-muted);
}

/* "Взяти в роботу" — єдина кнопка, доки лід нічий (запит 02.09):
   легкий золотавий тон замість нейтрального ghost, той самий рецепт, що
   вже в .sv-claim-badge/.sv-insight (тінт --sv-tint-amber + рамка
   --sv-accent). --sv-warning як текст, а не --sv-accent/--sv-sage —
   ті провалюють контраст на світлому тлі (правило дизайн-системи). */
.sv-btn-claim {
    background: var(--sv-tint-amber);
    border-color: var(--sv-accent);
    color: var(--sv-warning);
}

.sv-btn-claim:hover {
    background: #FBEFC8;
    border-color: var(--sv-accent-hover);
}

/* Заявку веде хтось інший — замість ряду кнопок лише ця плашка (запит
   08.09: "щоб інший не міг нічого зробити"). Той самий рецепт кольору,
   що й .sv-btn-claim/.sv-claim-badge, але на всю ширину ряду дій, а не
   пігулкою — тут це єдиний вміст #sv-actions, а не позначка поруч з кнопками. */
.sv-locked-note {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sv-s15);
    background: var(--sv-tint-amber);
    border: 1px solid var(--sv-accent);
    border-radius: var(--sv-radius);
    padding: var(--sv-s3);
    color: var(--sv-warning);
    font-weight: 700;
    font-size: 0.85rem;
    text-align: center;
}

.sv-btn:disabled {
    opacity: 0.55;
    cursor: not-allowed;
    transform: none;
}

/* Крок підтвердження — незворотні дії не виконуються з одного кліку */
.sv-confirm {
    width: 100%;
    background: var(--sv-tint-amber);
    border: 1px solid var(--sv-accent);
    border-radius: var(--sv-radius);
    padding: var(--sv-s3);
}

.sv-confirm-text {
    font-size: 0.85rem;
    font-weight: 600;
    margin-bottom: var(--sv-s3);
}

.sv-confirm-note {
    font-size: 0.78rem;
    color: var(--sv-text-muted);
    font-weight: 500;
    margin-top: var(--sv-s1);
}

.sv-confirm-row {
    display: flex;
    gap: var(--sv-s2);
    flex-wrap: wrap;
}

/* Обгортка навколо .sv-confirm-row: сама форма тепер може містити ще й поле
   нотатки над рядком кнопок, тож вертикальний стек — на рівні форми, а
   горизонтальний ряд кнопок лишається як був. */
.sv-confirm-form {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s2);
}

.sv-confirm-note-label {
    font-size: 0.78rem;
    color: var(--sv-text-muted);
    font-weight: 600;
}

.sv-textarea {
    font-family: inherit;
    font-size: var(--sv-fs-base);
    padding: 9px var(--sv-s3);
    border: 1px solid transparent;
    border-radius: 8px;
    background: var(--sv-field-fill);
    color: var(--sv-text);
    width: 100%;
    /* Лише по вертикалі: горизонтальний ресайз розпирав би модалку */
    resize: vertical;
    min-height: 58px;
}

/* Фокус — глобальним :focus-visible, як і в решти полів (був
   `outline: none` із золотою рамкою, ~2:1). */

/* Форма ручної правки полів (#sv-actions) — на відміну від решти вмісту
   цього блоку сама форма може вийти високою (до 6 секцій), а .sv-modal
   має overflow:hidden і фіксовану max-height. Без власного скролу нижні
   секції й кнопки "Зберегти"/"Скасувати" просто обрізались би, без жодної
   можливості докрутити. */
.sv-field-editor {
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: var(--sv-s3);
    max-height: min(52vh, 460px);
    overflow-y: auto;
    overflow-x: hidden;
    padding-right: var(--sv-s1);
    /* Мобільне "бовтається вліво-вправо" (скарга 02.09): цей блок — друга
       вкладена скрол-зона всередині вже скролюваної .sv-modal-body, і без
       підказки браузеру, в який бік узагалі можна тягнути, тач трохи по
       діагоналі змушував Safari/Chrome смикати вміст ще й горизонтально,
       поки він розбирався з напрямком свайпу. pan-y забороняє горизонтальний
       компонент жесту одразу, overscroll-behavior гасить bounce, що інакше
       просочувався б у батьківський скрол і посилював те саме відчуття. */
    touch-action: pan-y;
    overscroll-behavior: contain;
}

.sv-field-editor-section h5 {
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: var(--sv-s2);
}

.sv-field-editor-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sv-s2) var(--sv-s3);
}

.sv-field-check-row {
    display: flex;
    align-items: center;
    min-height: var(--sv-ctrl-h);
    padding-block: calc((var(--sv-ctrl-h) - 1lh) / 2);
}

@media (max-width: 767px) {
    .sv-field-editor-grid {
        grid-template-columns: 1fr;
    }
}

/* Причина блокування — акцентована плашка, а не звичайна секція: це те,
   заради чого куратор і відкриває заблоковану картку. */
.sv-block-reason {
    background: var(--sv-tint-red);
    border-left: 3px solid var(--sv-maroon);
    border-radius: var(--sv-radius);
    padding: var(--sv-s3);
    /* .sv-modal-body не має верхнього padding (лише бічні), а власний
       padding плашки — це вже її внутрішній відступ. Без margin вона
       впиралась просто в шапку модалки. */
    margin-top: var(--sv-s4);
}

.sv-block-reason-text {
    font-size: var(--sv-fs-sm);
    /* Причину вписує людина — переноси рядків у ній значущі */
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    margin: 0 0 var(--sv-s1);
}

.sv-block-reason-text.is-muted {
    color: var(--sv-text-muted);
}

/* Світлі підкладки: на них поле має бути ТЕМНІШИМ за тло (заглиблення),
   а не світлішим за нього. Перелічуємо самі підкладки, а не контроли на
   них — див. коментар при токені вище про напрям дефолта.

   .sv-period-panel і .sv-custom-range — теж поверхні (--sv-surface), але
   живуть поза .sv-panel, тож перелічені тут же. Перша з них — той самий
   пропуск, що й попередні два: семестровий <select> у попапі періоду
   малювався заливкою кольору попапа, 1.00:1. Заміряно, що це було й до
   перевертання дефолта, тобто баг старий, а не новий. */
.sv-panel,
.sv-modal,
.sv-drawer,
.sv-period-panel,
.sv-custom-range,
.sv-qb-card {
    --sv-field-fill: var(--sv-bg);
}

.sv-select,
.sv-input {
    font-family: inherit;
    font-size: var(--sv-fs-base);
    padding: 9px var(--sv-s3);
    border: 1px solid transparent;
    border-radius: 8px;
    background: var(--sv-field-fill);
    color: var(--sv-text);
    min-height: var(--sv-ctrl-h);
    width: 100%;
}

/* Кастомна стрілка замість системної: у нативного select'а вона малювалась
   впритул до правого краю — тісно. appearance:none знімає системну, свою
   малюємо з чітким відступом від краю (14px), а не «як вийде» в браузері. */
.sv-select {
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    padding-right: 34px;
    background-image: var(--sv-select-caret);
    background-repeat: no-repeat;
    background-position: right 14px center;
    background-size: 11px 8px;
}

/* Фокус тут більше не перевизначається: раніше було `outline: none` плюс
   золота рамка, тобто клавіатурний користувач отримував індикатор із
   контрастом ~2:1 замість кільця. Тепер спрацьовує глобальне
   :focus-visible — те саме кільце, що й у решти контролів. */

/* Поле форми з видимим підписом */
.sv-field {
    display: flex;
    flex-direction: column;
    gap: 5px;
    min-width: 0;
}

.sv-field-label {
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    color: var(--sv-text-muted);
}

.sv-field-action {
    justify-content: flex-end;
}

.sv-result {
    padding: var(--sv-s4) var(--sv-s5);
    border-top: 1px solid var(--sv-border);
    font-size: 0.86rem;
    font-weight: 600;
}

.sv-result-ok {
    background: var(--sv-tint-green);
    color: var(--sv-success);
}

.sv-result-warn {
    background: var(--sv-tint-amber);
    color: var(--sv-warning);
}

.sv-result-err {
    background: var(--sv-tint-red);
    color: var(--sv-maroon);
}


/* ============ Students ============ */

.sv-students-wrap {
    display: flex;
    flex-direction: column;
    padding: var(--sv-s4) var(--sv-s5);
}

.sv-filters {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s2);
    margin-bottom: var(--sv-s3);
    flex-shrink: 0;
    /* Контекст позиціювання для попапа конструктора умов — див. коментар
       біля .sv-filters > .sv-fbuilder-panel нижче. */
    position: relative;
}

/* .sv-select зазвичай width:100% (для форм-контейнерів на всю ширину),
   але тут це змушувало auto flex-basis резолвитись у 100% контейнера —
   кожен select забирав власний рядок. Явний flex-basis виправляє це,
   не чіпаючи базовий .sv-select для інших екранів (адміни, force-stage).
   basis звужено, а grow лишається 1: на вузьких екранах фільтри однаково
   не стискаються нижче 120px, а на широких зайвий простір іде переважно
   пошуку (той росте вдвічі швидше — flex-grow:2). */
.sv-filters .sv-select,
.sv-filters .sv-dselect,
.sv-filters .sv-mselect {
    flex: 1 1 120px;
    width: auto;
}

/* Один зріст для всього рядка фільтрів і рядка сортування Students. Швидкі
   дропдауни — 40px (.sv-select), а «Умови» й обидва сортувальні контроли
   рендеряться з .sv-select-sm (34px) і стояли помітно нижче за сусідів.
   Підіймаємо саме тут — конструктор на дашборді (у .sv-panel-head-title)
   лишається 34px поруч зі своїм .sv-select-sm розрізом. */
.sv-filters .sv-fbuilder > summary,
.sv-fsort .sv-dselect > summary {
    min-height: var(--sv-ctrl-h);
    font-size: var(--sv-fs-base);
    /* Стрілку й місце під неї беремо як у повнорозмірного .sv-select:
       .sv-select-sm лишає padding-right лише 26px, а стрілка тут посунута
       на `right 14px` завширшки 11px — текст «Умови» впритул до неї. */
    padding-right: 34px;
    background-position: right 14px center;
    background-size: 11px 8px;
}

/* Кнопка згортання фільтрів існує лише на мобільному — на десктопі всі
   дропдауни поміщаються в один-два рядки й ховати їх нема потреби. */
.sv-filters-toggle {
    display: none;
}

.sv-search-box {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
    background: var(--sv-field-fill);
    border: 1px solid transparent;
    border-radius: 8px;
    padding: 0 var(--sv-s3);
    color: var(--sv-text-muted);
    min-width: 240px;
    flex: 2 1 240px;
}

/* Обгортка пошуку — не фокусована сама, кільце дістає <input> усередині,
   тож :focus-within тут має лишити видимий слід на самій коробці. Не
   золотом (2:1), а тим самим кольором, що й глобальне кільце. */
.sv-search-box:focus-within {
    border-color: var(--sv-focus);
}

.sv-search-input {
    border: none;
    outline: none;
    background: transparent;
    font-family: inherit;
    font-size: 0.85rem;
    padding: 10px 0;
    width: 100%;
    color: var(--sv-text);
}

.sv-students-count {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sv-s3);
    font-size: var(--sv-fs-sm);
    color: var(--sv-text-muted);
    margin-bottom: var(--sv-s2);
    flex-shrink: 0;
}

/* Селектор був `#sv-count`, але шаблон рендерить просто <b> без id —
   правило не спрацьовувало жодного разу. Цілимось у сам <b>. */
.sv-students-count b {
    font-weight: 700;
    color: var(--sv-text);
}

.sv-table-wrap {
    /* Базовий фолбек — hidden, а не clip: старіші браузери (Safari <16,
       Chrome <90 і подібні) взагалі не знають значення "clip" для overflow,
       і CSS-декларацію з невідомим значенням браузер повністю ІГНОРУЄ —
       властивість тоді відкочується на дефолтне visible, яке нічого не
       обрізає. Саме тому в таких браузерах кути виглядали гострими:
       прямокутний фон таблиці й липкої шапки просто малювався ПОВЕРХ
       заокругленої рамки обгортки, а не ховався за нею. hidden тут —
       гарантія коректного заокруглення в БУДЬ-ЯКОМУ браузері ціною
       sticky-шапки (нижче поясню, чому саме нею жертвуємо). */
    overflow: hidden;
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-lg);
}

/* Прогресивне покращення для браузерів, що ЗНАЮТЬ значення "clip":
   overflow:clip, на відміну від hidden/auto, сам не створює скрол-контейнер,
   тож position:sticky в шапці таблиці й далі рахує top від СТОРІНКИ, а не
   від .sv-table-wrap. З hidden (базовий фолбек вище) сторінка це вважає
   "найближчим предком зі скролом" навіть без реального скролбару — і липка
   шапка тоді просто їде разом з рештою замість лишатись під топбаром.
   @supports тут — не про сумісність узагалі, а конкретно про ЦЮ різницю:
   не всі браузери, що не тягнуть clip, старі в широкому сенсі — але жоден
   із них коректно СПОЄДНАТИ заокруглені кути й робочий sticky не вміє. */
@supports (overflow: clip) {
    .sv-table-wrap {
        overflow: clip;
    }
}

.sv-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.85rem;
}

.sv-table thead th {
    position: sticky;
    /* Раніше top:0 було відносно .sv-table-wrap (він сам гортався).
       Тепер гортається сторінка, і найближчий predok, що гортається, —
       viewport, тож top:0 наліз би точно під липкий топбар і сховався
       за ним. Зсуваємо на висоту топбару, щоб шапка таблиці лишалась
       під ним, а не за ним. */
    top: var(--sv-topbar-h);
    background: var(--sv-surface);
    text-align: left;
    padding: var(--sv-s3) var(--sv-s4);
    font-size: var(--sv-fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--sv-text-muted);
    border-bottom: 1px solid var(--sv-border);
    z-index: 1;
}

/* Заголовок-сортувальник. Кнопка, а не сам <th> з onclick: клавіатурна
   доступність і роль елемента задарма. Успадковує типографіку <th>
   (uppercase, letter-spacing, muted) — щоб сортовані й несортовані
   колонки виглядали однією шапкою, а не рядом кнопок. */
.sv-th-sort {
    padding: 0;
}

.sv-sort {
    display: flex;
    align-items: center;
    gap: 5px;
    width: 100%;
    font: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    color: inherit;
    background: none;
    border: none;
    text-align: left;
    padding: var(--sv-s3) var(--sv-s4);
    cursor: pointer;
    transition: color var(--sv-dur-fast) var(--sv-ease);
}

.sv-sort:hover {
    color: var(--sv-text);
}

.sv-th-sort.is-active .sv-sort {
    color: var(--sv-text);
    font-weight: 700;
}

.sv-table-row {
    cursor: pointer;
    transition: background var(--sv-dur-fast) var(--sv-ease);
}

/* Ховер — через фон і вставний бордюр. Для фокуса з клавіатури цього мало
   (сама лише золота смужка ~2:1 на світлому — провал SC 1.4.11), тож
   повертаємо справжній контур. outline-offset:-2px кладе його ВСЕРЕДИНІ
   рядка, тож нічим не обрізається (контейнер таблиці більше не має
   overflow — див. .sv-table-wrap). */
.sv-table-row:hover td,
.sv-table-row:focus-visible td {
    background: var(--sv-row-hover);
}

.sv-table-row:hover td:first-child,
.sv-table-row:focus-visible td:first-child {
    box-shadow: inset 3px 0 0 var(--sv-accent);
}

.sv-table-row:focus-visible {
    outline: 2px solid var(--sv-focus);
    outline-offset: -2px;
}

.sv-table-row td {
    padding: var(--sv-s3) var(--sv-s4);
    border-bottom: 1px solid var(--sv-border);
    vertical-align: middle;
    transition: background var(--sv-dur-fast) var(--sv-ease);
}

.sv-table-name {
    font-weight: 700;
}

.sv-table-sub {
    font-size: 0.76rem;
    color: var(--sv-text-muted);
}

/* Рядок під курсором. Таблиці несуть половину дашборда й бувають на
   п'ять-шість колонок — без ведучої смуги око губить рядок десь між
   «Ім'я» і «Хто взяв у роботу».

   Два винятки в селекторі, обидва обов'язкові:
     * .sv-table-row (список Students) має власний, сильніший ховер — він
       клікабельний, там ще й акцентна смужка зліва;
     * .sv-heat (теплокарта) виключена ЗА СПЕЦИФІКАЦІЄЮ, а не за смаком:
       цей селектор важить (0,3,3), а .sv-heat-cell зі своєю заливкою —
       (0,1,0), тож рядковий фон просто стер би частку когорти з клітинок
       під курсором. Теплокарта дістає свою підсвітку нижче.
   Заливка, а не рамка: рамка змістила б рядок на пікселі. */
.sv-table:not(.sv-heat) tbody tr:not(.sv-table-row) > td,
.sv-table:not(.sv-heat) tbody tr:not(.sv-table-row) > th {
    transition: background var(--sv-dur-fast) var(--sv-ease);
}

.sv-table:not(.sv-heat) tbody tr:not(.sv-table-row):hover > td,
.sv-table:not(.sv-heat) tbody tr:not(.sv-table-row):hover > th {
    background: var(--sv-row-hover);
}

/* Таблиці всередині .sv-alert стоять не на поверхні панелі, а на
   --sv-tint-red, і загальний ховер вирізав би рядок із попередження
   майже білим. Свій тон, темніший за той самий тінт: рядок підсвічується
   ВСЕРЕДИНІ алерта, а не замість нього. */
.sv-alert .sv-table tbody tr:hover > td,
.sv-alert .sv-table tbody tr:hover > th {
    background: color-mix(in srgb, var(--sv-maroon) 12%, var(--sv-tint-red));
}

/* Ховер теплокарти живе при самій теплокарті, нижче — там, де її заливка. */

/* --- Числові колонки --- */

/* Дві речі, без яких колонка чисел не сканується поглядом:
   • праворуч — щоб розряди шикувались під розрядами, і «1228» проти «99»
     читалось як різниця на порядок, а не як різна довжина слова;
   • tabular-nums — пропорційні цифри дають «1» вужчою за «8», через що
     навіть рівний правий край усередині розсипається.
   Для великих ОДИНОЧНИХ чисел (значення плитки, headline) tabular-nums
   навпаки шкідливий — він робить їх розтягнутими, тож туди не поширюється.

   Модифікатор на ТАБЛИЦІ, а не клас на кожній клітинці: у цих таблицях
   перша колонка — підпис, решта числові, і одна правка на таблицю не
   розійдеться з розміткою так, як розійшлися б півтора десятки окремих
   класів. .sv-num — для змішаних таблиць, де числова лише частина колонок. */
.sv-table-num th:not(:first-child),
.sv-table-num td:not(:first-child),
.sv-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.sv-table-badge {
    display: inline-block;
    font-size: 0.7rem;
    font-weight: 700;
    padding: calc(var(--sv-pill-pad-top) + 1px) 10px calc(var(--sv-pill-pad-bottom) + 1px);
    border-radius: var(--sv-radius-pill);
    white-space: nowrap;
}

.sv-badge-neutral {
    background: var(--sv-sage);
    color: var(--sv-primary);
}

.sv-badge-accent {
    background: var(--sv-accent);
    color: var(--sv-primary);
}

/* «Нічого не відбувається» (закрита форма) — сірим, без кольору ролі:
   шавлієвий .sv-badge-neutral на темному читався майже як «Відкрита». */
.sv-badge-muted {
    background: var(--sv-surface-alt);
    color: var(--sv-text-muted);
    box-shadow: inset 0 0 0 1px var(--sv-border);
}

/* Попередження, а не помилка: «ще не налаштовано», «зверни увагу». Бурштиновий
   фон із затемненим золотим текстом — той самий --sv-warning, що вже заведений
   під дрібний текст на світлому (провалює AA лише чистий accent). */
.sv-badge-warning {
    background: var(--sv-tint-amber);
    color: var(--sv-warning);
}

.sv-badge-success {
    background: var(--sv-success);
    color: var(--sv-strong-text);
}

.sv-badge-danger {
    background: var(--sv-maroon);
    color: var(--sv-strong-text);
}

.sv-empty-row {
    text-align: center;
    color: var(--sv-text-muted);
    padding: var(--sv-s6) 0 !important;
}

/* ============ Dashboard ============ */

.sv-dash {
    max-width: 1800px;
    margin: 0 auto;
    padding: var(--sv-s4) var(--sv-s5) var(--sv-s6);
}

/* --- Панель керування: вкладки + період --- */

/* Три колонки: вкладки у СЕРЕДНІЙ лишаються рівно по центру сторінки,
   хоч праворуч і з'являється «Експорт» (на «Стані школи»), а на інших
   вкладках правої кнопки немає зовсім — порожні 1fr з обох боків
   тримають ту саму позицію, і вкладки не стрибають при перемиканні. */
.sv-dash-bar {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
    gap: var(--sv-s3);
    margin-bottom: var(--sv-s4);
}

.sv-dash-bar > .sv-tabs {
    grid-column: 2;
    min-width: 0;
}

/* Коробка того ж роду, що й рядок вкладок (поверхня, рамка, радіус), —
   це кнопка рівня сторінки, а не тиха дія в шапці панелі, як .sv-export. */
.sv-dash-export {
    grid-column: 3;
    justify-self: end;
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s15);
    padding: 9px var(--sv-s4);
    /* Без рамки (прохання користувача 26.09.2026): лише поверхня. */
    border: 0;
    border-radius: 10px;
    background: var(--sv-surface);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    font-weight: 700;
    text-decoration: none;
    white-space: nowrap;
    transition: color var(--sv-dur) var(--sv-ease), border-color var(--sv-dur) var(--sv-ease);
}

.sv-dash-export:hover {
    color: var(--sv-text);
}

.sv-dash-export svg {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
}

/* Вузький екран: вкладки на всю ширину, «Експорт» — окремим рядком
   праворуч під ними, а не стиснутою третьою колонкою. */
@media (max-width: 760px) {
    .sv-dash-bar {
        grid-template-columns: minmax(0, 1fr);
    }

    .sv-dash-bar > .sv-tabs,
    .sv-dash-export {
        grid-column: 1;
    }
}

.sv-tabs {
    display: flex;
    gap: var(--sv-s1);
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: 10px;
    padding: 3px;
}

.sv-tab {
    padding: 7px var(--sv-s4);
    border-radius: 7px;
    font-size: 0.85rem;
    font-weight: 700;
    text-decoration: none;
    color: var(--sv-text-muted);
    transition: background var(--sv-dur) var(--sv-ease), color var(--sv-dur) var(--sv-ease);
}

.sv-tab:hover {
    color: var(--sv-text);
}

.sv-tab.is-active {
    background: var(--sv-primary);
    color: white;
}

/* --- Ряд вкладок, що не вміщається --- */

/* Вісім вкладок замість двох: на вузькому екрані ряд поїде за край, і
   перенесення на другий рядок тут гірше за прокрутку — розірваний
   сегментований контрол перестає читатись як один перемикач.
   scrollbar-width:none, бо смуга під контролом висотою 36px займала б
   третину його висоти; край, що обрізається, сам показує, що є ще. */
.sv-tabs-scroll {
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}

.sv-tabs-scroll::-webkit-scrollbar {
    display: none;
}

.sv-tabs-scroll .sv-tab {
    white-space: nowrap;
    flex: 0 0 auto;
}

/* --- Зведений контрол періоду --- */

.sv-period-label {
    font-size: var(--sv-fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 700;
    color: var(--sv-text-muted);
    margin-right: var(--sv-s1);
}

/* «Інший період» — те саме тіло, що в чіпів поруч: візуально це просто
   четвертий варіант вибору, і лише при кліку зʼясовується, що під ним ще
   два способи. Розкривачка нативна (<details>), тож працює без JS. */
.sv-period-more {
    position: relative;
}

.sv-period-more > summary {
    list-style: none;
    cursor: pointer;
}

.sv-period-more > summary::-webkit-details-marker {
    display: none;
}

.sv-period-panel {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: var(--sv-z-sticky);
    display: flex;
    flex-direction: column;
    gap: var(--sv-s4);
    min-width: 260px;
    padding: var(--sv-s4);
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    box-shadow: var(--sv-shadow-md);
    text-align: left;
}

/* Попап росте вліво біля правого краю екрана, інакше вилазить за viewport. */
@media (max-width: 640px) {
    .sv-period-panel {
        left: auto;
        right: 0;
    }
}

.sv-period-group {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s2);
    /* Позиціювання, щоб групи взагалі брали участь у порядку накладання:
       без нього вони малюються у звичайному потоці, і випадний список
       семестрів (absolute усередині першої групи) опинявся ПІД полями
       діапазону з другої — пункти було видно, але не прочитати. */
    position: relative;
}

/* Список семестрів розкривається вниз і за визначенням накриває групу
   діапазону під собою. Це нормально для випадного списку — ненормально
   було протилежне. */
.sv-period-group:first-child {
    z-index: 2;
}

.sv-period-group-title {
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    color: var(--sv-text-muted);
}

/* Усередині попапа діапазон — звичайний стовпчик полів, а не пігулка:
   пігулковий вигляд був потрібен, поки форма стояла в одному ряду з
   чіпсами. Тут вона вже в своєму контейнері. */
.sv-period-panel .sv-custom-range {
    flex-wrap: wrap;
    border: none;
    background: none;
    padding: 0;
    border-radius: 0;
}

/* --- Свіжість даних --- */

/* Праворуч у шапці зони, тією ж вагою, що й підпис зони: це не попередження
   і не дія, по яку сюди прийшли, — це виноска, яку читають, лише коли число
   здалося дивним. */
.sv-fresh {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
    margin-left: auto;
}

.sv-fresh-time {
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    white-space: nowrap;
    /* Підказка є, але й без неї рядок самодостатній — title лише додає
       деталь про кеш, а не несе основний сенс. */
    cursor: help;
}

/* Коротке повідомлення вгорі по центру області вмісту (не всього вікна),
   напр. ліміт /dashboard/refresh. Якір нульової висоти прилипає до верху
   main, тож поява й зникнення нічого не зсувають; само гасне за ~4,5 с. */
.sv-top-toast-anchor {
    position: sticky;
    top: var(--sv-s3);
    z-index: 30;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    height: 0;
    pointer-events: none;
}

.sv-top-toast {
    max-width: calc(100% - 2 * var(--sv-s4));
    margin: 0;
    padding: var(--sv-s2) var(--sv-s4);
    border-radius: var(--sv-radius-pill);
    background: var(--sv-primary);
    color: var(--sv-on-primary);
    font-size: var(--sv-fs-sm);
    font-weight: 600;
    box-shadow: var(--sv-shadow-md);
    text-align: center;
    animation: sv-top-toast var(--sv-dur-toast) var(--sv-ease) both;
}

@keyframes sv-top-toast {
    0% {
        opacity: 0;
        transform: translateY(-6px);
    }

    6%,
    88% {
        opacity: 1;
        transform: none;
    }

    100% {
        opacity: 0;
        visibility: hidden;
    }
}

@media (prefers-reduced-motion: reduce) {
    .sv-top-toast {
        animation-name: sv-top-toast-still;
    }

    @keyframes sv-top-toast-still {
        0%,
        92% {
            opacity: 1;
        }

        100% {
            opacity: 0;
            visibility: hidden;
        }
    }
}

.sv-fresh-btn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font: inherit;
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    padding: var(--sv-s1) 10px;
    border: 1px solid transparent;
    border-radius: var(--sv-radius-sm);
    background: none;
    color: var(--sv-text-muted);
    cursor: pointer;
    transition: background var(--sv-dur) var(--sv-ease), color var(--sv-dur) var(--sv-ease);
}

.sv-fresh-btn:hover {
    background: var(--sv-surface);
    color: var(--sv-text);
}

/* Шапка зони: заголовок по центру ВСІЄЇ шапки, свіжість даних — праворуч.
   Сітка «1fr auto 1fr», а не flex: у flex група заголовка була елементом
   по вмісту й ставала при лівому краю, а text-align центрував текст лише
   всередині цієї вузької коробки — тобто заголовок виглядав притиснутим
   ліворуч. Дві рівні бокові колонки тримають середню рівно по центру
   незалежно від того, яка завширшки форма свіжості й чи вона взагалі є. */
.sv-zone-head {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: start;
    gap: var(--sv-s3);
}

.sv-zone-head > div {
    grid-column: 2;
}

.sv-zone-head > .sv-fresh {
    grid-column: 3;
    justify-self: end;
}

/* На вузькому екрані три колонки не вміщаються: бокові дорівнюють ширині
   форми, і середній не лишається нічого. Складаємо в стовпчик — так само,
   як це робив flex-wrap до сітки. */
@media (max-width: 899px) {
    .sv-zone-head {
        grid-template-columns: minmax(0, 1fr);
    }

    .sv-zone-head > div,
    .sv-zone-head > .sv-fresh {
        grid-column: 1;
    }

    /* justify-self лише на формі. На групі заголовка він означав би
       «ширина по вмісту», а в ній лежить розкривачка зі сталими 68ch —
       елемент ставав 531px у колонці 327px і вилазив у ОБИДВА боки
       (заміряно: left -78, right 453 при вікні 375). Групі потрібен
       stretch — текст у ній центрує text-align шапки. */
    .sv-zone-head > .sv-fresh {
        justify-self: center;
    }
}

/* --- Дельта до попереднього періоду --- */

/* Пігулка з тінтом, а не кольоровий текст: дельта стоїть поруч із
   заголовком панелі, і кольоровий текст там читався б як ще один
   заголовок. Колір означає «добре/погано», а НЕ «плюс/мінус» — клас
   приходить із Python (isGood), бо для метрики на кшталт «Застрягли»
   той самий плюс мав би бути червоним. */
.sv-delta {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    font-size: var(--sv-fs-xs);
    padding: var(--sv-s05) var(--sv-s2);
    border-radius: var(--sv-radius-pill);
    white-space: nowrap;
}

.sv-delta b {
    font-weight: 800;
    /* Знак поруч зі значенням — друге, незалежне від кольору кодування
       напрямку: на чорно-білому друку й при повній ахроматопсії пігулка
       лишається читабельною. */
    font-variant-numeric: tabular-nums;
}

.sv-delta.is-good {
    background: var(--sv-tint-green);
    color: var(--sv-success);
}

.sv-delta.is-bad {
    background: var(--sv-tint-red);
    color: var(--sv-maroon);
}

.sv-delta.is-flat,
.sv-delta.is-muted {
    background: var(--sv-bg);
    color: var(--sv-text-muted);
}

/* --- Шкала за барами воронки --- */

.sv-fchart {
    position: relative;
}

/* Нульова вісь — вертикальна лінія, від якої ростуть бари. Прямий лівий
   край барів і так був віссю, але невидимою: вона проявлялась лише як
   спільний початок прямокутників. Тепер це лінія, що сходиться з
   горизонтальною шкалою внизу в кут, тож «0%» має і позначку, і рейку.

   Стоїть на left:-1px, ПОЗА барами, а не під ними: під непрозорими барами
   лінія проглядала б лише у 2px проміжках і читалась би пунктиром. Низ
   доходить рівно до лінії шкали (її margin-top + 1px рамки).

   Колір — той самий --sv-border, що й у горизонтальної шкали: дві осі
   одного кута. Була --sv-text-muted, і на темній темі вертикаль світила
   помітно яскравіше за горизонталь, ніби це дві різні речі. */
.sv-fchart::before {
    content: "";
    position: absolute;
    left: -1px;
    top: -4px;
    bottom: calc(-1 * var(--sv-s2) - 1px);
    width: 1px;
    background: var(--sv-border);
    pointer-events: none;
}

/* Вісь ПІД барами, а не сітка за ними. Спершу тут були наскрізні лінії на
   25/50/75/100%, і на рендері стало видно, чому це не працює саме для
   воронки: ширина бара відносна до першого кроку, тож усі бари займають
   78–100% і суцільно перекривають сітку — з чотирьох ліній читалась одна.
   Вісь під ними не конкурує з даними й виконує ту саму роботу: кінець бара
   зіставляється з поділкою знизу. */
.sv-fscale {
    position: relative;
    height: 14px;
    /* Було -4px: вісь підтягувалась до барів і фізично лягала на останній —
       хайрлайн проходив усередині бара, а підписи торкались його краю.
       Тепер вісь стоїть ПІД графіком, на тому ж кроці, що й проміжок між
       самими барами (.sv-fchart gap), тож читається як його продовження,
       а не як частина останнього кроку. */
    margin-top: var(--sv-s2);
    /* Лінія осі — хайрлайн, суцільний (пунктир додав би ритм, якого в даних
       немає), на крок від поверхні. */
    border-top: 1px solid var(--sv-border);
    pointer-events: none;
}

/* Поділка — короткий штрих ВНИЗ від осі: він позначає позицію, не
   перекреслюючи графік. */
.sv-fscale-line {
    position: absolute;
    top: 0;
    width: 1px;
    height: 4px;
    background: var(--sv-border);
}

.sv-fscale-label {
    position: absolute;
    top: 5px;
    left: 0;
    transform: translateX(-50%);
    font-size: var(--sv-fs-xs);   /* було 0.65rem = 10.4px */
    font-style: normal;
    line-height: 1;
    white-space: nowrap;
    color: var(--sv-text-muted);
    font-variant-numeric: tabular-nums;
}

/* Крайні поділки інакше вилазять за панель: підпис центрований по штриху,
   а штрихи стоять рівно на 0% і 100% ширини. Притискаємо їх усередину. */
.sv-fscale-line:first-child .sv-fscale-label {
    transform: none;
}

/* Штрих 0% — продовження нульової осі (.sv-fchart::before), тож стоїть на
   тому самому пікселі й тим самим кольором, а не на пів кроку поруч. */
.sv-fscale-line:first-child {
    margin-left: -1px;
    background: var(--sv-border);
}

/* Дельта до попереднього семестру — під віссю, на місці колишнього
   жовтого попередження про незрілу когорту. */
.sv-fdelta {
    margin-top: var(--sv-s3);
}

.sv-fscale-line:last-child .sv-fscale-label {
    transform: translateX(-100%);
}

.sv-period {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--sv-s2);
    margin-bottom: var(--sv-s4);
}

.sv-chip {
    display: inline-flex;
    align-items: center;
    min-height: var(--sv-ctrl-h-chip);
    font-size: 0.78rem;
    font-weight: 700;
    padding: 0 14px;
    border-radius: var(--sv-radius-sm);
    /* Вага елемента кодує СТАН, а не тип: у ряду, де вибір завжди один,
       тіло (рамку й заливку) дістає лише активний чіп нижче, а решта —
       просто написи. Шість однакових коробок поспіль читались як шість
       рівнозначних кнопок, хоча п'ять із них — «не вибрано».

       Бордер прозорий, а не відсутній: .is-active фарбує його в
       --sv-primary, і за border:none перемикання періоду зсувало б увесь
       ряд на 2px. Геометрія та сама, видно лише вибраний. */
    border: 1px solid transparent;
    background: none;
    color: var(--sv-text-muted);
    text-decoration: none;
    transition: background var(--sv-dur) var(--sv-ease), color var(--sv-dur) var(--sv-ease);
}

/* Ховер — ледь помітна підкладка (чіпи лежать на --sv-bg, тож поверхня
   тут читається), а не золота рамка: золото зарезервоване за дією, що
   змінює дані, і на кожному чіпі під курсором воно конкурувало з активним.
   Клавіатурний фокус не залежить від цього правила — його малює глобальне
   :focus-visible вгорі файлу. */
.sv-chip:hover {
    background: var(--sv-surface);
    color: var(--sv-text);
}

/* Обраний період — темно-зелений, як активна вкладка CRM/Help Centre */
.sv-chip.is-active {
    background: var(--sv-primary);
    border-color: var(--sv-primary);
    color: white;
}

/* Дропдаун семестру стоїть у ТОМУ Ж ряду, що й чіпси періоду та пігулка
   «свого діапазону», але рендериться з .sv-select-sm (34px) — і був на 2px
   нижчим за сусідів. Тут висота задається рядом, а не загальним розміром
   контрола: той самий прийом, що вже вживає .sv-filters .sv-fbuilder вище
   (там .sv-select-sm навпаки підіймається до 40px під сусідні фільтри). */
.sv-period .sv-select-sm {
    min-height: var(--sv-ctrl-h-chip);
}

/* --- Свій діапазон дат --- */

/* Власна поверхня всередині ряду періоду: ряд лежить на тлі сторінки, а
   тут поверхня вже є, тож поля знову заглиблюються — інакше біле поле
   стояло б на білій пігулці. Сам токен — у списку підкладок вище. */
.sv-custom-range {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    min-height: var(--sv-ctrl-h-chip);
    gap: var(--sv-s2);
    padding: 3px var(--sv-s2) 3px var(--sv-s3);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-sm);
    background: var(--sv-surface);
}

.sv-custom-range.is-active {
    border-color: var(--sv-primary);
    box-shadow: 0 0 0 1px var(--sv-primary);
}

.sv-range-label {
    font-size: 0.75rem;
    font-weight: 700;
    color: var(--sv-text-muted);
    white-space: nowrap;
}

.sv-range-field {
    display: flex;
    align-items: center;
    gap: 5px;
}

.sv-range-field > span {
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    color: var(--sv-text-muted);
}

.sv-date {
    font-family: inherit;
    font-size: 0.76rem;
    min-height: 28px;
    padding: 0 var(--sv-s2);
    border: 1px solid transparent;
    border-radius: var(--sv-radius-sm);
    /* Поле пізнається за формою й курсором, а не за рамкою. Через токен, а
       не жорстким кольором: .sv-custom-range нижче — це власна поверхня
       всередині ряду періоду, тож вона повертає заливку до «заглиблення»,
       хоча сам ряд задає протилежне. */
    background: var(--sv-field-fill);
    color: var(--sv-text);
}

/* Було `outline: none` із золотою рамкою замість кільця — тобто єдиним
   індикатором фокуса лишався колір бордера (~2:1 на світлому, провал
   SC 1.4.11), а клавіатурний користувач втрачав кільце. Тепер правило
   просто не втручається: фокус малює глобальне :focus-visible вгорі
   файлу, тим самим кільцем, що й у решти контролів. */

.sv-apply {
    font-family: inherit;
    font-size: 0.76rem;
    font-weight: 800;
    min-height: 28px;
    padding: 0 var(--sv-s4);
    border: none;
    border-radius: var(--sv-radius-sm);
    background: var(--sv-accent);
    color: var(--sv-primary);
    cursor: pointer;
    transition: background var(--sv-dur) var(--sv-ease);
}

.sv-apply:hover {
    background: var(--sv-accent-hover);
}

/* --- Експорт --- */

.sv-export {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    gap: 5px;
    font-size: var(--sv-fs-xs);
    font-weight: 800;
    letter-spacing: 0.03em;
    padding: 5px 10px;
    border-radius: 6px;
    /* Тиха дія, а не кнопка. Обведена кнопка стояла в шапці КОЖНОЇ панелі —
       на дашборді їх одразу кілька, і кожна претендувала на увагу нарівні
       із заголовком панелі, під яким вона стоїть. Експорт — не те, заради
       чого сюди прийшли.

       Підпис лишається (ховається лише на вузьких екранах, де його
       заміняє aria-label): прибрати ще й слово означало б icon-only
       кнопку, яку видно, але не прочитати. Знімаємо коробку, не сенс. */
    border: 1px solid transparent;
    color: var(--sv-text-muted);
    text-decoration: none;
    white-space: nowrap;
    transition: color var(--sv-dur) var(--sv-ease), background var(--sv-dur) var(--sv-ease);
}

/* Підкладка кольору сторінки: панель під кнопкою — --sv-surface, тож саме
   --sv-bg читається на ній як заглиблення (в обох темах). Золота заливка
   звідси прибрана — вона зарезервована за дією, що змінює дані. */
.sv-export:hover {
    background: var(--sv-bg);
    color: var(--sv-text);
}

/* Вибір формату експорту (CSV/Excel) — нативний <details>, без JS.
   summary перевикористовує вигляд .sv-export один-в-один. */
.sv-export-menu {
    position: relative;
}

.sv-export-menu > summary {
    list-style: none;
    cursor: pointer;
}

.sv-export-menu > summary::-webkit-details-marker {
    display: none;
}

/* Відкрите меню — це і є «активний» стан, тож за нашим правилом воно
   єдине з усього ряду дістає тіло. Але тіло тихе: та сама підкладка, що й
   на ховері, плюс повний колір тексту. Золото тут не потрібне — меню
   нічого не змінює, воно лише розкрите. */
.sv-export-menu[open] > summary {
    background: var(--sv-bg);
    color: var(--sv-text);
}

.sv-export-menu-panel {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: var(--sv-z-sticky);
    display: flex;
    flex-direction: column;
    width: 260px;
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: 8px;
    box-shadow: var(--sv-shadow-md);
    padding: var(--sv-s2);
}

/* Формати — в один ряд під списком полів: це фінальна дія, а не ще один
   пункт вибору, тож візуально відокремлюємо їх від чекбоксів. */
.sv-export-formats {
    display: flex;
    gap: var(--sv-s2);
    margin-top: var(--sv-s2);
    padding-top: var(--sv-s2);
    border-top: 1px solid var(--sv-border);
}

.sv-export-formats button {
    flex: 1;
}

/* Обгортка дошки: чіпи умов і лічильник лежать усередині свопабельного
   вузла, а сітку колонок малює вкладений .sv-board (див. коментар у
   _pipeline_board.html). */
/* Рядок-маячок довантаження. Видимий навмисно: порожній рядок висотою 1px
   браузер може не встигнути показати до підміни, і гортання виглядало б як
   ривок. Текст із залишком заодно каже, що список ще не закінчився. */
.sv-more-cell {
    text-align: center;
    padding: var(--sv-s4);
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

.sv-board-root {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* Кнопки форматів (CSV/Excel) — не будь-яка кнопка в панелі: напис-дія
   «Всі/Зняти» поруч зі списком полів має лишатись пласким підкресленим
   текстом (.sv-linklike), а не отримувати цей padding/фон. */
.sv-export-formats button {
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 700;
    text-align: left;
    background: none;
    border: none;
    border-radius: 5px;
    padding: 7px 10px;
    color: var(--sv-text);
    cursor: pointer;
    transition: background var(--sv-dur-fast) var(--sv-ease);
}

.sv-export-formats button:hover {
    background: var(--sv-bg);
}

/* --- Кастомний dropdown (заміна нативного <select> там, де хочемо власний
   заокруглений список опцій — сам popup нативного select CSS не бере
   в жодному браузері, тож стилізувати можна лише через <details>). Прототип
   на одному фільтрі (Students → стадія), перш ніж поширювати на решту. --- */
.sv-dselect {
    position: relative;
}

.sv-dselect > summary {
    list-style: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* Рядок ФІКСОВАНИЙ, а не успадкований 1.5 від body: 14px * 1.5 = 21px, і
       з padding 9+9 та рамкою 1+1 контрол виходив 40.6px — на 0.6px вищий за
       сусідній .sv-input, у якого min-height:40px справді визначає висоту.
       З 20px висота дорівнює min-height рівно: 20+18+2 = 40 у звичайного
       розміру і 20+12+2 = 34 у .sv-select-sm, тобто точно як .sv-btn-sm,
       поруч з якою він і стоїть. */
    line-height: 20px;
}

.sv-dselect > summary::-webkit-details-marker {
    display: none;
}

/* Розкритий попап — «активний» стан, і за правилом ваги він єдиний у ряду
   має виділятись. Але золото зарезервоване за дією, що змінює дані:
   розкрити список — не дія. Беремо колір фокус-кільця, той самий, що вже
   означає «ось тут ти зараз» по всій панелі. */
.sv-dselect[open] > summary {
    border-color: var(--sv-focus);
}

/* min-width:0 обов'язковий: у flex-контейнері елемент за замовчуванням не
   стискається нижче ширини свого вмісту (min-width:auto), тож у вузьких
   контекстах (роль у рядку адміна, «Лід» у модалці) текст залазив у зону
   padding-right і опинявся ПІД намальованою там стрілкою. З min-width:0
   вмикається обіцяний нижче ellipsis. */
.sv-dselect-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Фіксована ширина, а не left:0;right:0 (яка тягнулась рівно під ширину
   тригера) — тригер стискається до поточного вибраного значення (напр.
   "Лід"), і список підлаштовувався під нього ж, обрізаючи довші назви
   опцій у два рядки. 220px — комфортна ширина незалежно від того, що
   зараз вибрано. */
.sv-dselect-list {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    width: 220px;
    z-index: var(--sv-z-sticky);
    display: flex;
    flex-direction: column;
    max-height: 280px;
    overflow-y: auto;
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    box-shadow: var(--sv-shadow-md);
    padding: var(--sv-s1);
    /* details[open] не має проміжного стану, тож transition тут безсилий —
       плавність через animation на вже вставленому елементі. */
    animation: sv-dselect-in var(--sv-dur) var(--sv-ease);
}

/* Закритий список ховаємо ЯВНО. Самого `details` для цього не досить:
   абсолютно спозиціонований нащадок вислизає з механізму, яким браузер
   ховає вміст закритого details — і панель 220×280 лишається в потоці
   з opacity:0, visibility:visible та pointer-events:auto. Візуально її
   не видно, але вона перехоплює тапи по тексту під собою (виміряно на
   дашборді: накривала рядки таблиці сегментів). */
.sv-dselect:not([open]) .sv-dselect-list {
    display: none;
}

/* Те саме для меню експорту — воно збудоване на тому ж патерні <details> */
.sv-export-menu:not([open]) .sv-export-menu-panel {
    display: none;
}

/* Рядок фільтрів на дошці. Свій клас поряд із .sv-filters: там сітка
   розрахована на п'ять дропдаунів, тут — лише пошук і кнопка умов, і без
   цього пошук розтягувався на всю ширину екрана. */
.sv-pfilters {
    margin-bottom: var(--sv-s3);
}

.sv-pfilters .sv-search-box {
    flex: 0 1 320px;
}

.sv-board-count {
    margin: 0 0 var(--sv-s3);
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

/* Рядок над колонками CRM: лічильник пошуку ліворуч, свіжість дошки праворуч.
   Лічильник тут без власного нижнього відступу — його дає сам рядок. */
.sv-board-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s1) var(--sv-s3);
    margin-bottom: var(--sv-s3);
}

.sv-board-meta .sv-board-count {
    margin: 0;
}

.sv-board-fresh {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s1);
    margin: 0 0 0 auto;
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

.sv-board-refresh {
    display: inline-grid;
    place-items: center;
    min-width: 24px;
    min-height: 24px;
    padding: 0;
    border: 0;
    border-radius: var(--sv-radius-sm);
    background: none;
    color: inherit;
    cursor: pointer;
}

.sv-board-refresh:hover {
    color: var(--sv-text);
    background: var(--sv-surface-alt);
}

.sv-board-refresh.htmx-request svg,
.sv-board-root.htmx-request .sv-board-refresh svg {
    animation: sv-spin var(--sv-dur-spin) linear infinite;
}

@keyframes sv-spin {
    to { transform: rotate(360deg); }
}

/* Технічні властивості в картці учня — блок для owner/admin. Приглушений
   фон і менший шрифт: це службова довідка (ідентифікатори, доступи до
   чатів), а не дані про людину, і вона не має конкурувати за увагу з
   контактами чи причиною блокування вище. */
.sv-tech-section {
    background: var(--sv-bg);
    border-radius: var(--sv-radius);
    padding: var(--sv-s3);
}

.sv-tech-section .sv-modal-row {
    font-size: var(--sv-fs-sm);
}

/* Доступ до чату — стан, який найчастіше й перевіряють, відкриваючи цей
   блок. Кольором, а не лише словом: "Ні" серед десятка рядків тексту
   інакше не видно. Обидва стани підписані словом, тож колір тут дублює
   інформацію, а не несе її один. */
.sv-flag-on {
    color: var(--sv-success);
}

.sv-flag-off {
    color: var(--sv-maroon);
}

/* --- Збережені підбірки --- */
.sv-views {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
    margin-bottom: var(--sv-s3);
}

.sv-views-label {
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    color: var(--sv-text-muted);
    white-space: nowrap;
}

.sv-views-empty {
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

/* Спільні підбірки — сейдж-заливка (як чіпи умов), особисті — лише контур:
   з одного погляду видно, що побачить решта команди, а що ні. Колір тут
   несе сенс, тож дублюємо його підказкою в title (доступність). */
.sv-view-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s05);
    /* Прозорий, а не відсутній: .is-active і .is-shared нижче фарбують
       рамку, і за border:none перемикання підбірки зсувало б увесь ряд. */
    border: 1px solid transparent;
    border-radius: var(--sv-radius-pill);
    padding-right: var(--sv-s1);
    font-size: var(--sv-fs-xs);
    max-width: 100%;
}

.sv-view-chip.is-shared {
    background: var(--sv-tint-sage);
}

/* Застосована зараз підбірка — єдина з тілом, решта просто написи. Досі
   активного стану не було взагалі: усі чіпи виглядали однаково, і з екрана
   не можна було зрозуміти, дивишся ти на збережену підбірку чи на руками
   зібраний фільтр. Це вже не вага заради вигляду, а відсутня інформація.

   Заливка перекриває .is-shared навмисно: «яку підбірку застосовано»
   важливіше за «спільна вона чи особиста», а друге лишається в title. */
.sv-view-chip.is-active {
    background: var(--sv-view-active-bg);
    border-color: var(--sv-view-active-bg);
}

.sv-view-chip.is-active > a,
.sv-view-chip.is-active .sv-view-del {
    color: var(--sv-view-active-ink);
}

.sv-view-chip > a {
    padding: 5px var(--sv-s1) 5px var(--sv-s3);
    color: var(--sv-text);
    text-decoration: none;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 240px;
}

.sv-view-chip:hover {
    border-color: var(--sv-sage);
}

.sv-view-del {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;    /* ціль натискання WCAG 2.5.8 (було 20px) */
    height: 24px;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--sv-text-muted);
    cursor: pointer;
    padding: 0;
    flex-shrink: 0;
}

.sv-view-del:hover {
    color: var(--sv-maroon);
}

.sv-viewsave {
    position: relative;
}

.sv-viewsave > summary {
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    width: auto;
}

.sv-viewsave > summary::-webkit-details-marker {
    display: none;
}

.sv-viewsave[open] > summary {
    border-color: var(--sv-focus);
}

.sv-viewsave-panel {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: var(--sv-z-sticky);
    width: min(320px, calc(100vw - 2 * var(--sv-s4)));
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    box-shadow: var(--sv-shadow-md);
    padding: var(--sv-s3);
    animation: sv-dselect-in var(--sv-dur) var(--sv-ease);
}

/* Той самий обов'язковий рядок, що для решти <details>-попапів: закритий
   details не ховає абсолютно спозиціонованого нащадка сам, і невидима
   панель перехоплювала б тапи по таблиці під собою. */
.sv-viewsave:not([open]) .sv-viewsave-panel {
    display: none;
}

.sv-viewsave-panel form {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s2);
}

.sv-viewsave-panel input[type="checkbox"] {
    accent-color: var(--sv-check);
    flex-shrink: 0;
}

/* --- Список полів для вибору (експорт-меню, збереження підбірки) ---
   Обгортки-<details> .sv-colpicker тут немає: пікер колонок як окремий
   попап прибрали, лишились тільки список і його рядки, вкладені в
   .sv-export-menu-panel / .sv-viewsave-panel.
   Список ЗАВЖДИ гортається: усередині немає вкладених дропдаунів. */
.sv-colpicker-list {
    display: flex;
    flex-direction: column;
    max-height: 320px;
    overflow-y: auto;
    margin-bottom: var(--sv-s2);
}

/* Підпис + напис-дія «Всі/Зняти» в одному рядку: дія притиснута праворуч,
   не забирає окремого рядка чи висоти. */
.sv-cols-note {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sv-s2);
}

/* <button>, що виглядає як підкреслений напис — для дій, яким кнопка завелика
   (напр. «Всі» у виборі колонок експорту). Семантично лишається кнопкою. */
.sv-linklike {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: var(--sv-link);
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
    white-space: nowrap;
}

.sv-linklike:hover {
    color: var(--sv-success);
}

/* Нативний чекбокс лишається нативним (доступність, тач, клавіатура) —
   міняємо лише колір галочки з дефолтного синього на брендовий: у пікері
   їх одразу півсотні, і чужий синій там єдина пляма не з палітри. */
.sv-colpicker-item input[type="checkbox"] {
    accent-color: var(--sv-check);
    flex-shrink: 0;
}

.sv-colpicker-item {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
    padding: var(--sv-s15) var(--sv-s2);
    border-radius: 6px;
    font-size: var(--sv-fs-sm);
    cursor: pointer;
}

.sv-colpicker-item:hover {
    background: var(--sv-bg);
}

/* --- Конструктор умов ------------------------------------------------
   Той самий патерн, що й меню експорту: нативний <details> + абсолютна
   панель. Свій клас у <summary>, а НЕ .sv-filters-toggle: той ловиться
   глобальним обробником у base.html і згортав би весь рядок фільтрів. */
.sv-fbuilder {
    position: relative;
}

/* У рядку фільтрів Students/дошки попап конструктора кріпимо до ПРАВОГО
   краю всього рядка (.sv-filters), а не до кнопки «Умови»: коли рядок
   переноситься (≈768–1200px), «Умови» опиняється зліва, і панель шириною
   560px з `right:0` відносно кнопки вилазила за лівий край екрана.
   Прив'язка до рядка тримає її на екрані за будь-якої ширини ≥ ~592px;
   вужче вже діє мобільне правило (панель у потоці). */
.sv-filters > .sv-fbuilder {
    position: static;
}

.sv-filters > .sv-fbuilder > .sv-fbuilder-panel {
    top: calc(100% + 6px);
    right: 0;
}

.sv-fbuilder > summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: var(--sv-s15);
    width: auto;
}

.sv-fbuilder > summary::-webkit-details-marker {
    display: none;
}

.sv-fbuilder[open] > summary {
    border-color: var(--sv-focus);
}

.sv-fbuilder-panel {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: var(--sv-z-sticky);
    width: min(560px, calc(100vw - 2 * var(--sv-s4)));
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    box-shadow: var(--sv-shadow-md);
    padding: var(--sv-s3);
    animation: sv-dselect-in var(--sv-dur) var(--sv-ease);
    /* НЕ overflow:auto. Усередині лежать .sv-dselect, чиї списки опцій —
       абсолютно спозиціоновані нащадки заввишки до 280px; будь-яке
       обрізання тут повторило б баг зі списками в модалці (див. коментар
       біля вимірювання місця в base.html), тільки для головного контрола
       конструктора — вибору колонки. Натомість обмежуємо кількість умов
       (filter_engine.MAX_CONDITIONS), щоб панель не переростала екран. */
    overflow: visible;
}

/* Закритий стан ховаємо ЯВНО: абсолютно спозиціонований нащадок вислизає
   з механізму, яким браузер ховає вміст закритого details, і невидима
   панель перехоплювала б тапи по таблиці під собою. */
.sv-fbuilder:not([open]) .sv-fbuilder-panel {
    display: none;
}

.sv-fbuilder-note {
    margin: 0 0 var(--sv-s2);
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

.sv-fcond {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
    margin-bottom: var(--sv-s2);
}

/* min-width:0 обов'язковий: у flex-контейнері елемент не стискається
   нижче ширини свого вмісту, і довга назва поля розпирала б рядок. */
.sv-fcond > .sv-dselect {
    flex: 1 1 130px;
    min-width: 0;
}

.sv-fcond-value {
    flex: 1 1 130px;
    min-width: 0;
    font-size: 0.78rem;
    padding: var(--sv-s15) 10px;
    min-height: var(--sv-ctrl-h-sm);
}

.sv-fcond-del {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-sm);
    background: var(--sv-surface);
    color: var(--sv-text-muted);
    cursor: pointer;
    transition: color var(--sv-dur) var(--sv-ease), border-color var(--sv-dur) var(--sv-ease);
}

.sv-fcond-del:hover {
    color: var(--sv-maroon);
    border-color: var(--sv-maroon);
}

.sv-fbuilder-foot {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s2);
    align-items: center;
    margin-top: var(--sv-s3);
    padding-top: var(--sv-s3);
    border-top: 1px solid var(--sv-border);
}

/* "Застосувати" — праворуч, окремо від "додати"/"очистити": це дія, що
   завершує роботу з попапом, а не ще одна правка всередині нього. */
.sv-fapply {
    margin-left: auto;
}

/* Сортування за полем, якого немає серед колонок таблиці. Стоїть поруч із
   таблицею, а не в попапі умов: попап рендериться лише при повному
   завантаженні сторінки, а цей блок приїжджає з кожною новою вибіркою —
   тобто його стан ніколи не розходиться з тим, що показано. */
.sv-fsort {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
    margin-bottom: var(--sv-s3);
}

.sv-fsort-label {
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    color: var(--sv-text-muted);
    white-space: nowrap;
}

.sv-fsort .sv-dselect {
    flex: 0 1 190px;
    min-width: 0;
}

/* --- Чіпи застосованих умов над таблицею --- */

.sv-fchips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s2);
    margin-bottom: var(--sv-s3);
}

.sv-fchip {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s15);
    font-size: var(--sv-fs-xs);
    color: var(--sv-text);
    /* Тінт АБО рамка, не обидва: чіп умови існує лише тоді, коли умова
       застосована, тож стан тут один, і кодувати його двічі — зайва лінія
       в ряду, де їх і так багато. Лишаємо заливку: вона читається з
       відстані, з якої рамку в 1px уже не видно. */
    background: var(--sv-tint-sage);
    border: 1px solid transparent;
    border-radius: var(--sv-radius-pill);
    padding: var(--sv-s1) var(--sv-s15) var(--sv-s1) var(--sv-s3);
    overflow-wrap: anywhere;
}

.sv-fchip-x {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--sv-text-muted);
    cursor: pointer;
    padding: 0;
}

.sv-fchip-x:hover {
    color: var(--sv-maroon);
}

@keyframes sv-dselect-in {
    from { opacity: 0; transform: translateY(-4px); }
    to { opacity: 1; transform: translateY(0); }
}

/* Коли знизу немає місця (JS у base.html вимірює це при відкритті) —
   список розгортається над тригером, а не під ним. */
.sv-dselect-up .sv-dselect-list {
    top: auto;
    bottom: calc(100% + 6px);
    animation-name: sv-dselect-in-up;
}

@keyframes sv-dselect-in-up {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: translateY(0); }
}

.sv-dselect-list button {
    /* Не стискатись у колонці з max-height: довгий пункт у два рядки інакше
       сплющувався, і пункти налазили один на одного (Паша 20.10, умова). */
    flex-shrink: 0;
    font-family: inherit;
    font-size: var(--sv-fs-base);
    text-align: left;
    background: none;
    border: none;
    border-radius: 6px;
    padding: var(--sv-s2) 10px;
    color: var(--sv-text);
    cursor: pointer;
    transition: background var(--sv-dur-fast) var(--sv-ease);
}

.sv-dselect-list button:hover {
    background: var(--sv-bg);
}

.sv-dselect-list button.is-selected {
    background: var(--sv-tint-sage);
    color: var(--sv-on-tint);
    font-weight: 700;
}


/* --- Мультивибір (_mselect.html) -----------------------------------------
   Той самий <details>, що й у dselect, і свідомо той самий тригер (.sv-select,
   40px) — контрол мультивибору стоїть у сітці поруч зі звичайними полями
   вводу, і будь-яка власна висота одразу читалась би як «провалився».
   Випадаючий список повністю успадковано (.sv-dselect-list), різні лише
   опції: там кнопки, тут <label> з реальним чекбоксом усередині. */
.sv-mselect {
    position: relative;
}

.sv-mselect > summary {
    list-style: none;
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
    /* Та сама фіксація рядка, що й у .sv-dselect вище, і з тієї ж причини */
    line-height: 20px;
}

.sv-mselect > summary::-webkit-details-marker {
    display: none;
}

.sv-mselect[open] > summary {
    border-color: var(--sv-focus);
}

/* Обов'язкове, і рівно з тієї ж причини, що й у .sv-dselect (див. вище):
   абсолютно спозиціонований нащадок не ховається механізмом закритого
   details і мовчки перехоплював би тапи по тому, що під ним. Самі чекбокси
   при цьому лишаються у формі й серіалізуються — display:none на це не
   впливає, і саме так значення переживає згортання списку. */
.sv-mselect:not([open]) .sv-dselect-list {
    display: none;
}

/* Підпис розтягуємо на весь вільний простір, лічильник тримаємо праворуч —
   але ДО зони стрілки (padding-right:34px у .sv-select), інакше вони
   накладаються одне на одне. */
.sv-mselect .sv-dselect-label {
    flex: 1;
}

.sv-dselect-label.is-placeholder {
    color: var(--sv-text-muted);
    font-weight: 500;
}

.sv-mselect-count {
    flex-shrink: 0;
    min-width: 20px;
    /* Власний рядок 18px і нульові вертикальні padding'и: лічильник — єдиний
       вміст summary, вищий за її рядок (20px), і з ним увесь контрол виростав
       до 41.6px, тобто рівно та розбіжність із сусідніми полями, заради якої
       все це й робиться. 18px лишає бейджу поля до країв і не розпирає рядок. */
    line-height: 18px;
    padding: 0 var(--sv-s15);
    border-radius: 999px;
    background: var(--sv-tint-sage);
    color: var(--sv-on-tint);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    text-align: center;
}

.sv-mselect-option {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
    padding: var(--sv-s2) 10px;
    border-radius: 6px;
    font-size: var(--sv-fs-base);
    color: var(--sv-text);
    cursor: pointer;
    transition: background var(--sv-dur-fast) var(--sv-ease);
}

.sv-mselect-option:hover {
    background: var(--sv-bg);
}

.sv-mselect-option.is-selected {
    background: var(--sv-tint-sage);
    color: var(--sv-on-tint);
    font-weight: 700;
}

/* Пошук у мультивиборі (search=…) — вгорі списку й лишається при прокрутці. */
.sv-mselect-search {
    position: sticky;
    top: 0;
    z-index: 1;
    flex-shrink: 0;
    margin-bottom: var(--sv-s1);
}

.sv-mselect-option[hidden] {
    display: none;
}

/* accent-color, як уже зроблено для чекбоксів у підбірках і виборі колонок
   (.sv-viewsave-panel / .sv-colpicker-item нижче): без нього галочка
   малюється системним фіолетовим, якого в палітрі немає взагалі.
   .sv-field-check-row — булеві поля в тій самій формі редагування, і
   стояти поруч із мультивибором різними кольорами вони не мають. */
.sv-mselect-option input,
.sv-field-check-row input[type="checkbox"] {
    accent-color: var(--sv-check);
}

.sv-mselect-option input {
    flex-shrink: 0;
    margin: 0;
}


.sv-select-sm {
    font-size: 0.78rem;
    padding: var(--sv-s15) 26px var(--sv-s15) 10px;
    /* Та сама 34px, що й .sv-btn-sm поруч (force-stage row) — з min-height:0
       список був помітно нижчим за кнопку "Перевести", хоч стоять в одному
       ряду. */
    min-height: var(--sv-ctrl-h-sm);
    background-position: right 10px center;
    background-size: 10px 7px;
}

.sv-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr));
    gap: var(--sv-s3);
    margin-bottom: var(--sv-s5);
}

/* Тайл — тепер <a>, а не <div>: весь блок клікабельний (веде на
   Students чи CRM), а не лише малий текстовий лінк усередині. */
.sv-tile {
    display: block;
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    /* Смуга лишається 3px У ВСІХ плиток, але в нейтральних вона кольору
       рамки, тобто непомітна. Прибрати її зовсім не можна: плитки — сусіди
       в грід-ряду, і різна товщина верхнього краю збила б їхні значення з
       однієї лінії на 2px.

       Була сейджева на кожній без винятку — і саме тому .is-danger на
       «Застрягли» не виділявся: він конкурував із чотирма іншими смугами
       замість контрастувати з ними. Тепер колір на смузі означає рівно
       одне: цій плитці є що сказати. */
    border-top: 3px solid var(--sv-border);
    border-radius: var(--sv-radius);
    padding: var(--sv-s4);
    color: inherit;
    text-decoration: none;
    transition: border-color var(--sv-dur) var(--sv-ease), box-shadow var(--sv-dur) var(--sv-ease), transform var(--sv-dur) var(--sv-ease);
}

/* Ховер — тінь, підйом і сейдж по контуру. Золото звідси прибрано: плитка
   веде на інший екран, вона не дія над даними. Сейдж якраз звільнився —
   він більше не працює смугою за замовчуванням. */
.sv-tile:hover {
    border-color: var(--sv-sage);
    box-shadow: var(--sv-shadow-md);
    transform: translateY(-1px);
}

/* Гучна плитка — РІВНО одна на екран: та, з якої треба почати. Смуга
   кольору каже «тут є що подивитись», заливка каже «почни звідси», і
   різниця між ними зникає, щойно залитих стає дві. Тому клас ставиться
   в шаблоні на найтерміновішу ненульову плитку, а не на кожну проблемну.

   Коли починати нема з чого, залитої плитки немає взагалі — і це теж
   відповідь, а не порожній стан. */
/* Той самий тінт, що й у .sv-alert, а не суцільний бордовий. Суцільний
   кричав голосніше за саме попередження під ним — виходило, що плитка
   тривожніша за текст, який пояснює, у чому річ. Тепер обидва однакові
   на вигляд, бо це одна й та сама тривога, показана двічі: числом і
   рядком.

   Не рівно --sv-tint-red, а з 8% маруну — і це заміряно, не на смак. Чистий
   тінт у ТЕМНІЙ темі має ту саму яскравість, що й поверхня плитки (#3A1E1F
   проти #15292E, різниця 0.0002, тобто 1.00:1): заливка розрізнялася б лише
   відтінком і не робила б нічого. Домішка маруну сама йде в потрібний бік у
   кожній темі, бо сам токен у них протилежний — у світлій він темний
   (#912627, тінт трохи глибшає), у темній світлий (#E88A8B, тінт трохи
   світлішає). Заміряно після: 1.30:1 у світлій і 1.15:1 у темній проти
   сусідньої плитки.

   Рамка по ВСЬОМУ периметру --sv-maroon (а не лише зверху, як у .is-danger):
   гучна плитка мусить лишитись «почни звідси», і рамка тримає цю різницю
   без крику. Текст — звичайний --sv-text: на світлому тінті він і читається
   краще за білий, і не потребує окремого кольору для підпису. */
.sv-tile.is-loud {
    background: color-mix(in srgb, var(--sv-maroon) 8%, var(--sv-tint-red));
    border-color: var(--sv-maroon);
    color: var(--sv-text);
}

.sv-tile.is-loud .sv-tile-label {
    color: var(--sv-maroon);
}

.sv-tile.is-loud:hover {
    border-color: var(--sv-maroon-hover);
    background: color-mix(in srgb, var(--sv-maroon) 16%, var(--sv-tint-red));
}

.sv-tile.is-accent {
    border-top-color: var(--sv-accent);
}

.sv-tile.is-danger {
    border-top-color: var(--sv-maroon);
}

/* Ховер вище задає border-color всім чотирьом краям — інакше він стирав би
   сенсовий колір смуги саме тоді, коли на плитку націлились. Вища
   специфічність (0,3,0 проти 0,2,0) повертає його на місце. */
.sv-tile.is-accent:hover {
    border-top-color: var(--sv-accent);
}

.sv-tile.is-danger:hover {
    border-top-color: var(--sv-maroon);
}

.sv-tile-label {
    font-size: 0.75rem;
    color: var(--sv-text-muted);
    font-weight: 600;
}

.sv-tile-value {
    font-size: 1.9rem;
    font-weight: 800;
    line-height: 1.2;
}

/* Рівно дві колонки, а не auto-fit: у кожній зоні по дві панелі, і на
   широкому екрані auto-fit створював третю порожню доріжку. */
.sv-dash-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sv-s4);
    align-items: start;
}

/* minmax(0,1fr) на треках не рятує від content-based min-width самого
   грід-елемента: таблиця всередині (навіть у прокручуваному .sv-table-wrap)
   штовхала панель — і разом з нею весь грід — ширше за viewport. */
.sv-dash-grid > * {
    min-width: 0;
}

/* Значення з одиницею («13.4 год») поруч зі звичайною плиткою читалось як
   менш важливе: 1.35rem проти 1.9rem — це видима різниця в статусі, а
   статус у них однаковий. Підняли до 1.7rem: усе ще трохи менше, бо тут
   рядок довший і на вузькій плитці 1.9 переносилось би, але різниця вже
   читається як «довший текст», а не як «другорядне число». */
.sv-tile-value-sm {
    font-size: 1.7rem;
}

/* --- Спарклайн у плитці --- */

/* Висота фіксована, ширина тягнеться: це не графік, який міряють, а форма,
   яку впізнають. Плитки без спарклайна (Студентів, Застрягли — там ряд
   недостовірний, див. build_tile_trends) лишаються нижчими, і це нормально:
   grid вирівнює їх по верхньому краю, тож числа стоять на одній лінії. */
.sv-spark {
    display: block;
    width: 100%;
    height: 20px;
    margin-top: var(--sv-s2);
    overflow: visible;
}

/* Лінія приглушена, остання точка в акценті — «ось де ми зараз». Той самий
   поділ, що й у великому тренді: контекст тихий, поточне значення помітне. */
.sv-spark-line {
    fill: none;
    stroke: var(--sv-spark-line);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    /* viewBox розтягується non-uniform (preserveAspectRatio="none"), і разом
       із ним розтяглася б товщина штриха — по горизонталі в кілька разів
       сильніше, ніж по вертикалі. Лінія виглядала б клиновидною. */
    vector-effect: non-scaling-stroke;
}

.sv-spark-end {
    fill: var(--sv-primary);
    /* Кільце кольору поверхні, щоб точка читалась там, де налазить на лінію
       (кругла форма не спотворюється, бо радіус у користувацьких одиницях). */
    stroke: var(--sv-surface);
    stroke-width: 1.5;
    vector-effect: non-scaling-stroke;
}

/* На кольорових плитках кінцева точка бере колір їхнього стану — інакше
   темно-зелена крапка на бордовій плитці читається як чужа. */
.sv-tile.is-danger .sv-spark-end { fill: var(--sv-maroon); }
.sv-tile.is-accent .sv-spark-end { fill: var(--sv-warning); }

/* --- Таблиця конверсії (джерела + сегменти) --- */

.sv-table-compact td,
.sv-table-compact th {
    padding: var(--sv-s2) var(--sv-s3);
    font-size: 0.82rem;
}

.sv-seg-name {
    font-weight: 700;
    margin-bottom: 3px;
}

.sv-seg-track {
    height: 4px;
    background: var(--sv-bg);
    border-radius: var(--sv-radius-pill);
    overflow: hidden;
    max-width: 180px;
}

.sv-seg-bar {
    height: 100%;
    background: var(--sv-sage);
    border-radius: var(--sv-radius-pill);
}

.sv-conv {
    font-weight: 800;
    padding: var(--sv-s05) var(--sv-s2);
    border-radius: var(--sv-radius-pill);
    background: var(--sv-bg);
    white-space: nowrap;
}

.sv-conv.is-good {
    background: var(--sv-sage);
    color: var(--sv-primary);
}

.sv-conv.is-bad {
    background: var(--sv-tint-red);
    color: var(--sv-maroon);
}

.sv-panel {
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-lg);
    padding: var(--sv-s5);
}

.sv-panel-wide {
    grid-column: 1 / -1;
}

/* Ціль якоря #segments (форма зміни розрізу нижче) — без цього липкий
   топбар (мобільний, position:sticky) перекривав би заголовок панелі
   рівно в момент переходу по фрагменту. */
#segments {
    scroll-margin-top: calc(var(--sv-topbar-h) + var(--sv-s3));
}

/* Сітка, а не flex із переносом. При flex-wrap браузер воліє ПЕРЕНЕСТИ
   елемент, а не стиснути: заголовок-питання «Скільки заявників проходять
   кожен етап…» займав 546px із 590px доступних, і кнопці експорту лишалось
   44px — вона з'їжджала на власний рядок і ставала при лівому краю, тобто
   виглядала як окремий контрол, а не як дія цієї панелі.

   Сітка ставить те саме питання інакше: перша колонка гнучка й може
   стиснутись (minmax(0,1fr) знімає автоматичний мінімум по вмісту), друга
   по вмісту кнопки. Заголовок переноситься ВСЕРЕДИНІ себе, кнопка лишається
   праворуч на тому ж рядку.

   Це рівно те, що вже робив мобільний оверрайд нижче — тепер поведінка
   одна на всіх ширинах, і нема двох різних розкладок, які треба тримати
   в голові одночасно. Шапки без кнопки просто займають першу колонку. */
.sv-panel-head {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    column-gap: var(--sv-s3);
    row-gap: var(--sv-s1);
    margin-bottom: var(--sv-s2);
}

.sv-panel-head h2 {
    font-size: var(--sv-fs-md);
}

/* Заголовок панелі + короткий підпис (напр. "дані з ...") в одному рядку,
   поруч, а не одне під одним — .sv-panel-head сам розводить цю групу
   і кнопку експорту по краях (space-between). */
.sv-panel-head-title {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--sv-s2);
    /* min-width:0 (без flex-grow!) дозволяє групі стискатись і переноситись
       УСЕРЕДИНІ себе на вузьких екранах, замість виштовхувати кнопку
       експорту на новий рядок. flex-grow:1 тут навмисно НЕ ставимо: він
       змушував групу "жадібно" займати весь рядок і сам же виштовхував
       кнопку — саме той баг, який це правило мало виправити. */
    min-width: 0;
}

/* Єдиний заголовок панелі, у якому лежать КОНТРОЛИ, а не підпис, — форма
   сегментації. Там baseline не годиться: він рівняє за базовою лінією
   тексту, а в «Група» і «Умови» різний line-height (20px проти 18.72px),
   тож дві однаково високі 34px-пігулки розходились по вертикалі на 3px.
   Заміряно: top 588 проти 585. Центр не залежить від типографіки всередині,
   тож будь-який наступний контрол у цьому ряду стане рівно. */
form.sv-panel-head-title {
    align-items: center;
}


/* margin явно занулено: без цього <p> брав дефолтний браузерний margin
   (~1em) і, через margin-collapse із margin-bottom заголовка вище,
   проміжок ставав більшим за задуманий і залежав від контексту. Клас
   використовується і як <p>-підпис одразу під .sv-panel-head (там
   потрібен відступ знизу — див. правило нижче), і як <span>-компаньйон
   заголовка всередині .sv-panel-head-title (там відступ не потрібен). */
.sv-panel-note {
    margin: 0;
    font-size: 0.75rem;
    color: var(--sv-text-muted);
}

.sv-panel-head + .sv-panel-note {
    margin-bottom: var(--sv-s3);
}

/* Корінь «прилипань»: підпис-абзац має margin:0 (він же живе й у щільних
   шапках), тож коли він стояв ПІСЛЯ блоку кнопок, списку чи форми — або
   блок стояв після нього, — між ними не було нічого (/admins «Вікове
   переведення…» впритул до кнопок синку; підпис над таблицею на дашборді).
   Раніше це латали по місцях; тепер правило одне: між абзацом-підписом і
   сусіднім блоком — s3. Правило лише ДОДАЄ повітря там, де його не було:
   пару «шапка панелі + підпис» не чіпає (там свій ритм вище), варіанти
   підпису зі своїм відступом виключені, а блок після підпису отримує s3
   через :where — будь-який його власний margin сильніший. */
:is(div:not(.sv-panel-head), form, ul, ol, table, section, fieldset, details)
    + p.sv-panel-note:not(.sv-panel-foot, .sv-board-count, .sv-buddy-pool-more),
:where(p.sv-panel-note) + :where(div, form, ul, ol, table, section, fieldset, details) {
    margin-top: var(--sv-s3);
}

.sv-panel-foot {
    margin: var(--sv-s4) 0 0;
    line-height: 1.5;
}

.sv-panel-empty {
    color: var(--sv-text-muted);
    font-size: 0.86rem;
    margin: 0;
    line-height: 1.6;
}

.sv-panel-empty code {
    background: var(--sv-bg);
    padding: 1px 5px;
    border-radius: 4px;
    font-size: 0.85em;
}

/* --- Синк зі SchoolToday (секція "Адміни") --- */

.sv-sync-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s3);
    margin-top: var(--sv-s3);
}

.sv-sync-group + .sv-sync-group {
    margin-top: var(--sv-s5);
    padding-top: var(--sv-s5);
    border-top: 1px solid var(--sv-border);
}

.sv-sync-result:empty {
    display: none;
}

.sv-sync-result {
    margin-top: var(--sv-s4);
}

.sv-sync-result-head {
    display: flex;
    align-items: center;
    gap: var(--sv-s3);
    margin-bottom: var(--sv-s2);
}

.sv-sync-error-note {
    font-size: var(--sv-fs-sm);
    color: var(--sv-text-muted);
}

/* Той самий монопростірний стек, що й .sv-error-detail, але більший блок:
   тут ціла консоль скрипта, а не один рядок причини помилки. */
.sv-sync-output {
    font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
    font-size: 0.78rem;
    line-height: 1.5;
    color: var(--sv-text);
    background: var(--sv-bg);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    padding: var(--sv-s4);
    max-height: 480px;
    overflow: auto;
    white-space: pre-wrap;
    word-break: break-word;
    margin: 0;
}

.sv-funnel-track {
    height: 8px;
    background: var(--sv-bg);
    border-radius: var(--sv-radius-pill);
    overflow: hidden;
}

/* --- Швидкість воронки: головне число, частини цілого, кроки на осі --- */

/* 1. Одне велике число — медіана всієї анкети. Той самий гротеск, що й
   усе довкола (ніколи не декоративний шрифт на фігурі), пропорційні
   цифри: це окреме число, а не колонка. */
.sv-velo-hero {
    display: flex;
    align-items: center;
    gap: var(--sv-s4);
    margin-top: var(--sv-s2);
}

.sv-velo-figure {
    font-size: 3rem;
    font-weight: 800;
    line-height: 1;
    letter-spacing: -0.02em;
    white-space: nowrap;
}

.sv-velo-caption {
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    line-height: 1.5;
}

/* Легенда — одна над усіма рядками (у рядків спільні кошики), текстовими
   токенами; колір несе лише квадратик. */
.sv-velo-legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s1) var(--sv-s4);
    margin: var(--sv-s5) 0 var(--sv-s3);
    padding: 0;
    list-style: none;
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

.sv-velo-legend li {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s15);
}

.sv-velo-key {
    width: 10px;
    height: 10px;
    border-radius: 3px;
    flex-shrink: 0;
}

/* Рядки: назва ліворуч, смуга праворуч. Назви в одній колонці однакової
   ширини, тож усі смуги починаються з однієї вертикалі й порівнюються. */
.sv-velo-mix {
    display: grid;
    grid-template-columns: minmax(150px, max-content) minmax(0, 1fr);
    gap: var(--sv-s3) var(--sv-s4);
    align-items: center;
}

.sv-velo-mrow {
    display: contents;
}

.sv-velo-mlabel {
    font-size: var(--sv-fs-sm);
    color: var(--sv-text-muted);
}

/* Уся анкета — підсумковий рядок: напівжирна назва й більша смуга, під
   ним повітря, щоб кроки читались як його розклад. */
.sv-velo-mrow.is-whole .sv-velo-mlabel {
    font-weight: 700;
    color: var(--sv-text);
}

.sv-velo-mrow.is-whole .sv-velo-bar {
    height: 26px;
    margin-bottom: var(--sv-s2);
}

.sv-velo-bar {
    display: flex;
    gap: var(--sv-s05);
    height: 20px;
}

/* Сегменти стикуються через розрив 2px кольору поверхні, а не рамку;
   4px заокруглення — лише на крайніх торцях смуги. */
.sv-velo-seg {
    min-width: 3px;
    flex-basis: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: visible;
    font-size: 0.7rem;
    font-weight: 700;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.sv-velo-seg:first-child { border-radius: 4px 0 0 4px; }
.sv-velo-seg:last-child { border-radius: 0 4px 4px 0; }
.sv-velo-seg:only-child { border-radius: 4px; }

.sv-velo-seg.is-b10,
.sv-velo-key.is-b10 { background: var(--sv-velo-1); color: var(--sv-velo-ink-dark); }
.sv-velo-seg.is-b60,
.sv-velo-key.is-b60 { background: var(--sv-velo-2); color: var(--sv-velo-ink-dark); }
.sv-velo-seg.is-b1440,
.sv-velo-key.is-b1440 { background: var(--sv-velo-3); color: var(--sv-velo-ink-light); }
.sv-velo-seg.is-blong,
.sv-velo-key.is-blong { background: var(--sv-velo-4); color: var(--sv-velo-ink-light); }
.sv-velo-seg.is-pending,
.sv-velo-key.is-pending { background: var(--sv-velo-pending); color: color-mix(in srgb, var(--sv-text-muted) 85%, var(--sv-text)); }

/* Вузький екран: назва над смугою, смуга на всю ширину. */
@media (max-width: 767px) {
    .sv-velo-mix {
        grid-template-columns: minmax(0, 1fr);
        row-gap: var(--sv-s1);
    }

    .sv-velo-bar {
        margin-bottom: var(--sv-s2);
    }

    .sv-velo-figure {
        font-size: 2.4rem;
    }
}

/* --- Funnel-chart (Конверсія воронки) --- */
/* Справжній chart-бар, а не тонка смужка-прогресбар: прямокутник (не пігулка),
   достатньо високий, щоб текст читався ВСЕРЕДИНІ нього — назва стадії зліва,
   к-сть+відсоток справа, обидва притиснуті до країв того самого бару. Ширина
   бару й далі відносна до піку (перший крок), тож "лійка" звужується так само,
   як і раніше — просто тепер це помітно як справжня форма, а не як прогресбар. */
.sv-fchart {
    display: flex;
    flex-direction: column;
    /* 2px, а не 12: воронка має читатись суцільною фігурою, а не п'ятьма
       окремими смугами. Нуль теж не годиться — два сусідні кроки бувають
       однакової ширини («На розгляді» і «Студент» при 218 обидва), і без
       жодного просвіту вони зливаються в один блок. 2px кольору поверхні —
       рівно той мінімум, що лишає межу видимою. */
    gap: var(--sv-s05);
}

.sv-fbar {
    display: flex;
    align-items: center;
    /* Назва тепер поза баром (absolute), тож числа притискаємо праворуч */
    justify-content: flex-end;
    gap: var(--sv-s3);
    /* Мінімум рахований під вміст, а не «на око»: назва кроку лежить поверх
       лівого краю (absolute), числа притиснуті праворуч — у вузькому барі
       вони налазили одне на одного (найкоротший крок «Студент», 132px).
       Найдовша назва + числа + відступи ≈ 250px. */
    min-width: 250px;
    height: 42px;
    padding: 0 var(--sv-s3);
    /* Заливка — два шари.

       Нижній: послідовна рампа. Один відтінок, від --sv-fbar-base до
       сейджу, крок за кроком світліше — тобто колір кодує ПОЗИЦІЮ в
       воронці, а не категорію. Це єдине, що колір тут означає: ширина вже
       несе величину, і другий вимір їй не потрібен.

       Верхній: ледь помітне сяйво згори (6% білого, згасає на 60% висоти).
       Воно й дає обʼєм, якого бракувало пласким прямокутникам.

       Контраст білого тексту заміряно на найсвітлішій точці найсвітлішого
       кроку (рампа 40% + сяйво 6%): 4.95:1 у світлій темі. Далі світлішати
       нема куди — 12% сяйва вже дають 4.35:1, тобто провал AA. */
    background:
        linear-gradient(180deg, rgb(255 255 255 / 0.06), transparent 60%),
        color-mix(in srgb, var(--sv-sage) var(--sv-fbar-mix, 0%), var(--sv-fbar-base));
    /* Заокруглення лише на ТОРЦІ ДАНИХ. Лівий край — нуль, спільний для
       всіх п'яти кроків, і рівна вертикаль там і є віссю (та сама, що
       підписана 0% у .sv-fscale нижче). Заокруглений нуль читався б так,
       ніби бар починається трохи після нуля. */
    border-radius: 0 6px 6px 0;
    color: white;
    overflow: hidden;
    transition: width 0.3s var(--sv-ease), box-shadow var(--sv-dur-fast) var(--sv-ease);
}

/* Крок воронки: обгортка навколо бара + його назви. Назва лежить поруч
   із баром у розмітці, а сюди її кладе absolute — тож ширина бара
   (пропорційна значенню) більше не обмежує довжину тексту. */
.sv-fstep {
    position: relative;
}

/* Кроки рампи — явним переліком, а не обчисленням від кількості кроків:
   стадій воронки рівно пʼять і вони зашиті в FUNNEL_ORDER, тож ділити
   щось на щось у CSS тут нема потреби. Шостий крок (якщо колись зʼявиться)
   просто візьме базовий колір — це помітно, але не зламано. */
.sv-fstep:nth-child(2) .sv-fbar { --sv-fbar-mix: calc(var(--sv-fbar-ramp) * 1); }
.sv-fstep:nth-child(3) .sv-fbar { --sv-fbar-mix: calc(var(--sv-fbar-ramp) * 2); }
.sv-fstep:nth-child(4) .sv-fbar { --sv-fbar-mix: calc(var(--sv-fbar-ramp) * 3); }
.sv-fstep:nth-child(5) .sv-fbar { --sv-fbar-mix: calc(var(--sv-fbar-ramp) * 4); }

.sv-fbar-label {
    position: absolute;
    left: var(--sv-s3);
    top: 0;
    height: 42px;
    display: flex;
    align-items: center;
    font-size: 0.85rem;
    font-weight: 700;
    color: white;
    white-space: nowrap;
    pointer-events: none;
}

.sv-fbar-nums {
    display: flex;
    align-items: baseline;
    gap: var(--sv-s15);
    flex-shrink: 0;
}

.sv-fbar-count {
    font-size: 0.95rem;
    font-weight: 800;
}

.sv-fbar-pct {
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    color: rgba(255, 255, 255, 0.75);
    white-space: nowrap;
}

/* Конверсія ВІД ПОПЕРЕДНЬОГО кроку — показує ДЕ втрата, на відміну від
   «від лідів» поруч. Колір — НЕ --sv-accent, хоч той і дає 6.03:1 на
   базовому --sv-primary: у темній темі .sv-fbar навмисно світлішає до
   #2F5A63 (щоб бар не зливався з майже чорною сторінкою; заміряно самим
   кодом поруч — "білий текст усередині — 7.6:1"), і на цьому світлішому
   тлі той самий золотий падає до 3.71:1. Білий уже калібрований під ОБИДВА
   фони бару (7.60 на темному, 21 на світлому — свій колір для
   .sv-fbar-count), тож ідемо тим самим шляхом: повний білий + вага, а не
   ще один спецколір на тему. Жирність і розмір лишають його візуально
   акцентнішим за приглушений .sv-fbar-pct поруч. */
.sv-fbar-step {
    font-size: 0.78rem;
    font-weight: 800;
    color: white;
    white-space: nowrap;
}

/* Бар став посиланням — знімаємо все, що браузер додає до <a>, і лишаємо
   видиму мішень фокусу для клавіатури. */
a.sv-fbar {
    text-decoration: none;
}

/* brightness(1.12) тут не працював, і це заміряно: --sv-primary #1A393F
   множиться до rgb(29,64,71), тобто 1.1:1 проти самого себе — зміни не
   видно взагалі. Бар веде в Students із фільтром по стадії, і ця дія має
   бути видимою до кліку, а не здогадною. Обідок тим же золотом, що й
   кільце фокуса нижче: ховер і фокус ведуть в одне місце, тож і виглядати
   мають однаково. */
a.sv-fbar:hover {
    box-shadow: 0 0 0 2px var(--sv-accent);
}

a.sv-fbar:focus-visible {
    outline: 2px solid var(--sv-accent);
    outline-offset: 2px;
}

/* Перший крок ("вхід у воронку") і рядки без даних — текстова примітка
   замість відсотка, менш насичена, щоб не виглядала як реальне число */
.sv-fbar-pct.is-muted {
    font-weight: 500;
    color: rgba(255, 255, 255, 0.65);
}


/* --- Роль у шапці --- */

/* Ім'я/прізвище як є (Олена Кравченко), не КАПСОМ — капс лишається лише
   на самій капсулці ролі поруч, яка досить дрібна й декоративна, щоб це
   нести, а на ім'я людини звичний регістр читається природніше. */
.sv-user-name {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
    font-weight: 700;
    letter-spacing: 0.02em;
}

/* Напівпрозора золота підкладка замість суцільної заливки — та сама
   легка «tint» ідея, що й .sv-claim-badge (rgba(220,175,66,.16)): досить
   яскраво, щоб читалось як бейдж, але не ріже очі суцільною плямою поруч
   із ім'ям. Колір тексту різний за місцем, бо підклад під ним різний:
   .sv-user (десктопний кластер) завжди на темному топбарі; підвал
   висувного меню — на --sv-surface, яка йде за темою сайту. */
.sv-role-badge {
    font-size: 0.58rem;
    font-weight: 800;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    padding: var(--sv-s05) var(--sv-s2);
    border-radius: var(--sv-radius-pill);
    background: rgba(220, 175, 66, 0.16);
    white-space: nowrap;
}

/* color-mix(...,transparent) — той самий колір, що й раніше, але
   притлумлений: текст, а не тільки підклад, тепер теж напівпрозорий.
   .sv-user (завжди на темному топбарі) має тонший запас контрасту, ніж
   .sv-drawer-foot-user (--sv-surface, за темою сайту): на світлому/темному
   топбарі це ~3.6:1 (нижче AA 4.5:1 для дрібного тексту — свідомий
   компроміс на прохання користувача, самого бейджа це не робить
   нечитабельним); у підвалі меню запас з великим відривом (>6:1 в обох
   темах), там та сама прозорість нічим не ризикує. */
.sv-user .sv-role-badge {
    color: color-mix(in srgb, var(--sv-accent) 82%, transparent);
}

.sv-drawer-foot-user .sv-role-badge {
    color: color-mix(in srgb, var(--sv-on-tint) 82%, transparent);
}

/* --- Ручне переведення стадії --- */

.sv-force-stage {
    width: 100%;
    border-top: 1px dashed var(--sv-border);
    padding-top: var(--sv-s3);
    margin-top: var(--sv-s1);
}

.sv-force-label {
    display: block;
    font-size: var(--sv-fs-xs);
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--sv-text-muted);
    margin-bottom: var(--sv-s3);
}

.sv-btn-sm {
    min-height: var(--sv-ctrl-h-sm);
    padding: var(--sv-s15) 14px;
    font-size: var(--sv-fs-sm);
    /* Якір для невидимого ::after, який на мобільному (нижче) розширює
       тач-зону — саме поле лишається візуально 34px. */
    position: relative;
}

.sv-insight {
    display: flex;
    align-items: flex-start;
    gap: var(--sv-s2);
    background: var(--sv-tint-amber);
    border: 1px solid var(--sv-accent);
    border-radius: var(--sv-radius);
    padding: var(--sv-s3);
    margin-top: var(--sv-s4);
    font-size: 0.83rem;
    color: var(--sv-text);
}

.sv-insight svg {
    color: var(--sv-warning);
    flex-shrink: 0;
    margin-top: var(--sv-s05);
}

/* Пояснення обмежень даних — інформація, а не проблема, тож без жовтого */
.sv-insight.is-neutral {
    background: var(--sv-bg);
    border-color: var(--sv-border);
    color: var(--sv-text-muted);
    font-size: 0.78rem;
    line-height: 1.5;
}

.sv-insight.is-neutral svg {
    color: var(--sv-text-muted);
}

/* --- NPS --- */

.sv-nps {
    display: flex;
    gap: var(--sv-s5);
    align-items: center;
    margin-bottom: var(--sv-s4);
}

.sv-nps-score {
    text-align: center;
    flex-shrink: 0;
}

.sv-nps-value {
    font-size: 2.4rem;
    font-weight: 800;
    color: var(--sv-text);
    line-height: 1;
}

.sv-nps-label {
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

.sv-nps-dist {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.sv-nps-row {
    display: grid;
    grid-template-columns: 30px 1fr 24px;
    align-items: center;
    gap: var(--sv-s2);
    font-size: 0.75rem;
}

.sv-nps-star {
    color: var(--sv-text-muted);
    font-weight: 700;
}

.sv-nps-bar {
    height: 8px;
    background: var(--sv-accent);
    border-radius: var(--sv-radius-pill);
}

.sv-nps-n {
    text-align: right;
    color: var(--sv-text-muted);
    font-weight: 700;
}

/* Ряд чисел — ПЛИТКИ, а не текст із розділювачем над ним (прохання
   користувача 29.09.2026). Підпис і число без власної коробки зливались із
   поясненнями й таблицями довкола в одну сіру масу, і жодна лінія-
   розділювач цього не рятувала. Тепер кожне число — окрема плитка.

   Порядок у шаблоні = важливість: зліва найважливіше (для часу — медіана,
   потім середнє), далі за спаданням. Тло плитки ТЬМЯНІЄ разом із
   порядком — перша помітна, остання майже зливається з панеллю. Правило
   живе тут, за позицією, а не класом на кожній плитці: тоді в шаблоні
   достатньо поставити числа в правильному порядку, і ступінь акценту
   стає сам. */
.sv-stat-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(120px, 100%), 1fr));
    /* Два рядки на всю смугу: підписи в перший, значення в другий. Без
       цього плитка з довгим підписом («Середнє (без 5% крайніх)») ламала
       його на два рядки й зсувала СВОЄ значення нижче за сусідні. */
    grid-template-rows: auto auto;
    gap: var(--sv-s2);
    margin: var(--sv-s4) 0;
}

/* Остання смуга в панелі — без відступу знизу: у панелі вже є padding. */
.sv-stat-row:last-child {
    margin-bottom: 0;
}

/* subgrid, а не власна сітка: плитка успадковує висоти рядків смуги, тож
   усі підписи ділять висоту найвищого з них, а всі значення стартують з
   однієї лінії. */
.sv-stat-row > div {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span 2;
    row-gap: var(--sv-s1);
    align-content: start;
    padding: var(--sv-s3) var(--sv-s4);
    border-radius: var(--sv-radius);
    background: color-mix(in srgb, var(--sv-stat-ink) var(--sv-stat-mix, 3%), var(--sv-surface));
}

/* Сходинки акценту: 1-ша плитка найпомітніша, далі тьмяніє до ледь
   помітної. П'ятої й далі плиток сходинка вже не розрізняє — вони на
   рівні «майже фон». */
.sv-stat-row > div:nth-child(1) { --sv-stat-mix: 16%; }
.sv-stat-row > div:nth-child(2) { --sv-stat-mix: 10%; }
.sv-stat-row > div:nth-child(3) { --sv-stat-mix: 6%; }
.sv-stat-row > div:nth-child(4) { --sv-stat-mix: 4%; }

.sv-stat-row span {
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

.sv-stat-row > div:nth-child(1) span {
    color: color-mix(in srgb, var(--sv-text-muted) 75%, var(--sv-text));
}

/* Проміжок між підписом і значенням тепер тримає grid-gap смуги, тож
   line-height лишаємо щільним — фігура має читатись як одне ціле.
   Пропорційні цифри навмисно (без tabular-nums): значення стоять у РЯД,
   а не в стовпчик, вирівнювати тут нема чого, а tabular на 1.15rem
   розріджує число («121» розповзається). Моноширинні лишаються там, де
   вони й потрібні, — у колонках таблиць (.sv-table-num). */
.sv-stat-row b {
    font-size: 1.15rem;
    font-weight: 800;
    line-height: 1.25;
}

.sv-subhead {
    font-size: var(--sv-fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--sv-text-muted);
    margin: var(--sv-s4) 0 var(--sv-s2);
}

.sv-curator-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--sv-s15) 0;
    font-size: 0.83rem;
    border-bottom: 1px solid var(--sv-border);
}

.sv-curator-row:last-child {
    border-bottom: none;
}

.sv-curator-count {
    font-weight: 800;
    background: var(--sv-bg);
    padding: var(--sv-s05) 10px;
    border-radius: var(--sv-radius-pill);
    font-size: 0.76rem;
}

.sv-result-actions {
    padding: 0 var(--sv-s5) var(--sv-s4);
}

/* ============ Зони дашборда ============ */

/* Дві зони з різною областю дії фільтра. Межа тепер зверху зони (а не під
   заголовком) — розділ спочатку відокремлює зону, а тоді йдуть назва й
   фільтр, а не навпаки. Фільтр періоду фізично стоїть усередині тієї зони,
   якою керує, і по центру над панелями, які він живить. */
.sv-zone {
    margin-bottom: var(--sv-s6);
    padding-top: var(--sv-s4);
    border-top: 2px solid var(--sv-border);
}

.sv-zone-head {
    margin-bottom: var(--sv-s4);
    text-align: center;
}

/* Плитки стану: 2 широкі зверху, 3 знизу. Шість однакових колонок ділять
   і той, і той ряд без жодної арифметики в розмітці — перші дві плитки
   беруть по три колонки, три наступні по дві. Ряди рівні заввишки, тож
   спарклайни стоять на одній лінії. */
.sv-tiles-state {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    grid-auto-rows: 1fr;
    gap: var(--sv-s3);
    /* Базовий .sv-tiles має margin-bottom: якщо його лишити, блок плиток
       розтягується на висоту картки метрики РАЗОМ із відступом, тобто самі
       плитки виходять на 24px коротшими й їхній нижній край не збігається
       з краєм картки. Відступ знизу тут дає .sv-state-top. */
    margin-bottom: 0;
}

.sv-tiles-state > :nth-child(-n + 2) {
    grid-column: span 3;
}

.sv-tiles-state > :nth-child(n + 3) {
    grid-column: span 2;
}

/* Без min-width:0 мінімальна ширина групи дорівнює ширині вмісту, а
   розкривачка в ній має сталі 68ch — на 375px це розпирало документ до
   555px і вся сторінка їхала вбік. Заміряно: з min-width:0 scrollWidth
   назад 375. Лишається й для сітки: `auto`-колонка теж росте по вмісту. */
.sv-zone-head > div {
    min-width: 0;
}

.sv-zone-title {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: var(--sv-s2);
    flex-wrap: wrap;
    font-size: var(--sv-fs-md);
}

.sv-zone-note {
    /* margin-left/right:0 центрувало заголовок (той без max-width, тому
       рівно по центру всієї ширини), але сам підпис — з max-width:68ch і
       без auto-полів — лишався притиснутим до лівого краю: його ТЕКСТ
       центрувався в СВОЇЙ вужчій коробці, яка сама стояла зліва, тож
       центр підпису не збігався з центром заголовка над ним. */
    margin: var(--sv-s1) auto 0;
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    max-width: 68ch;
}

/* Тестова правка на прохання користувача: підпис зони в один рядок на
   широких екранах. На вузьких (<900px) лишаємо перенос — інакше рядок
   не влазить і вилазить за межі екрана. */
@media (min-width: 900px) {
    .sv-zone-note {
        white-space: nowrap;
        max-width: none;
    }
}

.sv-scope-badge {
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    text-transform: lowercase;
    letter-spacing: 0.02em;
    padding: var(--sv-s05) 10px;
    border-radius: var(--sv-radius-pill);
    background: var(--sv-bg);
    color: var(--sv-text-muted);
    border: 1px solid var(--sv-border);
}

/* Зона, якою фільтр справді керує — бейдж акцентний */
/* Верх вкладки «Стан школи»: метрика ліворуч, плитки праворуч.
   `auto` для лівої колонки, а не частка: картка метрики має бути рівно
   такою, якою її роблять кільце й підпис, а вся решта ширини — плиткам,
   бо саме там числа, за якими приходять. */
.sv-state-top {
    display: grid;
    /* Фіксовані 280px, а не `auto`: при `auto` ширину картки диктував
       найдовший нерозривний шматок підпису, і той ліз у три рядки, роблячи
       картку вищою за плитки. 280px вистачає на два рядки, а плиткам
       лишається все інше. */
    grid-template-columns: 280px minmax(0, 1fr);
    gap: var(--sv-s4);
    margin-bottom: var(--sv-s5);
    align-items: stretch;
}

/* Медіа-запити тут, а НЕ вище при .sv-tiles-state: вага в них однакова з
   базовим правилом, тож вирішує порядок у файлі. Коли вони стояли раніше,
   базове `auto minmax(0,1fr)` перебивало їх на мобільному — заміряно:
   колонка під метрику лишалась активною, і плитка виходила 45px завширшки
   замість 160. */
/* Вузький екран: макет 2+3 тримається лише поки колонка ширша за число.
   Нижче — звичайні дві колонки, далі одна. */
@media (max-width: 1100px) {
    .sv-state-top {
        grid-template-columns: minmax(0, 1fr);
    }

    .sv-headline {
        padding: var(--sv-s4);
    }
}

@media (max-width: 720px) {
    .sv-tiles-state {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .sv-tiles-state > :nth-child(-n + 2),
    .sv-tiles-state > :nth-child(n + 3) {
        grid-column: span 1;
    }
}

/* Та сама поверхня, що й у плиток поруч: картка стоїть із ними в одному
   ряду, і темне тло робило з неї окремий віджет, а не першу картку ряду.

   Темний фон тут був не примхою: на ньому золото дає 6.03:1, і саме тому
   підпис із кільцем були золоті. На світлій поверхні золото — ~2:1, тобто
   провал AA, тож разом із фоном мусив помінятись увесь вміст. Кільце
   повертається до СВОЇХ типових кольорів (--sv-funnel-bar на доріжці
   --sv-bg, тобто primary у світлій темі й sage у темній) — тих самих, під
   які _ring.html і писався.

   Колонка, а не рядок: кільце згори, під ним підпис і знаменник. Так
   картка лишається вузькою і не з'їдає ширину в плиток. */
.sv-headline {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--sv-s2);
    padding: var(--sv-s5);
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    text-align: center;
}

/* Підпис — ПІДПИС, а не друга половина заголовка. Був --sv-fs-xl (24px)
   при числі 32px: два золоті блоки майже одного кегля, з яких більший за
   площею — саме текст, бо він у два рядки. Тепер 16px проти 40px, і
   ієрархія читається з відстані, а не вичитується. */
/* Кільце й підпис — один ряд, вирівняні по ЦЕНТРУ, а не по базовій лінії:
   у кільця базової лінії немає, і при align-items:baseline воно ставало б
   на рядок тексту нижнім краєм. Решта дітей .sv-headline (знаменник,
   застереження, друга метрика) мають flex-basis:100%, тож ідуть під цим
   рядком і без окремої обгортки. */
/* Той самий золотий, що й число в кільці (--sv-ring-value) — 6.03:1 на
   --sv-primary, той самий пройдений контраст, що й бейдж ролі в топбарі. */
.sv-headline-label {
    max-width: 26ch;
    font-size: var(--sv-fs-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    line-height: 1.35;
    /* --sv-text, а не золото: на світлій поверхні золото ~2:1. Підпис тут
       несе назву метрики, тобто це заголовок, а не службовий текст, — тому
       повний контраст, а не приглушений. */
    color: var(--sv-text);
}

/* Знаменник під підписом — приглушений, як підписи плиток поруч: він
   потрібен, щоб число можна було звірити, але сперечатись із ним за увагу
   не має. */
.sv-headline-base {
    max-width: 26ch;
    font-size: var(--sv-fs-xs);
    line-height: 1.4;
    color: var(--sv-text-muted);
}

/* Іконка попередження успадковує колір тексту, а не лишається темною на
   темному тлі плитки. */
/* ---- Кільце: одна частка проти 100% ---- */
/* Два власні токени, а не зашиті кольори: кільце малюється і на темній
   headline-плитці, і (потенційно) на світлій панелі, а це протилежні
   поверхні. Значення за замовчуванням — рівно ті, якими вже намальовані
   бари швидкості: --sv-funnel-bar (primary у світлій темі, sage у темній)
   на доріжці --sv-bg. Нових кольорів у палітру не додано.

   Контраст, заміряний для headline-варіанта (дуга --sv-accent, тло
   --sv-primary):
     * дуга проти тла — 6.03:1. Це і є несуча пара: значення читається з
       того, ДЕ дуга закінчується, а її кінець лежить на темному тлі.
     * дуга проти доріжки — 2.95:1, доріжка проти тла — 2.0:1.
   Світліша доріжка виправляє друге рівно настільки, наскільки псує перше
   (при білому 40% виходить 3.33 проти тла, але вже 1.81 проти дуги), тож
   оптимуму, де обидві пари ≥3:1, на цьому тлі не існує. Обрано те, що
   тримає несучу пару, а доріжка лишається рейкою; саме число до того ж
   надруковане текстом усередині кільця, тож жодна величина не передається
   лише геометрією. */
.sv-ring {
    --sv-ring-track: var(--sv-bg);
    --sv-ring-arc: var(--sv-funnel-bar);

    position: relative;
    flex: none;
    /* 160px, а не 140: число всередині мусить лишатись заголовковим
       (2.5rem), а внутрішній діаметр при 140px тримав "100.0%" впритул.
       Внутрішній діаметр = 2 × (r − stroke/2) у частках viewBox 42,
       тобто 160 × 35.4 / 42 ≈ 135px. */
    width: 160px;
    height: 160px;
    display: grid;
    place-items: center;
}

.sv-ring-svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    /* Нуль — на 12 годині, а не на 3: інакше дуга починається збоку і
       «повний» стан неможливо впізнати з першого погляду. */
    transform: rotate(-90deg);
}

.sv-ring-track,
.sv-ring-arc {
    fill: none;
    /* В одиницях viewBox (42): 3.2/42 ≈ 10.7px при діаметрі 140px. */
    stroke-width: 3.2;
}

.sv-ring-track {
    stroke: var(--sv-ring-track);
}

/* Кінці рівні, не заокруглені — і це не забутий stroke-linecap. Бари в
   панелі заокруглені через border-radius, який ОБРІЗАЄ кут: смуга завширшки
   W лишається завширшки W. SVG-шний linecap:round навпаки ДОДАЄ по пів
   товщини штриха за кожен кінець дуги — при товщині 10.7px і колі 440px це
   +1.2% з кожного боку, тобто 76% малювалися б як 78.4%. Дрібниця, але
   дрібниця того самого роду, що «з'єднати прямою тижні без даних». */
.sv-ring-arc {
    stroke: var(--sv-ring-arc);
    stroke-linecap: butt;
}

.sv-ring-value {
    position: relative;
    font-size: var(--sv-fs-3xl);
    font-weight: 800;
    line-height: 1;
    color: var(--sv-ring-arc);
}

/* Знак відсотка вдвічі дрібніший за цифри: він однаковий у кожному
   значенні, а місця з'їдає стільки ж, скільки цифра. Без цього "100.0%"
   не влазить у внутрішній діаметр. */
.sv-ring-value i {
    font-size: 0.5em;
    font-style: normal;
}

/* Перекриття для темного тла більше не потрібне: картка стоїть на
   --sv-surface, тобто рівно на тій поверхні, під яку писані типові
   значення --sv-ring-* вище. */

/* ============ Воронка вступу (вкладка «Стан школи») ============ */

/* Ряд воронки вступу (під плитками): воронка широка, картка Overall
   праворуч — дзеркало ряду над ним (Funnel-кільце ліворуч, плитки
   праворуч), тож обидва кільця стоять у колонках однакової ширини, 280px. */
.sv-state-enrol {
    grid-template-columns: minmax(0, 1fr) 280px;
}

/* Медіа ПІСЛЯ базового правила — вага однакова, вирішує порядок (та сама
   пастка, що описана біля .sv-state-top). */
@media (max-width: 1100px) {
    .sv-state-enrol {
        grid-template-columns: minmax(0, 1fr);
    }
}

.sv-ef-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s3);
    margin-bottom: var(--sv-s4);
}

/* margin-right:auto — заголовок ліворуч, усі контроли праворуч, і при
   переносі вони йдуть окремим рядком, а не розсипаються по ширині. */
.sv-ef-head h2 {
    font-size: var(--sv-fs-md);
    margin-right: auto;
}

.sv-ef-controls {
    display: flex;
    align-items: center;
    gap: var(--sv-s3);
    flex-wrap: wrap;
}

/* Низ блоку: розкривачка «Як рахується» ліворуч, «Внести дані» праворуч.
   flex-start, а не center: розкрита розкривачка росте вниз, і кнопка має
   лишатись навпроти її заголовка, а не з'їжджати на середину тексту. */
.sv-ef-bottom {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sv-s3);
    margin-top: var(--sv-s4);
}

.sv-ef-bottom > .sv-notes {
    flex: 1;
    min-width: 0;
    margin-top: 0;
}

/* «Внести дані» — тихий сірий напис того ж кегля й кольору, що
   «Як рахується» ліворуч (.sv-gloss > summary), а не кнопка з рамкою:
   два елементи одного рядка мають читатись як пара. */
.sv-ef-bottom > .sv-ef-input {
    flex-shrink: 0;
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s15);
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-size: var(--sv-fs-sm);
    font-weight: 600;
    color: var(--sv-text-muted);
    cursor: pointer;
    transition: color var(--sv-dur) var(--sv-ease);
}

.sv-ef-bottom > .sv-ef-input:hover {
    color: var(--sv-text);
}

.sv-ef-bottom > .sv-ef-input:focus-visible {
    outline: 2px solid var(--sv-accent);
    outline-offset: 2px;
    border-radius: 3px;
}

.sv-ef-bottom > .sv-ef-input svg {
    width: 14px;
    height: 14px;
}

/* Семестр блоку — підпис, не контрол: той самий кегль, що й сусідній
   перемикач, але напівжирний, щоб читався як «про що ця таблиця». */
.sv-ef-sem {
    font-size: var(--sv-fs-sm);
    font-weight: 700;
    color: var(--sv-text);
    white-space: nowrap;
}

.sv-ef-compare {
    font-size: var(--sv-fs-sm);
    color: var(--sv-text);
    cursor: pointer;
}

.sv-ef-compare input {
    accent-color: var(--sv-check);
    width: 16px;
    height: 16px;
    margin: 0;
}

.sv-ef-input svg {
    flex-shrink: 0;
}

/* --- Таблиця --- */
/* Колонки задає ТАБЛИЦЯ, а рядки беруть їх через subgrid: так числа всіх
   семи рядків стоять в одну вертикаль, хоч рядок і є окремим елементом
   (потрібно для ховера, фокуса й тултіпа на весь рядок). */
.sv-ef-table {
    position: relative;
    display: grid;
    grid-template-columns: minmax(240px, 1.3fr) minmax(120px, 2fr) 5.5em 9.5em;
    column-gap: var(--sv-s4);
}

.sv-ef-table.has-prev {
    grid-template-columns: minmax(240px, 1.3fr) minmax(120px, 2fr) 5.5em 9.5em 6.5em;
}

.sv-ef-row {
    display: grid;
    grid-template-columns: subgrid;
    grid-column: 1 / -1;
    align-items: center;
    padding: 10px var(--sv-s2);
    border-top: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-sm);
    transition: background var(--sv-dur-fast) var(--sv-ease);
}

/* Ховер/фокус — легка заливка рядка, а не рамка: рядок «відгукується»,
   але ваги даних не додає. */
.sv-ef-row.is-active,
.sv-ef-row:focus-visible {
    background: var(--sv-row-hover);
}

.sv-ef-headrow {
    padding-top: 0;
    padding-bottom: var(--sv-s2);
    border-top: 0;
    font-size: var(--sv-fs-xs);
    font-weight: 600;
    color: var(--sv-text-muted);
}

/* Межа між сайтом/вебінаром і заявкою в боті: далі міняється і природа
   чисел (ручні -> автоматичні), і база кроку. Повітря, а не лінія —
   лінія вже є між кожними двома рядками. */
.sv-ef-row.is-block-start {
    margin-top: var(--sv-s3);
}

.is-num {
    text-align: right;
    /* Моноширинні цифри — лише в колонках: саме тут числа мусять стати
       одне під одним. */
    font-variant-numeric: tabular-nums;
}

/* --- Етап: назва + джерело --- */
.sv-ef-stage {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
    min-width: 0;
}

.sv-ef-name {
    font-size: var(--sv-fs-base);
    font-weight: 600;
    color: var(--sv-text);
    line-height: 20px;
}

/* Джерело — нейтральна пігулка з текстом, без кольору. Колір тут нічого б
   не кодував, крім того, що вже написано словами, і лише додав би на
   графік чотири «серії», яких немає. */
.sv-ef-src {
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1.5;
    padding: 0 var(--sv-s2);
    border-radius: var(--sv-radius-pill);
    background: var(--sv-bg);
    color: var(--sv-text-muted);
    white-space: nowrap;
}

/* Вкладеність гілки вебінару: відступ + «коліно» від батьківського рядка.
   Реєстрація на вебінар — гілка сторінки реєстрації, відвідування — гілка
   реєстрації, тож рівні два. */
.sv-ef-row.is-l1 .sv-ef-stage { padding-left: 22px; }
.sv-ef-row.is-l2 .sv-ef-stage { padding-left: 44px; }

.sv-ef-row.is-l1 .sv-ef-stage::before,
.sv-ef-row.is-l2 .sv-ef-stage::before {
    content: "";
    position: absolute;
    top: 0;
    width: 11px;
    height: 10px;
    border-left: 1.5px solid var(--sv-text-muted);
    border-bottom: 1.5px solid var(--sv-text-muted);
    border-bottom-left-radius: 4px;
    opacity: 0.6;
}

.sv-ef-row.is-l1 .sv-ef-stage::before { left: 6px; }
.sv-ef-row.is-l2 .sv-ef-stage::before { left: 28px; }

/* --- Смуга --- */
/* Доріжка на всю ширину колонки — шкала «від старту», щоб довжину смуги
   було з чим порівняти й без осі. Та сама пара, що в кільця:
   --sv-funnel-bar на доріжці --sv-bg. */
.sv-ef-track {
    display: block;
    height: 12px;
    border-radius: 0 4px 4px 0;
    background: var(--sv-bg);
    overflow: visible;
}

/* 12px завтовшки (не більше 24 — смуга не заповнює рядок), торець даних
   заокруглений на 4px, край біля нуля — прямий: це й є вісь. */
.sv-ef-bar {
    display: block;
    height: 100%;
    min-width: 2px;
    border-radius: 0 4px 4px 0;
    background: var(--sv-funnel-bar);
}

/* Гілка вебінару — світліший крок тієї самої гами, а не інший колір:
   це не окрема серія, а відгалуження тієї самої воронки. */
.sv-ef-bar.is-branch {
    background: var(--sv-ef-branch);
}

/* --- Числа --- */
.sv-ef-count {
    font-size: var(--sv-fs-md);
    font-weight: 700;
    color: var(--sv-text);
}

.sv-ef-missing {
    font-size: var(--sv-fs-xs);
    font-weight: 600;
    color: var(--sv-text-muted);
}

.sv-ef-step {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    font-size: var(--sv-fs-base);
    color: var(--sv-text);
}

/* Підпис бази — лише там, де вона не рядок вище («Розпочали заявку» —
   від сторінки реєстрації, через голову гілки вебінару). */
.sv-ef-step small {
    font-size: var(--sv-fs-xs);   /* було 0.68rem = 10.9px — нижче мінімуму системи */
    line-height: 1.3;
    color: var(--sv-text-muted);
    text-align: right;
}

.sv-ef-prev {
    font-size: var(--sv-fs-sm);
    font-weight: 700;
    color: var(--sv-text-muted);
}

/* Знак стоїть у самому числі, тож колір — друге кодування, не єдине. Для
   кроку воронки вгору завжди добре. */
.sv-ef-prev.is-up { color: var(--sv-success); }
.sv-ef-prev.is-down { color: var(--sv-maroon); }

.sv-ef-foot {
    margin: var(--sv-s3) 0 0;
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

.sv-ef-foot:empty {
    display: none;
}


/* Тултіп рядка — той самий .sv-tip, що в тренду, але прив'язаний до
   рядка: JS ставить top на верх рядка, а сам тултіп стоїть над ним.
   Два класи, а не один: базовий .sv-tip стоїть у файлі НИЖЧЕ, і з рівною
   вагою його bottom/transform перебивали б ці — заміряно, тултіп
   стискався до 17px заввишки між top і bottom. */
.sv-tip.sv-ef-tip {
    bottom: auto;
    transform: translate(-50%, calc(-100% - 6px));
}

.sv-ef-tip-note {
    margin-top: var(--sv-s1);
    font-size: 0.68rem;
    color: var(--sv-text-muted);
    white-space: nowrap;
}

/* Семестр під знаменником картки Overall — кільце залежить від вибору в
   блоці, і без підпису його не відрізнити від Funnel-кільця «за весь час». */
.sv-headline-scope {
    font-weight: 700;
}

/* --- Модалка ручних чисел --- */
.sv-ef-form .sv-modal-body {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s4);
    padding-top: var(--sv-s4);
    padding-bottom: var(--sv-s5);
}

.sv-ef-form-hint {
    margin: 0;
}

/* Той самий відступ вкладеності, що й у таблиці: поля йдуть у тій самій
   ієрархії, і людина, яка вносить, бачить, що від чого рахується. */
.sv-ef-field.is-l1 { padding-left: 16px; }
.sv-ef-field.is-l2 { padding-left: 32px; }

.sv-ef-field .sv-input {
    max-width: 220px;
    font-variant-numeric: tabular-nums;
}

.sv-ef-field-hint {
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

.sv-ef-field-error {
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    color: var(--sv-maroon);
}

.sv-ef-field .sv-input[aria-invalid="true"] {
    border-color: var(--sv-maroon);
}

/* Вузький екран: назва етапу — власним рядком на всю ширину, під нею
   смуга й числа. Спершу було навпаки (назва й числа в одному рядку, смуга
   під ними), і з чотирма колонками на 375px назві лишалось 13–46px —
   заміряно. Числа ж коротші за назву й поруч зі смугою вміщаються. */
@media (max-width: 720px) {
    .sv-ef-table {
        grid-template-columns: minmax(0, 1fr) auto auto;
        column-gap: var(--sv-s3);
    }

    .sv-ef-table.has-prev {
        grid-template-columns: minmax(0, 1fr) auto auto auto;
    }

    .sv-ef-row {
        row-gap: var(--sv-s15);
    }

    .sv-ef-row > :nth-child(1) { grid-column: 1 / -1; grid-row: 1; }
    .sv-ef-row > :nth-child(2) { grid-column: 1; grid-row: 2; }
    .sv-ef-row > :nth-child(3) { grid-column: 2; grid-row: 2; }
    .sv-ef-row > :nth-child(4) { grid-column: 3; grid-row: 2; }
    .sv-ef-row > :nth-child(5) { grid-column: 4; grid-row: 2; }

    /* У рядку заголовків «Етап» стояв би окремим рядком над рештою —
       назва етапу й так очевидна, лишаємо підписи колонок із числами. */
    .sv-ef-headrow > :nth-child(1) {
        display: none;
    }

    /* …і тоді решта підписів — у першому ж рядку, без порожнього над ними. */
    .sv-ef-row.sv-ef-headrow > * {
        grid-row: 1;
    }

    /* Колонка кроку — `auto`, тобто по найширшому вмісту, а ним був
       підпис бази «від «Сторінка реєстрації»» в один рядок (132px).
       З max-width підпис переноситься всередині клітинки. */
    .sv-ef-step {
        max-width: 5em;
    }
}

/* ============ Адміни ============ */

.sv-admins {
    max-width: 1080px;
}

/* Розділяє блок "хто має доступ" від "синк зі SchoolToday" — це різні за
   вагою дії (керування людьми проти запису в прод-базу школи), і раніше
   вони просто йшли одне за одним із тим самим міжсекційним відступом, що
   й будь-які два сусідні sv-panel — нічого не натякало на межу між ними. */
.sv-section-divider {
    display: flex;
    align-items: center;
    gap: var(--sv-s3);
    margin: var(--sv-s5) 0 var(--sv-s2);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.sv-section-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--sv-border);
}

.sv-admin-add-form {
    display: grid;
    grid-template-columns: 2fr 1.5fr 1fr auto;
    gap: var(--sv-s3);
    align-items: end;
}

/* Колонки задає САМ список (subgrid у рядках нижче), а не кожен рядок
   окремо: раніше кожен .sv-admin-row був незалежним grid і сам вираховував
   ширину своїх колонок із власного вмісту. Рядки з різним вмістом
   (найпомітніше — власний рядок: без чекбокса, без кнопки доступів, без
   кнопок дій) отримували РІЗНУ ширину тих самих "колонок", і все їхало
   врозтіч між рядками. Тепер ширина рахується один раз тут, і subgrid
   лише перейомає готові трек. */
/* Колонка людини — minmax(16ch, 1fr), а не (0, 1fr): на 768–1024px
   auto-колонки (роль, підсумок доступів, статус, дії) забирали все місце,
   і ім'я з поштою стискались до однієї літери — пошта сипалась по
   символу в рядок. Підсумок доступів може переноситись (minmax(10ch, …)). */
.sv-admin-list {
    display: grid;
    grid-template-columns: auto minmax(16ch, 1fr) auto minmax(10ch, max-content) auto auto;
    row-gap: var(--sv-s2);
    column-gap: var(--sv-s3);
    margin-top: var(--sv-s4);
}

/* Бар масових дій, заголовок групи й порожній стан — не картки рядка, а
   звичайні смуги на всю ширину: без grid-column вони посіли б лише першу
   колонку списку. */
.sv-bulk-bar,
.sv-admin-group-head,
.sv-admin-empty {
    grid-column: 1 / -1;
}

/* Заголовок групи (рівень доступу, яким людей нижче востаннє налаштували) —
   легший за .sv-section-divider (без риски праворуч, менший відступ): той
   розділяє великі секції сторінки, цей — лише кластери всередині одного
   списку, вони йдуть значно частіше. Перший заголовок одразу під панеллю
   масових дій/власником — margin-top той самий, що й між групами, так
   рівномірніше, ніж по-особливому займатись першим випадком. */
.sv-admin-group-head {
    margin-top: var(--sv-s3);
    padding: 0 var(--sv-s1);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.sv-admin-row {
    display: grid;
    grid-column: 1 / -1;
    /* Перша колонка — чекбокс масового вибору, остання група — підсумок
       доступів: роль більше не описує, що людина може, тож саме той блок
       несе головну інформацію рядка. subgrid — щоб колонки бралися з
       .sv-admin-list, а не рахувались наново під вміст саме цього рядка
       (див. коментар вище). Мобільні медіа-запити нижче перевизначають це
       на звичайний (не subgrid) шаблон, бо там уже інша кількість колонок. */
    grid-template-columns: subgrid;
    align-items: center;
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    padding: var(--sv-s3) var(--sv-s4);
}

/* Приглушуємо лише ім'я/пошту вимкненого адміна — НЕ всю картку: інакше
   тьмяніли б і кнопка «Увімкнути» (єдина потрібна тут дія), і бейдж
   статусу. */
.sv-admin-row.is-inactive .sv-admin-id {
    opacity: 0.6;
}

.sv-admin-name {
    display: flex;
    flex-wrap: wrap;   /* бейдж «це ти» переходить нижче, а не вилазить за колонку */
    align-items: center;
    gap: var(--sv-s1) var(--sv-s2);
    min-width: 0;
    font-weight: 700;
    font-size: var(--sv-fs-base);
}

.sv-admin-you {
    /* Не стискається й не переноситься по складах («це / ти») поруч із
       довгим подвійним прізвищем — переноситься саме ім'я. */
    flex-shrink: 0;
    white-space: nowrap;
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    color: var(--sv-text-muted);
    background: var(--sv-surface);
    border: 1px solid var(--sv-sage);
    padding: 1px 7px;
    border-radius: var(--sv-radius-pill);
}

.sv-admin-email {
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    overflow-wrap: anywhere;
}

/* Назва рівня доступу — довільний текст власника («Повний доступ —
   розширений для старших кураторів»). Бейдж nowrap розпирав колонку
   ролі й виштовхував рядок за край екрана; тепер — трикрапка, повна
   назва в title. */
.sv-admin-role .sv-table-badge {
    display: inline-block;
    max-width: 22ch;
    overflow: hidden;
    text-overflow: ellipsis;
    vertical-align: middle;
}

.sv-admin-actions {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
}

/* Видалення — приглушене, стає червоним лише під курсором: щоб його не
   тиснули рефлекторно замість «вимкнути» */
.sv-btn-quiet-danger {
    background: transparent;
    border-color: var(--sv-border);
    color: var(--sv-text-muted);
}

.sv-btn-quiet-danger:hover {
    background: var(--sv-maroon);
    border-color: var(--sv-maroon);
    color: var(--sv-strong-text);
}

.sv-admin-empty {
    padding: var(--sv-s5);
    text-align: center;
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
}

/* Тумблер "міняти стадії вручну" — окремий per-admin грант поверх ролі.
   Власний рядок у grid (grid-column на всю ширину), під основними колонками
   картки й над розкривним списком полів. */
.sv-admin-grant {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sv-s2) var(--sv-s3);
    margin-top: var(--sv-s3);
    padding-top: var(--sv-s3);
    border-top: 1px dashed var(--sv-border);
}

.sv-admin-grant-toggle {
    font-weight: 700;
    white-space: normal;
}

.sv-admin-grant-note {
    margin: 0;
    flex: 1 1 12rem;
}

/* Персональний перелік полів профілю, дозволених адміну (запит 02.09) —
   окрема розкривна секція під рядком, а не ще одна колонка в grid: список
   полів надто довгий для рядка таблиці, і в 9 з 10 випадків його взагалі
   не відкривають. */
.sv-admin-fields {
    grid-column: 1 / -1;
    /* Контекст позиціювання для .sv-fields-head — щоб "Вибрати всі" клалось
       точно поверх рядка summary, а не в потоці, звідки воно й опускало
       список полів нижче (запит 02.09). */
    position: relative;
}

.sv-admin-fields > summary {
    cursor: pointer;
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    font-weight: 700;
    user-select: none;
    /* Явна висота — той самий орієнтир, що і в .sv-fields-head нижче,
       щоб кнопка спиралась на щось передбачуване, а не на "як вийде". */
    min-height: var(--sv-ctrl-h-sm);
    display: flex;
    align-items: center;
    /* Праворуч лишаємо місце під кнопку, щоб довге "Поля, які можна
       редагувати (26)" не запливало під неї на вузьких екранах. */
    padding-right: 100px;
}

.sv-fields-form {
    margin-top: var(--sv-s3);
    padding-top: var(--sv-s3);
    border-top: 1px dashed var(--sv-border);
    display: flex;
    flex-direction: column;
    gap: var(--sv-s3);
}

/* Позиціонується поверх рядка summary (той самий top:0, та сама висота
   34px), а не власним рядком у потоці .sv-fields-form — інакше кожне
   відкриття списку зсувало секції полів нижче лише заради однієї кнопки. */
.sv-fields-head {
    position: absolute;
    top: 0;
    right: 0;
    height: 34px;
    display: flex;
    align-items: center;
}

.sv-fields-section {
    border: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sv-s2) var(--sv-s4);
}

.sv-fields-section > legend {
    width: 100%;
    font-size: var(--sv-fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--sv-text-muted);
    font-weight: 700;
    margin-bottom: var(--sv-s05);
    padding: 0;
}

.sv-field-check {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s15);
    font-size: var(--sv-fs-xs);
    white-space: nowrap;
}

.sv-fields-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sv-s3);
}

.sv-fields-foot .sv-panel-note {
    margin: 0;
}

@media (max-width: 767px) {
    .sv-fields-foot {
        flex-direction: column;
        align-items: stretch;
    }
}

/* ============ Меню доступів (/admins) ============ */

.sv-admin-access {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
}

/* Колонка чекбокса лишається на місці й у рядків, де вибрати не можна
   (власник, ти сам) — інакше їхній вміст поїхав би вліво відносно решти. */
.sv-admin-pick {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
}

.sv-admin-pick input {
    accent-color: var(--sv-check);
    cursor: pointer;
}

/* Панель масових дій — липка над списком: при 10+ акаунтах обраний рядок
   може бути внизу, і кнопка дії не має тікати з екрана.

   top:0 тут наліз би точно під липкий топбар (той самий z-index) і
   перекривав його — той самий клас бага, що й у .sv-table thead th вище,
   виправлений тим самим прийомом: зсув на висоту топбару, нижчий z-index. */
.sv-bulk-bar {
    position: sticky;
    top: var(--sv-topbar-h);
    z-index: 1;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sv-s3);
    padding: var(--sv-s2) var(--sv-s3);
    margin-bottom: var(--sv-s2);
    background: var(--sv-tint-sage);
    border: 1px solid var(--sv-sage);
    border-radius: var(--sv-radius);
}

.sv-bulk-count {
    font-weight: 700;
    font-size: var(--sv-fs-sm);
}

.sv-bulk-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
}

.sv-bulk-actions .sv-select {
    width: auto;
    min-width: 150px;
}

/* Специфічність [hidden] на елементі однакова з .sv-bulk-bar { display:flex }
   вище, і авторське правило вигравало в UA-стилю — панель лишалась видимою
   ("Обрано акаунтів: 0") навіть коли нікого не обрано. Той самий бекграунд,
   що й .sv-buddy-roster-row[hidden] нижче в цьому файлі. */
.sv-bulk-bar[hidden] {
    display: none;
}

/* На вузькому панель переноситься у 2-3 ряди (~125px) — липкою вона з'їдала б
   третину екрана заради дії, яку роблять раз. Лишаємо звичайним блоком
   угорі списку. */
@media (max-width: 767px) {
    .sv-bulk-bar {
        position: static;
    }

    .sv-bulk-actions {
        width: 100%;
    }

    .sv-bulk-actions .sv-btn {
        flex: 1 1 auto;
    }
}

/* «Востаннє заходив» — довідка під бейджем статусу, а не самостійний сигнал:
   дрібніша й приглушена, щоб не змагатися з «Активний/Вимкнений». */
.sv-admin-seen {
    display: block;
    margin-top: var(--sv-s05);
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    white-space: nowrap;
}

/* Ширша за звичайні 540px: усередині матриця на три колонки, і на 540px
   підписи полів ламались би в два рядки кожен. */
.sv-modal.sv-access-modal {
    max-width: 720px;
}

/* .sv-modal-body/.sv-modal-actions скролять/лишаються на місці лише коли
   вони ПРЯМІ flex-діти .sv-modal (картка студента так і зроблена). Тут між
   ними стоїть <form> (весь попап зберігається одним POST) — звичайний
   display:block, тож overflow-y:auto на .sv-access-body ніколи не мав
   обмеженої висоти, щоб її смикати: вміст просто розпирав форму, а зайве
   мовчки зрізалось overflow:hidden із .sv-modal. Найпомітніше на мобільному
   (менше висоти екрана), де це читалось як "картка взагалі не гортається". */
.sv-access-form {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}

.sv-access-body {
    flex: 1 1 auto;
    min-height: 0;
}

.sv-access-section {
    padding: var(--sv-s4) 0;
    border-bottom: 1px solid var(--sv-border);
}

.sv-access-section:last-child {
    border-bottom: none;
}

.sv-access-section h4 {
    font-size: var(--sv-fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--sv-text-muted);
    margin-bottom: var(--sv-s2);
}

.sv-access-templates {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
    margin-bottom: var(--sv-s2);
}

.sv-access-copy {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
    margin-left: auto;
}

.sv-access-copy .sv-select {
    width: auto;
    min-width: 160px;
}

/* Вкладки й дії — по одному рядку на пункт, а не в сітку: підписи тут
   пояснювальні («CRM — дошка заявок»), і в дві колонки вони б обрізались. */
.sv-access-toggles {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s1);
}

/* .sv-field-check успадковує white-space:nowrap, і найдовший підпис
   («Ухвалювати рішення по заявках (зарахувати / відхилити / розблокувати)»)
   на 375px обрізався по правому краю. Тут підписи саме пояснювальні, тож
   переносимо; align-items:flex-start — щоб на двох рядках галочка лишалась
   біля ПЕРШОГО рядка, а не з'їжджала на середину висоти. */
.sv-access-toggles .sv-field-check {
    white-space: normal;
    align-items: flex-start;
    line-height: 1.35;
}

.sv-access-toggles .sv-field-check input {
    flex-shrink: 0;
}

.sv-access-group {
    margin-bottom: var(--sv-s3);
}

.sv-access-group-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sv-s3);
    padding: var(--sv-s1) 0 var(--sv-s2);
}

.sv-access-group-title {
    font-weight: 700;
    font-size: var(--sv-fs-sm);
}

.sv-access-group-actions {
    display: flex;
    gap: var(--sv-s3);
    white-space: nowrap;
}

/* Сама матриця. Дві вузькі колонки фіксованої ширини — щоб галочки «бачить»
   і «редагує» стояли рівними стовпчиками через усі секції, а не гуляли
   залежно від довжини підпису поля. */
.sv-access-matrix {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 68px 68px;
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-sm);
    overflow: hidden;
}

.sv-access-row {
    display: contents;
}

.sv-access-row > * {
    padding: var(--sv-s15) var(--sv-s2);
    border-top: 1px solid var(--sv-border);
    display: flex;
    align-items: center;
    min-height: var(--sv-ctrl-h-sm);
}

/* Шапка матриці залипає при прокрутці довгої секції — інакше на «Стан заявки»
   (11 полів) незрозуміло, котра колонка «бачить», а котра «редагує». */
.sv-access-row-head > * {
    position: sticky;
    top: 0;
    z-index: 1;
    border-top: none;
    background: var(--sv-bg);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    color: var(--sv-text-muted);
    justify-content: center;
}

.sv-access-field {
    font-size: var(--sv-fs-sm);
    min-width: 0;
}

.sv-access-cell {
    justify-content: center;
    cursor: pointer;
}

.sv-access-cell input {
    accent-color: var(--sv-check);
    cursor: pointer;
}

.sv-access-cell-na {
    color: var(--sv-text-muted);
    cursor: default;
}

.sv-access-row:hover > * {
    background: var(--sv-bg);
}

/* Блоки модулів (вкладка + дії) — та сама рамка, що й матриця полів, але з
   однією колонкою галочок і без рядка-шапки: «бачить/редагує» тут немає.
   Тому верхня лінія першого рядка зайва — її дає рамка. Колонка 68px —
   щоб галочки модулів стояли на одній вертикалі з колонкою «Редагує». */
.sv-access-matrix-single {
    grid-template-columns: minmax(0, 1fr) 68px;
}

.sv-access-matrix-single > .sv-access-row:first-child > * {
    border-top: none;
}

/* Рядок — <label>: клікабельний увесь, не лише квадратик. */
.sv-access-matrix-single .sv-access-field {
    cursor: pointer;
}

/* «Вкладка в меню» — ворота модуля, решта рядків під нею — дії всередині. */
.sv-access-row-tab .sv-access-field {
    font-weight: 600;
}

.sv-access-log {
    list-style: none;
    margin: var(--sv-s2) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--sv-s2);
}

.sv-access-log li {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s2);
    font-size: var(--sv-fs-sm);
}

.sv-access-log-when {
    color: var(--sv-text-muted);
    white-space: nowrap;
}

.sv-access-log-who {
    font-weight: 700;
}

.sv-access-added {
    color: var(--sv-success);
}

.sv-access-removed {
    color: var(--sv-maroon);
}

/* Список рівнів доступу — свідомо НЕ .sv-admin-list/.sv-admin-row: ті
   рахують subgrid під конкретні 6 колонок списку людей (чекбокс/роль/
   статус/дії), а тут рівно три шматки вмісту на рядок. Окремі, простіші
   класи замість підлаштування під чужу сітку. */
.sv-access-levels-list {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s2);
    margin-top: var(--sv-s4);
}

.sv-access-level-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sv-s3);
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    padding: var(--sv-s3) var(--sv-s4);
}

.sv-access-level-name {
    font-weight: 700;
    min-width: 140px;
}

.sv-access-level-summary {
    flex: 1 1 auto;
}

/* ============ Адаптивність ============ */

@media (max-width: 1024px) {
    .sv-admin-add-form {
        grid-template-columns: 1fr 1fr;
    }

    .sv-dash-grid {
        grid-template-columns: 1fr;
    }
}

/* Тег-лайн — nowrap-текст у 1fr grid-колонці: коли він ширший за трек, він
   переливається й налазить на нав. Показуємо його лише на широких екранах
   (≥1100px), де він гарантовано вміщається центрованим; на мобільному
   (<768px) його ховає окреме правило нижче. */
@media (max-width: 1100px) {
    .sv-topbar .sv-tagline {
        display: none;
    }
}

/* Ще вужче — ховаємо й ім'я з бейджем ролі (продубльоване на головній), щоб
   юзер-кластер із перемикачем теми не виштовхував нав за екран. */
@media (max-width: 880px) {
    .sv-topbar .sv-user-name {
        display: none;
    }
}

/* Планшет і вузьке вікно ноутбука: шість колонок рядка адміна не
   вміщаються в 768–1023px (рядок виходив за край або ім'я стискалось до
   літери) — компактний рядок у дві смуги з явними областями. */
@media (min-width: 768px) and (max-width: 1023px) {
    .sv-admin-row {
        grid-template-columns: auto minmax(0, 1fr) auto;
        grid-template-areas:
            "pick id      status"
            "pick role    access"
            ".    actions actions";
        gap: var(--sv-s2) var(--sv-s3);
    }

    .sv-admin-pick    { grid-area: pick; }
    .sv-admin-id      { grid-area: id; }
    .sv-admin-role    { grid-area: role; justify-self: start; }
    .sv-admin-access  { grid-area: access; justify-self: end; }
    .sv-admin-status  { grid-area: status; justify-self: end; text-align: right; }

    .sv-admin-actions {
        grid-area: actions;
        justify-content: flex-end;
    }
}

@media (max-width: 767px) {
    /* Топбар/nav/юзер-кластер для мобільного живуть в ОДНОМУ місці — у
       великому мобільному блоці нижче (.sv-topbar, .sv-nav-links, .sv-user,
       .sv-topbar .sv-tagline) — не тут. Раніше тут був окремий блок на
       max-width:768px із частково тими самими селекторами (1px різниці від
       блоку нижче), і три різні місця по-своєму вирішували, показувати
       tagline чи ні; звели в одне джерело правди. */
    .sv-board-wrap {
        padding: var(--sv-s3);
    }

    .sv-modal-actions {
        flex-direction: column;
    }

    /* Прямий нащадок, а не будь-який .sv-btn усередині: force-stage форма
       теж фізично лежить у #sv-actions, і без цього обмеження її власна
       кнопка "Перевести" (яка мала стояти поруч зі своїм dselect) теж
       розтягувалась на всю ширину і падала на новий рядок. */
    .sv-modal-actions > .sv-btn {
        width: 100%;
    }

    .sv-admin-add-form {
        grid-template-columns: 1fr;
    }

    /* Рядок адміна перебудовується у дві колонки, дії йдуть окремим рядком.
       Явний шаблон замість subgrid із базового правила — тут своя, менша
       кількість колонок, тож повертаємо і власний gap (на desktop його
       підхоплював subgrid від .sv-admin-list). */
    .sv-admin-row {
        grid-template-columns: minmax(0, 1fr) auto;
        gap: var(--sv-s3);
    }

    .sv-admin-actions {
        grid-column: 1 / -1;
        justify-content: flex-end;
    }

    /* Пігулковий border-radius:999px пасує рядку в один рядок; щойно
       "Свій період" переноситься на кілька рядків (вузький екран), та сама
       заокругленість розтягується у потворну краплю навколо всього блоку. */
    .sv-custom-range {
        border-radius: var(--sv-radius-lg);
    }
}

@media (max-width: 480px) {
    /* (бейдж ролі й «Вийти» тепер у випадному меню топбару — у самій смузі
       місця не займають, колишні тут display:none/padding-правила прибрано) */

    /* Кнопка експорту — лише іконка, без "Excel": разом із заголовком і
       датою/dselect'ом поруч підпис не вміщався в один рядок і виштовхував
       кнопку на новий рядок нижче. */
    .sv-export-label {
        display: none;
    }

    .sv-export {
        padding: 5px var(--sv-s2);
    }

    /* Кнопку експорту тут раніше виводили з потоку (absolute у кут панелі),
       щоб її не виштовхував вниз заголовок. Це створювало гіршу проблему:
       опис панелі — СУСІДНІЙ блок після .sv-panel-head, власного відступу
       праворуч не має, і підлазив просто під плаваючу кнопку. Замінено на
       grid у мобільній секції нижче: заголовок і кнопка стоять у своїх
       колонках, а опис починається під ними обома. */

    .sv-modal-overlay {
        padding: 0;
        align-items: flex-end;
    }

    .sv-modal {
        max-width: 100%;
        max-height: 94vh;
        border-radius: var(--sv-radius-lg) var(--sv-radius-lg) 0 0;
    }

    .sv-admin-row {
        grid-template-columns: 1fr;
        gap: var(--sv-s3);
    }

    .sv-admin-role,
    .sv-admin-status {
        justify-self: start;
    }
}

/* ============ Висувне меню (мобільний) ============
   Повноцінна бокова панель — заміна і колишньому спливному попапу теми/
   мови/виходу, і нижній таб-панелі (усі розділи тепер тут, одним списком).
   На десктопі нічого з цього блоку не рендериться: тригер, підкладка й
   сама панель — display:none, навігація лишається інлайновою в топбарі
   (.sv-nav-links/.sv-user вище). Логіка відкриття (тап), закриття (тап,
   свайп ліворуч усередині меню, Esc, клік по пункту/підкладці) — у
   base.html. */

.sv-drawer-backdrop {
    display: none;
}

.sv-drawer {
    display: none;
}

@media (max-width: 767px) {
    .sv-drawer-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: var(--sv-z-modal);
        background: var(--sv-overlay);
        opacity: 0;
        pointer-events: none;
        transition: opacity var(--sv-dur) var(--sv-ease);
    }

    .sv-drawer-backdrop.is-open {
        opacity: 1;
        pointer-events: auto;
    }

    /* transform, не left: анімується на композиторі (плавно навіть на
       слабких телефонах), а visibility з transition-delay прибирає панель
       з тач/фокус-дерева, коли вона закрита, — інакше приховані посилання
       й далі ловили б Tab і випадкові тапи за межами видимої області.

       Ширина — за вмістом (fit-content), не фіксовані 300px: пункти меню й
       ім'я — короткі слова, фіксована ширина лишала праворуч суцільну
       порожню смугу, непропорційну зліва/справа. fit-content рахує
       max-content найширшого рядка (зазвичай — ім'я/бейдж ролі або
       найдовший пункт на кшталт "Dashboard"), панель стає рівно трохи
       ширшою за нього; min/max — підлога, щоб короткий вміст не стискав
       панель до незручного мінімуму, і стеля на всяк випадок (дуже довге
       ім'я), щоб не з'їсти весь екран. */
    .sv-drawer {
        display: flex;
        flex-direction: column;
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        z-index: calc(var(--sv-z-modal) + 1);
        width: fit-content;
        min-width: 220px;
        max-width: min(300px, 84vw);
        background: var(--sv-surface);
        color: var(--sv-text);
        box-shadow: var(--sv-shadow-lg);
        transform: translateX(-100%);
        visibility: hidden;
        transition: transform var(--sv-dur) var(--sv-ease), visibility 0s linear var(--sv-dur);
        overflow-y: auto;
        padding-top: env(safe-area-inset-top, 0px);
        padding-bottom: env(safe-area-inset-bottom, 0px);
    }

    .sv-drawer.is-open {
        transform: translateX(0);
        visibility: visible;
        transition: transform var(--sv-dur) var(--sv-ease);
    }

    .sv-drawer-head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: var(--sv-s3);
        padding: var(--sv-s4) var(--sv-s4) var(--sv-s3);
        flex-shrink: 0;
    }

    .sv-drawer-head .sv-logo {
        font-weight: 800;
        font-size: var(--sv-fs-md);
        letter-spacing: 0.02em;
        color: var(--sv-text);
    }

    .sv-drawer-close {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 32px;
        height: 32px;
        border: none;
        border-radius: var(--sv-radius-sm);
        background: var(--sv-bg);
        color: var(--sv-text-muted);
        flex-shrink: 0;
        position: relative;
        transition: background var(--sv-dur-fast) var(--sv-ease), color var(--sv-dur-fast) var(--sv-ease);
    }

    .sv-drawer-close::after {
        content: "";
        position: absolute;
        inset: -6px;
    }

    .sv-drawer-close:hover {
        background: var(--sv-tint-red);
        color: var(--sv-maroon);
    }

    /* Розділи — один під одним, кожен на всю ширину панелі: список, а не
       ряд іконок, так їх можна підписати повністю й вистачає місця під
       усі права одразу (раніше нижня панель мовчки обрізалась на шостому
       пункті — «не більше п'яти вкладок»). */
    .sv-drawer-links {
        display: flex;
        flex-direction: column;
        gap: var(--sv-s05);
        padding: 0 var(--sv-s3) var(--sv-s3);
    }

    .sv-drawer-link {
        display: flex;
        align-items: center;
        gap: var(--sv-s3);
        min-height: 44px;
        padding: 0 var(--sv-s3);
        border-radius: var(--sv-radius);
        font-weight: 700;
        font-size: 0.92rem;
        text-decoration: none;
        color: var(--sv-text);
        transition: background var(--sv-dur-fast) var(--sv-ease);
    }

    .sv-drawer-link svg {
        flex-shrink: 0;
        color: var(--sv-text-muted);
        transition: color var(--sv-dur-fast) var(--sv-ease);
    }

    .sv-drawer-link:hover {
        background: var(--sv-bg);
    }

    /* Той самий сигнал «ти тут», що й активна вкладка десктопного нав —
       золота заливка з темним текстом, а не просто зміна кольору іконки. */
    .sv-drawer-link.is-active {
        background: var(--sv-accent);
        color: var(--sv-primary);
    }

    .sv-drawer-link.is-active svg {
        color: var(--sv-primary);
    }

    /* Підвал притиснутий донизу панелі (margin-top:auto), незалежно від
       того, скільки розділів вище показано за правами. Перемикачі й
       профіль — кожен по СВОЄМУ лівому краю, урівень з розділами вище,
       а не розкидані довільно. */
    .sv-drawer-foot {
        margin-top: auto;
        flex-shrink: 0;
        display: flex;
        flex-direction: column;
        gap: var(--sv-s3);
        padding: var(--sv-s3) var(--sv-s4);
        border-top: 1px solid var(--sv-border);
    }

    .sv-drawer-foot-switches {
        display: flex;
        align-items: center;
        gap: var(--sv-s3);
    }

    /* Сегментовані перемикачі розраховані на темний топбар (напівпрозоре
       біле тло, світлий текст) — тут перефарбовуємо під світлу панель
       (як .sv-login-lang). */
    .sv-drawer .sv-lang {
        background: var(--sv-bg);
    }

    .sv-drawer .sv-lang-btn:not(.is-active) {
        color: var(--sv-text-muted);
    }

    /* :not(.is-active): інакше ховер перебивав білий текст активної
       (золотої) кнопки на темний — 1.7:1. */
    .sv-drawer .sv-lang-btn:not(.is-active):hover {
        color: var(--sv-text);
    }

    .sv-drawer-foot-user {
        display: flex;
        flex-direction: column;
        gap: var(--sv-s2);
    }

    .sv-drawer-foot-user .sv-user-name {
        display: flex;
        flex-wrap: wrap;
        font-size: var(--sv-fs-sm);
        color: var(--sv-text);
    }

    /* --sv-primary тут було б невидиме в темній темі (майже збігається з
       --sv-surface). --sv-maroon читається в обох темах і до того ж
       семантично пасує «вийти». */
    .sv-drawer-logout {
        display: inline-flex;
        align-items: center;
        gap: var(--sv-s15);
        align-self: flex-start;
        min-height: 32px;
        /* Змішано з --sv-surface (не білим напряму) — той самий підхід
           коректно пом'якшує колір і на світлій, і на темній панелі,
           а не лише в одній із двох тем. 85% лишає з запасом над AA
           (5.99:1 світла / 4.77:1 темна — обидва вище 4.5:1), тож саме
           тут "трохи слабший" не зачіпає читабельність. */
        color: color-mix(in srgb, var(--sv-maroon) 85%, var(--sv-surface));
        font-weight: 700;
        font-size: 0.85rem;
        text-decoration: none;
        position: relative;
        transition: color var(--sv-dur-fast) var(--sv-ease);
    }

    .sv-drawer-logout::after {
        content: "";
        position: absolute;
        inset: -6px 0;
    }

    .sv-drawer-logout:hover {
        color: var(--sv-maroon-hover);
    }
}

/* ============ Мобільний режим ============ */

@media (max-width: 767px) {
    /* Чіпси/пігулки на мобільному нижчі (30px замість 36px, див. .sv-chip
       нижче — 44px тут виглядали товщими за свій десктопний вигляд, а тач-
       ціль добирає невидимий ::after). Перевизначаємо САМ ТОКЕН, а не
       кожне правило окремо: все, що на нього посилається (.sv-chip,
       .sv-custom-range, дропдаун семестру в ряду періоду), їде разом і
       лишається врівень. */
    :root {
        --sv-ctrl-h-chip: 30px;
    }

    /* --- Каркас --- */

    /* На мобільному відмовляємось від «десктопного» каркаса, де сторінка
       зафіксована по висоті екрана, а скролиться вкладений контейнер.
       Вкладені скрол-області на тачі ненадійні: інерція гаситься, а якщо
       контейнер не отримав власної висоти — вміст просто обрізається і не
       гортається взагалі (саме це й сталося зі списком студентів).
       Далі сторінка гортається природно, як звичайний документ. */
    .sv-app {
        height: auto;
        min-height: 100dvh;
        overflow: visible;
    }

    .sv-students-wrap,
    .sv-board-wrap,
    .sv-table-wrap {
        flex: none;
        min-height: 0;
        overflow: visible;
    }

    /* display:flex тут (а не grid, як на десктопі) — grid-трек не стискається
       нижче max-content сам по собі, тож без цього відкату .sv-nav-links
       своїм overflow-x:auto нижче розпирав би увесь топбар по горизонталі.
       position:sticky вже задано десктопним правилом. Розкладка тепер
       [☰] [лого] [тег-лайн] — пакуються зліва, тег-лайн забирає залишок. */
    .sv-topbar {
        display: flex;
        align-items: center;
        justify-content: flex-start;
        padding: 0 var(--sv-s3);
        gap: var(--sv-s2);
    }

    /* Раніше тут резервувався простір під нижню таб-панель (48px). Її
       більше немає — всю навігацію забрало висувне меню (.sv-drawer нижче),
       тож лишається лише невеликий відступ під safe-area (home-індикатор
       iPhone), а не повний тач-бар. */
    .sv-app .sv-students-wrap,
    .sv-app .sv-board-wrap {
        padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--sv-s4));
    }

    .sv-shell,
    .sv-dash {
        padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--sv-s6));
    }

    /* Уся навігація (розділи + тема/мова/профіль/вийти) тепер у .sv-drawer —
       і топ-рядок нав, і десктопний кластер перемикачів на мобільному не
       рендеряться, звільняючи смугу під тег-лайн. */
    .sv-nav-links,
    .sv-user {
        display: none;
    }

    .sv-drawer-btn {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 40px;
        height: 40px;
        border: none;
        border-radius: var(--sv-radius-sm);
        background: none;
        color: white;
        flex-shrink: 0;
        position: relative;
    }

    .sv-drawer-btn:active,
    .sv-drawer-btn[aria-expanded="true"] {
        background: rgba(255, 255, 255, 0.12);
    }

    /* Тач-ціль до 44px, вигляд без змін */
    .sv-drawer-btn::after {
        content: "";
        position: absolute;
        inset: -2px;
    }

    /* --- Тег-лайн повертається у смугу (місце звільнило меню) --- */
    /* Один рядок, а не два: висувне меню забрало собі всю навігацію й
       перемикачі, тож у смузі лишились тільки ☰, лого й тег-лайн — двом
       рядкам більше нема чого компенсувати.

       Розмір — clamp(): росте з шириною екрана (гнучко), стеля 1.3rem —
       підібрана порівнянням ЗАГАЛЬНОЇ візуальної ваги рядка з лого
       (скріншот поряд, не висота однієї літери: "SOLARPANEL" — суцільні
       великі жирні літери, а в тег-лайні великі лише перші літери слів,
       тож збіг висоти однієї літери все одно лишав рядок візуально
       дрібнішим).

       Підлога — 0.68rem, НЕ пропорційно піднята разом зі стелею: рядок
       фіксований ("Converting Svitlo into Energy" ніколи не перекладається),
       і на вузьких телефонах (320-375px) правий відступ (нижче) з'їдає
       рівно стільки місця, що піднята підлога (пробували 0.9rem) банально
       не влазила — текст ліз за ліву межу коробки й обрізався БЕЗ
       багатокрапки (text-overflow:ellipsis працює лише для кінця рядка,
       а тут через flex-end переповнення йде з ПОЧАТКУ — оманливо виглядало
       норм і в DOM-розмірах (scrollWidth==clientWidth), і ловилось лише
       порівнянням із canvas.measureText: на 375px рядку бракувало ~7px).
       0.68rem @ 320px лишає ~6px запасу навіть із відступом нижче — далі
       підлога лише зростає, тож на ширших екранах запас більший.
       Лінійна інтерполяція 0.68rem@320px -> 1.3rem@700px (2.61vw + 0.16rem).

       .sv-tagline-slot (обгортка, НЕ клікабельна) несе flex:1/відступ від
       краю/вертикальне центрування — та сама причина, що на десктопі
       (EditorsNoteHairline із незвичною метрикою інакше сидить не по
       центру своєї коробки). Сам <button> нижче — рівно за контентом,
       без stretch: зона натиску має відповідати видимому напису. */
    .sv-topbar .sv-tagline-slot {
        display: flex;
        align-items: center;
        justify-content: flex-end;
        flex: 1 1 auto;
        min-width: 0;
        /* Не рівно 40px (ширина .sv-drawer-btn) — на вузьких екранах це
           з'їдало запас, якого й так небагато (вище). --sv-s4 — той самий
           відступ, що й в інших місцях панелі: помітний, навмисний, але
           лишає рядку дихати на 320-375px. */
        padding-right: var(--sv-s4);
    }

    .sv-topbar .sv-tagline {
        /* display:none лягає на цей самий елемент з @media(max-width:1100px)
           вище (тег-лайн ховається в "середньому" діапазоні, де для нього
           замало місця) — тут навпаки, знову показуємо. */
        display: inline-block;
        /* Лінійна інтерполяція 0.68rem@320px -> 1.3rem@700px, далі стеля. */
        font-size: clamp(0.68rem, 2.61vw + 0.16rem, 1.3rem);
        /* НЕ 1: у EditorsNoteHairline фактичний контур ("чорнило") виходить
           за межі line-height:1 і згори, і знизу порівну (виміряно ~2.8px
           з кожного боку при висоті рядка 20.8px) — низ "g"/"y" ("Energy")
           обрізало. overflow-x:hidden/overflow-y:visible окремо не рішення:
           CSS-специфікація примусово рахує "visible" як "auto", щойно інша
           вісь — не visible (лишається скрол-контейнер, а не справжня
           видимість), тож просто дали рядку більше висоти. 1.3 — з запасом
           над виміряним співвідношенням (~1.27). */
        line-height: 1.3;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        min-width: 0;
    }

    /* --- Тач-цілі: мінімум 44×44 --- */

    /* Висоти навмисно нижчі за 44px: суцільна сітка з 44-піксельних елементів
       на екрані 375×812 виглядала громіздко й з'їдала місце під дані. Норму
       тач-цілі добираємо невидимим ::after — він розширює зону дотику, не
       займаючи місця у верстці. Тому все нижче водночас і компактне,
       і натискається пальцем. */
    .sv-dselect > summary {
        min-height: 38px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        position: relative;
    }

    /* .sv-export (і .sv-export-menu > summary, той самий вигляд) — окремо
       від 38px вище: поруч, у .sv-students-count, стоїть звичайний текстовий
       рядок "Знайдено: N з M" (~19px), і 38px-кнопка виглядала помітно
       вищою за нього. 30px — природна висота кнопки з іконкою й паддінгами
       (28.9px за фактом), тач-зону все одно розширює невидимий ::after. */
    .sv-export,
    .sv-export-menu > summary {
        min-height: 30px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        position: relative;
    }

    .sv-export::after,
    .sv-dselect > summary::after,
    .sv-export-menu > summary::after {
        content: "";
        position: absolute;
        inset: -3px -2px;
    }

    /* Хрестики на чіпах (прибрати умову / видалити підбірку) — 20×20, і
       раніше БЕЗ розширення зони дотику, на відміну від решти контролів.
       Той самий невидимий ::after: вигляд не міняється, палець влучає. */
    .sv-fchip-x,
    .sv-view-del,
    .sv-fcond-del {
        position: relative;
    }

    .sv-fchip-x::after,
    .sv-view-del::after,
    .sv-fcond-del::after {
        content: "";
        position: absolute;
        inset: -12px;
    }

    /* Тумблер «Міняти стадії вручну» — <label> заввишки ~19px. Робимо його
       повноцінною 38px-ціллю (сабмітить одразу по кліку). */
    .sv-admin-grant-toggle {
        min-height: 38px;
        display: inline-flex;
        align-items: center;
    }

    /* Стрілка dselect малюється фоном при `right 14px`, а місце під неї
       резервує padding-right: 34px базового .sv-select. Тут раніше стояв
       padding: 0 12px — він затирав цей резерв, і текст залазив під стрілку. */
    .sv-dselect > summary {
        padding: 0 34px 0 var(--sv-s3);
        justify-content: space-between;
    }

    /* Перемикач мов лишається таким самим компактним, як на десктопі:
       розтягнутий до 44×44 він перетворювався на дві важкі плити, більші
       за лого. Норму тач-цілі добираємо невидимим полем через ::after —
       воно не займає місця у верстці, тож вигляд не змінюється. */
    .sv-lang-btn {
        position: relative;
    }

    .sv-lang-btn::after {
        content: "";
        position: absolute;
        top: 50%;
        left: 0;
        right: 0;
        height: 44px;
        transform: translateY(-50%);
    }

    /* Лого — теж посилання (на головну), тож теж повноцінна тач-ціль */
    .sv-topbar .sv-logo {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
    }

    /* 16px — поріг, нижче якого Safari на iOS примусово зумить сторінку
       при фокусі на полі, після чого верстку «зносить» вбік. Шрифт лишаємо
       16px навіть при нижчих полях — це не про естетику, а про те, щоб
       сторінку не рвало при кожному тапі в пошук.

       .sv-select тут БУЛО раніше і не мало сенсу: це <summary> кастомного
       dselect (templates/_dselect.html), не текстове поле — фокус на ньому
       ніколи не відкриває клавіатуру й не тригерить той самий Safari-зум,
       тож захист від зуму йому не потрібен. А побічний ефект був реальний:
       .sv-select-sm задає min-height:34px (щоб зрівнятись із сусідньою
       кнопкою, напр. "Перевести" у force-stage), але при 16px замість
       0.78rem рядок тексту вже не влазив у ці 34px і висота "спливала"
       понад min-height — на телефоні кнопка й список знову розходились
       по висоті, хоч на десктопі (де цей блок не діє) все збігалось. */
    .sv-search-input,
    .sv-input,
    input[type="text"],
    input[type="email"],
    input[type="search"] {
        font-size: 16px;
    }

    .sv-search-box {
        min-height: 38px;
        min-width: 0;
    }

    /* Власний вертикальний padding поля (10px зверху й знизу) додавався до
       висоти рамки й тягнув її до 44px попри min-height. */
    .sv-search-input {
        min-height: 38px;
        padding: 0;
    }

    .sv-input {
        min-height: 38px;
    }

    /* Кнопки дій — нижчі, але з розширеною зоною дотику.
       :not(.sv-btn-sm) — той самий виняток, що й нижче біля другого
       .sv-btn на мобільному: .sv-btn-sm має власну, ще компактнішу висоту
       (34px), яка на мобільному має збігатися з сусіднім .sv-select-sm
       (force-stage, "Вибрати всі" в /admins), а не підганятись під це
       загальне правило для звичайних кнопок. */
    .sv-btn:not(.sv-btn-sm) {
        min-height: 36px;
        padding: 0 var(--sv-s3);
        position: relative;
    }

    .sv-btn:not(.sv-btn-sm)::after {
        content: "";
        position: absolute;
        inset: -4px 0;
    }

    /* Хрестик закриття модалки був 28×28 — найдрібніша ціль у всій панелі,
       і при цьому єдиний спосіб закрити картку студента. */
    .sv-modal-close {
        width: 44px;
        height: 44px;
    }

    /* Інлайнові посилання (Telegram у профілі) лишаються в потоці тексту,
       але отримують 44px висоти для пальця: padding розширює зону дотику,
       від'ємний margin повертає верстку на місце, тож візуально нічого не
       зсувається. */
    .sv-link {
        display: inline-block;
        padding: 13px 0;
        margin: -13px 0;
    }

    /* --- Тактильний відгук замість :hover --- */

    /* На тачі :hover не існує: людина тапає й до відповіді сервера не бачить
       жодної реакції. Дублюємо всі ховер-стани через :active. */
    .sv-card:active,
    .sv-table-row:active {
        background: var(--sv-row-hover);
        transform: scale(0.995);
    }

    .sv-btn:active {
        transform: translateY(1px);
    }

    .sv-btn:hover,
    .sv-card:hover {
        transform: none;
    }

    /* --- Фільтри під кнопкою --- */

    /* П'ять дропдаунів у стовпчик з'їдали 132px — третину екрана до першого
       рядка даних. Тепер вони згорнуті, видимий лише пошук. */
    .sv-filters {
        gap: var(--sv-s2);
        margin-bottom: var(--sv-s2);
    }

    .sv-filters-toggle {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: var(--sv-s15);
        /* 38px — врівень із полем пошуку та дропдаунами, щоб рядок фільтрів
           читався як одна смуга, а не як сходинки різної висоти. */
        min-height: 38px;
        padding: 0 var(--sv-s3);
        background: var(--sv-surface);
        border: 1px solid var(--sv-border);
        border-radius: 8px;
        font-size: var(--sv-fs-sm);
        font-weight: 700;
        color: var(--sv-text);
        flex: 0 0 auto;
        position: relative;
    }

    .sv-filters-toggle::after {
        content: "";
        position: absolute;
        inset: -3px -2px;
    }

    .sv-filters.is-collapsed .sv-dselect,
    .sv-filters.is-collapsed .sv-mselect {
        display: none;
    }

    /* Конструктор ховається разом зі швидкими фільтрами — цілим елементом.
       Це принципово: правило вище — селектор НАЩАДКА, тож інакше кнопка
       "Умови" лишалась би видимою, а всі дропдауни всередині її попапа
       були б display:none — попап відкривався б порожнім і виглядав
       зламаним. */
    .sv-filters.is-collapsed .sv-fbuilder {
        display: none;
    }

    /* На вузькому екрані панель не спливає, а стає частиною рядка фільтрів:
       абсолютний попап заввишки в кілька умов вилазив би за нижню межу
       екрана, і докрутитись до нього було б неможливо — він не збільшує
       висоту сторінки. У потоці ж сторінка гортається як звичайно, і
       списки опцій нічим не обрізаються. */
    .sv-fbuilder {
        flex: 1 1 100%;
    }

    .sv-fbuilder-panel {
        position: static;
        width: auto;
        margin-top: var(--sv-s2);
        box-shadow: none;
        animation: none;
    }

    /* Контроли в стовпчик — на 375px три дропдауни в рядок нечитабельні.
       Хрестик при цьому опинявся б окремим четвертим рядком, наче ще один
       контрол умови; виносимо його в правий верхній кут блоку, де він
       читається як "прибрати ЦЮ умову", а рядки відділяємо лінією, щоб
       стовпчик із двох умов не зливався в одну купу. */
    .sv-fcond {
        position: relative;
        padding: 0 38px var(--sv-s3) 0;
        border-bottom: 1px solid var(--sv-border);
    }

    .sv-fcond:last-child {
        border-bottom: none;
    }

    .sv-fcond > .sv-dselect,
    .sv-fcond-value {
        flex: 1 1 100%;
    }

    /* Загальне мобільне правило робить <summary> dselect'а inline-flex —
       швидкі фільтри нагорі свідомо виглядають компактними пігулками. Тут
       навпаки: назви полів довгі ("Дата народження (за версією ШІ)"), і в
       пігулці вони обрізались би до невпізнанного. Плюс поруч стоїть
       повноширинний інпут значення, і різна ширина читалась як недоробка. */
    .sv-fcond > .sv-dselect > summary {
        display: flex;
        width: 100%;
    }

    .sv-fcond-del {
        position: absolute;
        top: 0;
        right: 0;
    }

    /* basis 0, а не auto: з auto пошук роздувався на всю ширину рядка й
       виштовхував кнопку «Фільтри» на другий рядок — 96px замість 52px. */
    .sv-filters .sv-search-box {
        flex: 1 1 0;
    }

    .sv-filters .sv-dselect,
    .sv-filters .sv-mselect {
        flex: 1 1 100%;
    }

    /* На телефоні біжучий рядок — окремим рядком на всю ширину під
       пошуком, а не половиною ряду поруч із ним. */
    .sv-ticker {
        flex: 1 1 100%;
        min-height: 38px;
    }

    /* --- Список студентів: таблиця → картки --- */

    /* П'ять колонок фізично не вміщаються у 375px: вміст таблиці був 658px
       при контейнері 325px, тобто видно було лише «Ім'я». Перебудовуємо
       рядок у картку — розмітку не чіпаємо, тож htmx-оновлення працює як є. */
    .sv-table-wrap {
        background: transparent;
        border: none;
        border-radius: 0;
        overflow-x: hidden;
    }

    .sv-table,
    .sv-table tbody,
    .sv-table tr,
    .sv-table td {
        display: block;
    }

    .sv-table thead {
        display: none;
    }

    .sv-table {
        font-size: var(--sv-fs-base);
    }

    /* `.sv-table tr` вище має специфічність 0-1-1 і перебивав би самий
       `.sv-table-row` (0-1-0) — рядок лишався display:block, і вся сітка
       картки не працювала. Тому селектор тут навмисно довший. */
    /* Сітка картки: дві колонки лише в першому рядку (ім'я + бейдж етапу),
       решта — на всю ширину.

       Локація (grid-row:3) і бейдж (grid-row:1) фактично сидять в ОДНІЙ
       колонці 2 — auto-трек рахує свою ширину як максимум з обох, а не
       окремо на кожен рядок (кожен <tr> тут свій grid, але колонки всередині
       нього спільні для всіх його grid-row). Довга локація в одній картці
       розтягувала колонку 2, тим самим стискаючи `1fr` з іменем у тій самій
       картці до майже нуля — тому те саме за довжиною ім'я в одній картці
       ціле, а в іншій обрізане: залежало від того, яка в НЕЇ локація, а не
       від самого імені. minmax(140px, ...) — гарантований мінімум під ім'я/
       хаус незалежно від того, скільки хоче забрати бейдж чи локація. */
    .sv-table tr.sv-table-row {
        display: grid;
        grid-template-columns: minmax(140px, 1fr) auto;
        gap: 1px var(--sv-s2);
        align-items: start;
        background: var(--sv-surface);
        border: 1px solid var(--sv-border);
        border-radius: var(--sv-radius);
        padding: var(--sv-s2) var(--sv-s3);
        margin-bottom: var(--sv-s15);
        box-shadow: var(--sv-shadow-sm);
    }

    .sv-table-row td {
        padding: 0;
        border-bottom: none;
        min-width: 0;
    }

    /* Ім'я + вікова група */
    .sv-table-row td:nth-child(1) {
        grid-column: 1;
        grid-row: 1;
    }

    /* Бейдж етапу. max-width не дає йому забрати більше половини ширини
       картки навіть за найдовшої назви стадії — імені завжди лишається місце.
       justify-self:end — бо кожен <tr> тут свій окремий grid-контейнер
       (не спільна таблична сітка), і auto-колонка бейджа рахується для
       КОЖНОГО рядка окремо від його ж контенту. Без цього права межа
       колонки в різних рядках опинялась на різній відстані від краю картки
       (де-не-де навіть не дотягувала до нього) — бейдж візуально "гуляв",
       а не сидів рівно по правому краю. */
    .sv-table-row td:nth-child(3) {
        grid-column: 2;
        grid-row: 1;
        justify-self: end;
        max-width: 48vw;
    }

    .sv-table-row td:nth-child(3) .sv-table-badge {
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    /* Контакти — на всю ширину, окремим блоком під іменем */
    .sv-table-row td:nth-child(2) {
        grid-column: 1 / -1;
        grid-row: 2;
        padding-top: 3px;
        line-height: 1.35;
    }

    /* Хаус і локація — один службовий рядок під контактами. Обидва
       розкладені явно в третьому рядку, тому auto-трек їх не стосується. */
    .sv-table-row td:nth-child(4) {
        grid-column: 1;
        grid-row: 3;
    }

    .sv-table-row td:nth-child(5) {
        grid-column: 2;
        grid-row: 3;
        text-align: right;
        max-width: 60vw;
    }

    /* Довгі пошти не ховаємо трьома крапками — куратору потрібна вся адреса */
    .sv-table-row td:nth-child(2) > div {
        overflow-wrap: anywhere;
    }

    /* А ось назва міста/країни може бути якою завгодно — її ріжемо, інакше
       вона переносилась на кілька рядків і ламала висоту картки. */
    .sv-table-row td:nth-child(4),
    .sv-table-row td:nth-child(5) {
        font-size: var(--sv-fs-xs);
        color: var(--sv-text-muted);
        padding-top: 3px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    /* Ім'я теж ріжемо: без цього довге прізвище розсовувало картку */
    .sv-table-row .sv-table-name {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    /* Ховер-підсвітка з десктопа тут недоречна — картка вже має свій фон */
    .sv-table-row:hover td,
    .sv-table-row:focus-visible td {
        background: transparent;
    }

    .sv-table-row:hover td:first-child,
    .sv-table-row:focus-visible td:first-child {
        box-shadow: none;
    }

    .sv-table-row:focus-visible {
        outline: 2px solid var(--sv-accent);
        outline-offset: 2px;
    }

    .sv-empty-row {
        background: var(--sv-surface);
        border: 1px solid var(--sv-border);
        border-radius: var(--sv-radius);
    }

    /* --- Дошка CRM: колонки → вертикальний акордеон --- */

    /* Kanban шириною 252px на екрані 375px показував ~1.2 колонки з шести.
       Вертикальний стек зі згортанням дає огляд усієї воронки одним скролом. */
    .sv-board-wrap {
        overflow-x: hidden;
        overflow-y: auto;
        padding: var(--sv-s3);
    }

    .sv-board {
        flex-direction: column;
    }

    /* flex: none — у вертикальному стеку базис 252px з десктопа став би
       ВИСОТОЮ колонки, а не шириною. Селектор той самий, що в десктопного
       правила, інакше воно перебивало б цей відкат вагою. */
    .sv-column,
    .sv-board > .sv-column {
        flex: none;
        width: 100%;
        min-width: 0;
        max-width: none;
    }

    /* Шеврон і приховування нативного маркера тепер глобальні (колонки
       згортаються на будь-якому екрані) — тут лишається тільки збільшена
       ціль дотику. */
    .sv-column > summary {
        min-height: 48px;
    }

    /* --- Модалка --- */

    .sv-modal {
        max-height: 92dvh;
    }

    .sv-modal-actions {
        padding-bottom: calc(var(--sv-s4) + env(safe-area-inset-bottom, 0px));
    }

    /* --- Головний екран --- */

    /* Картки розділів були по ~140px кожна з порожньою правою половиною —
       четверта не влазила в екран. Горизонтальний макет тримає всі чотири
       в межах одного екрана. column-gap/row-gap окремо, а не спільний
       `gap` — той однаково розсував і колонку іконки від тексту (треба),
       і сам заголовок від підпису в текстовій колонці (не треба: там уже
       margin:0 на обох, а grid-gap все одно вставляв свої 12px між рядками
       незалежно від margin). */
    .sv-nav-card {
        display: grid;
        grid-template-columns: 40px minmax(0, 1fr);
        align-items: center;
        column-gap: var(--sv-s3);
        row-gap: var(--sv-s05);
        padding: var(--sv-s3) var(--sv-s4);
    }

    .sv-nav-card-icon {
        grid-row: span 2;
        margin-bottom: 0;
    }

    .sv-nav-card h3 {
        font-size: 0.95rem;
        margin-bottom: 0;
    }

    .sv-nav-card p {
        font-size: 0.78rem;
    }

    /* Підпис — це <p>, і йому діставався браузерний margin-top (~1em), якого
       ніхто не задавав: у .sv-welcome-sub виставлено лише margin-bottom.
       Разом із line-height великого заголовка це давало помітну діру. */
    .sv-welcome {
        font-size: 1.45rem;
        line-height: 1.25;
        margin-bottom: var(--sv-s15);
    }

    .sv-welcome-sub {
        margin-top: 0;
        margin-bottom: var(--sv-s4);
    }

    /* --- Дашборд --- */

    /* Сегментація (conversion_table у _dash_crm.html): та сама таблиця, що
       на десктопі, лишалась на загальному правилі `.sv-table td {
       display:block }` без жодної картки-трансформації (та є лише для
       .sv-table-row, якого тут немає) — рядок розсипався на стовпчик
       голих чисел без підписів, geen жодним чином не позначено, що це
       "Total"/"Students"/... Тому: своя картка на рядок, і "label: число"
       з тих самих перекладів, що в thead (data-label у шаблоні), а не
       голе число. */
    .sv-table-compact tbody tr {
        display: block;
        background: var(--sv-surface);
        border: 1px solid var(--sv-border);
        border-radius: var(--sv-radius);
        padding: var(--sv-s3);
        margin-bottom: var(--sv-s15);
    }

    .sv-table-compact tbody tr > td:first-child {
        margin-bottom: var(--sv-s2);
    }

    .sv-table-compact tbody td {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 3px 0;
        border-bottom: none;
        font-size: 0.85rem;
    }

    .sv-table-compact tbody td:not(:first-child)::before {
        content: attr(data-label);
        color: var(--sv-text-muted);
        font-weight: 600;
        font-size: 0.76rem;
    }

    .sv-table-compact .sv-seg-track {
        max-width: 100%;
    }

    /* Теплокарта — ВИНЯТОК із перетворення "таблиця -> картки" вище.
       Її сенс — порівняння клітинок одна з одною, рядок за рядком і
       стовпець за стовпцем; розсипана в стос пар "стадія: відсоток" вона
       перестає бути тим, чим є. Замість цього лишаємо її СПРАВЖНЬОЮ
       таблицею й повертаємо горизонтальний скрол, який generic-правило
       .sv-table-wrap { overflow-x: hidden } (нижче) вимкнуло для ВСІХ
       таблиць на мобільному заради картко-стосу. :has() тут не про
       прогресивне покращення — ним самим і живе весь опис .sv-nps-row
       вище, підтримка на цьому рівні вже прийнята для панелі. */
    .sv-table-wrap:has(.sv-heat) {
        overflow-x: auto;
        background: var(--sv-surface);
        border: 1px solid var(--sv-border);
        border-radius: var(--sv-radius-lg);
    }

    .sv-heat {
        display: table;
        min-width: 640px;
    }

    .sv-heat thead {
        display: table-header-group;
    }

    .sv-heat tbody {
        display: table-row-group;
    }

    .sv-heat tbody tr {
        display: table-row;
        background: none;
        border: none;
        border-radius: 0;
        margin-bottom: 0;
    }

    .sv-heat thead tr,
    .sv-heat tbody tr {
        border-bottom: 1px solid var(--sv-border);
    }

    /* Специфічність рахована свідомо: .sv-table-compact tbody td важить
       (0,1,2) (клас + два елементи), тож голе ".sv-heat td" (0,1,1)
       програло б каскад навіть стоячи нижче в файлі. tbody в селекторі
       зрівнює вагу, і тоді вирішує порядок оголошення (наш — пізніше). */
    .sv-heat thead th,
    .sv-heat tbody th,
    .sv-heat tbody td {
        display: table-cell;
        padding: 0.5rem 0.7rem;
    }

    .sv-heat tbody td::before {
        content: none;
    }

    /* Плитка «Overall Conversion» займала 430px із 812 — більш ніж півекрана
       на одну цифру. Стискаємо до компактного блоку. */
    .sv-headline {
        gap: var(--sv-s2);
        padding: var(--sv-s4);
    }

    .sv-ring {
        width: 104px;
        height: 104px;
    }

    .sv-ring-value {
        font-size: var(--sv-fs-xl);
    }

    .sv-headline-label {
        font-size: var(--sv-fs-xs);
        line-height: 1.35;
    }

    .sv-headline-base {
        font-size: 0.7rem;
    }

    /* На вузькому екрані назва виходить З бара і стає рядком над ним.
       Раніше вона лежала всередині, і щоб уміститись, бар мусив мати
       min-width 246px — при ширині контейнера 327px це зрівнювало майже
       всі кроки й воронка переставала бути воронкою. Тепер назві місця
       скільки завгодно, а бар вільно звужується до реальної пропорції. */
    .sv-fstep {
        display: flex;
        flex-direction: column;
        gap: 3px;
    }

    .sv-fbar-label {
        position: static;
        height: auto;
        font-size: 0.78rem;
        color: var(--sv-text);
    }

    /* Мінімум лише під числа («46 100.0%»), не під назву */
    .sv-fbar {
        min-width: 92px;
        gap: var(--sv-s2);
        padding: 0 var(--sv-s2);
    }

    .sv-fchart {
        gap: var(--sv-s2);
    }

    .sv-fbar-count {
        font-size: 0.88rem;
    }

    /* Підпис «Свій період» зайвий — «з ... по ...» читається й без нього */
    .sv-custom-range > .sv-range-label {
        display: none;
    }

    /* Кнопка експорту без підпису (той прихований на 480) перетворювалась
       на вузький високий прямокутник. Робимо рівний квадрат — але 44×44
       поруч із заголовком виглядав важкою порожньою плитою, тож малюємо
       34×34, а до норми тач-цілі добираємо невидимим полем (::after),
       яке не займає місця у верстці. */
    .sv-panel-head > .sv-export {
        position: relative;
        width: 34px;
        height: 34px;
        min-height: 0;
        padding: 0;
        justify-content: center;
    }

    .sv-panel-head > .sv-export::after {
        content: "";
        position: absolute;
        inset: -5px;
    }

    .sv-panel-head > .sv-export svg {
        width: 15px;
        height: 15px;
    }

    /* Згорнута колонка дошки лишала під заголовком порожню білу пляму —
       через min-height, розрахований на колонку з картками. */
    .sv-column:not([open]) {
        min-height: 0;
    }

    /* Кнопка експорту була приколота абсолютно (медіа 480) і накривала собою
       текст опису панелі. Повертаємо в потік. */
    /* Селектор із .sv-panel — щоб перебити `align-items: baseline` базового
       правила: за baseline кнопка експорту вирівнювалась по останньому рядку
       заголовка з підписом і «провисала» на 60px нижче за нього. */
    /* Grid, а не flex: заголовок отримує колонку minmax(0,1fr) і переноситься
       ВСЕРЕДИНІ неї, скільки б рядків не зайняв, а кнопка стоїть у своїй
       auto-колонці й ніколи не виштовхується вниз. Опис панелі — окремий
       блок під головою — тепер вільно займає всю ширину, бо кнопка більше
       не висить над ним. */
    .sv-panel .sv-panel-head {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        align-items: start;
        column-gap: var(--sv-s3);
        row-gap: var(--sv-s1);
        padding-right: 0;
    }

    .sv-panel .sv-panel-head > .sv-panel-head-title {
        grid-column: 1;
    }

    /* Кнопка трохи піднята: візуально врівноважує рядок із заголовком,
       у якого line-height більший за її 34px. */
    .sv-panel .sv-panel-head > .sv-export {
        grid-column: 2;
        margin-top: var(--sv-s05);
    }

    .sv-zone-title,
    .sv-panel-head-title {
        min-width: 0;
    }

    /* --- Тач-цілі, яких не вистачало на дашборді та в адмінах --- */

    /* .sv-lang-btn свідомо НЕ тут: тач-ціль для нього вже добрана невидимим
       ::after вище (.sv-lang-btn { position:relative } + ::after 44px) — той
       підхід явно не займає місця у верстці. Тут раніше було ще одне,
       конфліктне правило (min-width/height: 44px просто на самій кнопці),
       яке розтягувало компактну пігулку UA/EN у важкий квадрат — саме тому
       на мобільному вона виглядала не так охайно, як на десктопі. */

    /* 44px тут форсило ту саму "чому некрасиво" історію, що вже була з
       .sv-export/.sv-lang-btn на дашборді (період/дати/вкладки виходили
       помітно товщими за свій десктопний вигляд). Висота — та сама, що в
       .sv-export (30px), тач-ціль — невидимим ::after, не роздуванням
       самого блоку. .sv-date виняток: це <input>, ::after на нативних
       формах ненадійний у браузерах, тож там просто менший min-height. */
    .sv-tab,
    .sv-chip,
    .sv-apply {
        min-height: var(--sv-ctrl-h-chip);
        display: inline-flex;
        align-items: center;
        position: relative;
    }

    .sv-tab::after,
    .sv-chip::after,
    .sv-apply::after {
        content: "";
        position: absolute;
        inset: -4px;
    }

    /* Пігулки періоду (Весь час/7 днів/...) розтягуються на всю ширину
       ряду замість "сидіти" кожна за своїм текстом і лишати нерівні
       проміжки. */
    .sv-chip {
        flex: 1;
        justify-content: center;
        text-align: center;
        /* "Весь час" — найдовший підпис із пробілом — переносився на два
           рядки (кожна пігулка тепер лише 1/4 ширини ряду), а решта трьох
           поміщались в один рядок і виглядало нерівно/поламано. Кегль
           трохи нижчий, ніж на десктопі (сам .sv-chip), щоб напевно
           влазив в один рядок навіть на вузьких 375px. */
        white-space: nowrap;
        font-size: var(--sv-fs-xs);
        padding: 0 var(--sv-s15);
    }

    /* Підпис, два поля дати поруч, кнопка на всю ширину — три компактні
       рядки замість розповзання на всю висоту екрана. flex-basis:100% —
       примусово своїм рядком під пігулками періоду (а не тулиться збоку
       від останньої, якщо раптом лишилось трохи місця), і на всю ширину
       контейнера, а не лише на ширину свого вмісту (полів дат). */
    .sv-custom-range {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        align-items: center;
        gap: var(--sv-s2);
        padding: var(--sv-s3);
        flex-basis: 100%;
        width: 100%;
        box-sizing: border-box;
    }

    .sv-custom-range > .sv-range-label,
    .sv-custom-range > .sv-apply {
        grid-column: 1 / -1;
    }

    .sv-range-field {
        min-width: 0;
    }

    .sv-date {
        /* Сам розмір шрифту (16px) чіпати не можна — нижче поріг, і Safari
           на iOS примусово зумить сторінку при фокусі на полі (див.
           коментар вище про 16px). Але 16px успадковує ще й line-height:1.5
           з body — це й роздувало поле по висоті понад те, що дає сам
           min-height. line-height:1 прибирає той зайвий запас. */
        min-height: 30px;
        line-height: 1;
        width: 100%;
        min-width: 0;
        flex: 1 1 0;
        font-size: 16px;
    }

    .sv-apply {
        font-size: 0.9rem;
        justify-content: center;
    }

    .sv-dselect-list button {
        min-height: 44px;
    }

    /* :not(.sv-btn-sm) — .sv-btn-sm лишається компактним (34px) навіть тут:
       інакше на мобільному він розходився по висоті з сусіднім .sv-select-sm
       (той не форсується до 44px, бо не .sv-btn), як-от "Перевести" проти
       списку стадій у force-stage, чи "Вибрати всі" в /admins. Тач-ціль
       все одно розширюємо невидимим ::after нижче — той самий прийом, що
       вже для .sv-logout/.sv-export/.sv-dselect > summary вище. */
    .sv-btn:not(.sv-btn-sm) {
        min-height: 44px;
    }

    .sv-btn-sm::after {
        content: "";
        position: absolute;
        inset: -5px;
    }

    /* --- Рядок адміна --- */

    /* Роль стискалась до ширини свого тексту й виглядала обрізаною —
       даємо їй мінімальну ширину, щоб уміщалась найдовша назва ролі. */
    .sv-admin-role {
        min-width: 128px;
    }

    .sv-admin-actions .sv-btn {
        flex: 1 1 0;
    }
}


/* ============ Buddy ============
   Дошка переюзає .sv-board / .sv-column / .sv-card з CRM — тут лише те, чого
   там немає: горизонтальна тека нерозподілених над колонками, ряди кнопок
   у картці й блок керування розподілом. */

/* Тека нерозподілених — саме СМУГА на всю ширину, а не п'ята колонка:
   "не призначено" це інша вісь, ніж стадія, і в колонці воно читалось би
   як черговий етап. Картки течуть горизонтально й переносяться. */
.sv-buddy-pool {
    margin-bottom: var(--sv-s4);
}

.sv-buddy-pool-body {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s2);
    align-items: flex-start;
}

.sv-buddy-pool-body .sv-card {
    flex: 0 1 240px;
}

.sv-buddy-pool-body .sv-column-empty {
    flex: 1 1 100%;
}

/* Стадії — ряд кнопок, а не дропдаун: це найчастіша дія на екрані, і два
   кроки "відкрити список → обрати" коштували б щоразу. */
.sv-buddy-stages {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s2);
    align-items: center;
}

.sv-buddy-note {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s2);
    align-items: flex-start;
}

.sv-buddy-note textarea {
    width: 100%;
    resize: vertical;
}

.sv-buddy-config {
    margin-top: var(--sv-s5);
}

.sv-buddy-team {
    display: flex;
    align-items: flex-end;
    gap: var(--sv-s3);
    margin-bottom: var(--sv-s3);
}

.sv-buddy-team .sv-field {
    flex: 1 1 auto;
    min-width: 0;
}

.sv-buddy-team textarea {
    width: 100%;
    resize: vertical;
}

@media (max-width: 767px) {
    .sv-buddy-team {
        flex-direction: column;
        align-items: stretch;
    }

    .sv-buddy-pool-body .sv-card {
        flex: 1 1 100%;
    }
}

/* Форми в блоці керування — це .sv-field (підпис НАД полем) поруч із кнопкою.
   .sv-confirm-row тягне дітей на всю висоту, тож кнопка ставала заввишки як
   "підпис + поле" і візуально відривалась від інпута. Вирівнюємо по нижньому
   краю, щоб кнопка стояла на одній лінії з полем, а не з підписом. */
.sv-buddy-config .sv-confirm-row {
    align-items: flex-end;
    flex-wrap: wrap;
    gap: var(--sv-s3);
}

.sv-buddy-config .sv-confirm-row .sv-field {
    flex: 1 1 220px;
    min-width: 0;
}

.sv-buddy-config .sv-confirm-row .sv-btn {
    flex: 0 0 auto;
}

/* Кнопка команди — під текстареа, а не збоку від неї: поруч із трирядковим
   полем вона висіла в порожнечі. */
.sv-buddy-team {
    align-items: flex-start;
    flex-direction: column;
}

.sv-buddy-team .sv-field {
    width: 100%;
}

/* Попередження в блоці керування (нема кандидатів у баді тощо) — має
   читатись як застереження, а не як черговий сірий підпис. */
/* .sv-panel-note має margin:0, тож попередження прилипало до списку над ним
   і читалось як його останній рядок, а не як окрема заувага. */
.sv-buddy-warn {
    color: var(--sv-text);
    border-left: 3px solid var(--sv-sage);
    padding: var(--sv-s2) var(--sv-s3);
    margin-top: var(--sv-s3);
    background: var(--sv-surface-alt);
    border-radius: 0 var(--sv-radius-sm) var(--sv-radius-sm) 0;
}

.sv-buddy-pool-more {
    flex: 1 1 100%;
    margin: var(--sv-s2) 0 0;
}

/* ============ Конверсія по тижнях ============ */

/* Підказка — спільна з «Воронкою вступу» (static/js/enrol.js). */
.sv-tip {
    position: absolute;
    bottom: calc(100% - 8px);
    transform: translateX(-50%);
    z-index: var(--sv-z-sticky);
    min-width: 132px;
    padding: var(--sv-s2) 10px;
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-sm);
    box-shadow: var(--sv-shadow-md);
    /* Не перехоплює курсор: інакше тултіп під вказівником ганяв би
       pointerleave/pointermove по колу й мерехтів. */
    pointer-events: none;
}

.sv-tip-when {
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    color: var(--sv-text-muted);
    margin-bottom: var(--sv-s1);
}

.sv-tip-row {
    display: flex;
    align-items: baseline;
    gap: var(--sv-s15);
    white-space: nowrap;
}

/* Значення веде, підпис іде за ним — у легенді головне «яка серія», а тут
   читач серію вже знає й хоче число. */
.sv-tip-row b {
    font-size: var(--sv-fs-base);
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

.sv-tip-row span {
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

.sv-tip-note {
    margin-top: var(--sv-s1);
    font-size: 0.68rem;
    color: var(--sv-warning);
    white-space: nowrap;
}

/* ---- «Скільки людей щотижня починають заявку…» ----
   Одна шкала — люди. Колонка на тиждень, поле фіксованої висоти (--plot
   задає шаблон; за тією ж константою він вирішує, чи влазить число в
   стовпчик), зверху запас під відсоток. */
.sv-wk {
    margin: var(--sv-s4) 0 0;
}

.sv-wk-cols {
    display: flex;
    margin: 0;
    padding: 0;
    list-style: none;
}

.sv-wk-col {
    position: relative;
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
}

/* Нижній край поля — спільна базова лінія: колонки стоять впритул, тож
   хайрлайни зливаються в одну вісь. Скруглення — щоб підсвітка тижня на
   наведенні була плиткою, а не різким прямокутником. */
.sv-wk-plot {
    box-sizing: border-box;
    width: 100%;
    height: calc(var(--plot) + 44px);
    padding-top: 44px;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    border-bottom: 1px solid var(--sv-border);
    border-radius: var(--sv-radius) var(--sv-radius) 0 0;
    transition: background var(--sv-dur) var(--sv-ease);
}

/* Стовпчик — «склянка»: контейнер = скільки почали заявку, заливка
   всередині знизу = скільки стали учнями. Контур малюється тінню
   всередину, а не рамкою: рамка додала б 2px і зсунула висоту з даними. */
.sv-wk-bar {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    width: min(52%, 76px);
    min-height: 3px;
    background: var(--sv-trend-total);
    box-shadow: inset 0 0 0 1px var(--sv-trend-edge);
    border-radius: 6px 6px 0 0;
    transform-origin: bottom;
    animation: sv-wk-grow 520ms var(--sv-ease) both;
    animation-delay: calc(var(--i, 0) * 60ms);
}

.sv-wk.is-dense .sv-wk-bar {
    width: 68%;
}

@keyframes sv-wk-grow {
    from { transform: scaleY(0); }
    to { transform: scaleY(1); }
}

@media (prefers-reduced-motion: reduce) {
    .sv-wk-bar {
        animation: none;
    }
}

/* Заливка від того самого нуля. Торець прямий — це рівень усередині
   склянки, а не кінець величини; дійшли всі — тоді бере її скруглення. */
.sv-wk-part {
    display: flex;
    justify-content: center;
    align-items: flex-start;
    width: 100%;
    background: var(--sv-trend-part);
}

.sv-wk-part.is-full {
    border-radius: 6px 6px 0 0;
}

.sv-wk-n {
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Скільки почали — угорі склянки, скільки стали учнями — угорі заливки.
   Текст ніколи не має кольору даних: чорнило на тонованому, білий
   (темна тема — темне чорнило) на заливці. */
.sv-wk-n.is-total {
    position: absolute;
    top: 6px;
    left: 0;
    right: 0;
    text-align: center;
    color: var(--sv-text-muted);
}

.sv-wk-n.is-part {
    padding-top: var(--sv-s15);
    color: var(--sv-trend-part-ink);
}

/* Над торцем: відсоток — головне число тижня; для низьких стовпчиків,
   куди числа не влазять, під ним «2 з 5». */
.sv-wk-cap {
    position: absolute;
    bottom: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-bottom: var(--sv-s15);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sv-s05);
    white-space: nowrap;
}

.sv-wk-pct {
    font-size: var(--sv-fs-md);
    font-weight: 800;
    line-height: 1;
    letter-spacing: -0.01em;
    color: var(--sv-text);
}

.sv-wk-cap-n {
    font-size: var(--sv-fs-xs);
    line-height: 1.1;
    color: var(--sv-text-muted);
    font-variant-numeric: tabular-nums;
}

/* Тиждень, де людей замало, щоб відсоток щось означав: «100%» з однієї
   людини не має звучати нарівні з «79%» із 280. Відсоток тихий, заливка
   приглушена — дані є, але висновку з них не робимо. */
.sv-wk-col.is-small .sv-wk-pct {
    font-size: var(--sv-fs-sm);
    font-weight: 600;
    color: var(--sv-text-muted);
}

.sv-wk-col.is-small .sv-wk-part {
    opacity: 0.55;
}

/* Набір ще триває — частка неминуче зросте. */
.sv-wk-col.is-immature .sv-wk-pct {
    color: var(--sv-text-muted);
}

.sv-wk-date {
    margin-top: var(--sv-s2);
    font-size: var(--sv-fs-xs);
    font-weight: 600;
    color: var(--sv-text-muted);
    font-variant-numeric: tabular-nums;
}

.sv-wk-date.is-skip {
    visibility: hidden;
}

/* Наведення: тиждень стає плиткою, підказка — поруч зі стовпчиком.
   Лише CSS: усі числа вже на графіку, підказка їх тільки збирає. */
.sv-wk-col:hover .sv-wk-plot {
    background: var(--sv-bg);
}

.sv-wk-tip {
    display: none;
    top: 44px;
    bottom: auto;
    left: calc(50% + 46px);
    transform: none;
}

.sv-wk-col:hover .sv-wk-tip {
    display: block;
}

.sv-wk-col:last-child .sv-wk-tip {
    left: auto;
    right: calc(50% + 46px);
}

.sv-wk-key {
    display: inline-block;
    flex-shrink: 0;
    width: 10px;
    height: 10px;
    border-radius: 3px;
}

.sv-wk-key.is-total {
    background: var(--sv-trend-total);
    box-shadow: inset 0 0 0 1px var(--sv-trend-edge);
}

.sv-wk-key.is-part {
    background: var(--sv-trend-part);
}

/* Період календаря — капсула з підписом під датами своїх тижнів. Не поле
   поверх стовпчиків: контекст не лягає на дані, але й не губиться. */
.sv-wk-bands {
    position: relative;
    height: 24px;
    margin-top: 10px;
}

.sv-wk-band {
    position: absolute;
    top: 0;
    height: 22px;
    display: flex;
    align-items: center;
    padding: 0 10px;
    box-sizing: border-box;
    border-radius: var(--sv-radius-pill);
    background: var(--sv-trend-band);
    overflow: hidden;
}

.sv-wk-band-label {
    font-size: var(--sv-fs-xs);
    font-weight: 600;
    color: var(--sv-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.sv-wk-legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2) var(--sv-s4);
    margin-top: var(--sv-s4);
    padding-top: var(--sv-s3);
    border-top: 1px solid var(--sv-border);
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

.sv-wk-legend span {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s15);
}

.sv-wk-legend-pct {
    color: var(--sv-text);
    font-weight: 800;
}

/* Алерт усередині панелі: не червона плашка на весь екран, але помітно
   гучніше за звичайну примітку. Рамкою й тлом, а не кольором тексту —
   всередині лежить таблиця, і фарбувати її рядки було б надміру. */
.sv-alert {
    margin-bottom: var(--sv-s4);
    padding: var(--sv-s3) var(--sv-s4);
    /* --sv-maroon і --sv-tint-red — ті самі токени, що вже несуть «погано»
       в плитках (.sv-tile.is-danger) і рядках таблиць. Свого кольору тут
       немає навмисно: ще один червоний у палітрі означав би два різні
       «погано» на одному екрані. */
    border: 1px solid var(--sv-maroon);
    border-radius: var(--sv-radius);
    background: var(--sv-tint-red);
}

.sv-alert-head {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
    margin-bottom: var(--sv-s2);
    color: var(--sv-maroon);
}

.sv-alert-head svg {
    flex: none;
    width: 1em;
    height: 1em;
}

/* «Вирішено» в рядку «не отримали повідомлення про рішення» (Паша 18.10.2026).
   Обгортка фрагмента (ціль htmx) — без власної коробки: розкладка панелі
   та сама, що й без неї. */
.sv-undelivered-slot {
    display: contents;
}

.sv-resolve-cell {
    width: 1%;
    text-align: right;
    white-space: nowrap;
}

/* Смуга причини блокування. RU-фільтр — інший колір, бо це інший сигнал:
   не «заявка не пройшла розгляд», а «це не наша аудиторія». Той самий
   --sv-maroon, що вже означає «погано» в плитках і рядках таблиць. */
.sv-seg-bar.is-ru {
    background: var(--sv-maroon);
}

/* Поріг поруч зі значенням простою — тихіше за саме значення. */
.sv-muted-inline {
    color: var(--sv-text-muted);
    /* 0.8em у дрібній клітинці давало 10.9px; не менше за мінімум системи */
    font-size: max(0.8em, var(--sv-fs-xs));
}

/* Глосарій етапів воронки: згорнутий за замовчуванням, щоб не розсувати
   панель — його читають один раз, а дивляться на воронку щодня. */
.sv-gloss {
    margin-top: var(--sv-s3);
    font-size: var(--sv-fs-sm);
}

.sv-gloss > summary {
    cursor: pointer;
    color: var(--sv-text-muted);
    font-weight: 600;
    list-style: none;
}

.sv-gloss > summary::-webkit-details-marker {
    display: none;
}

.sv-gloss > summary:focus-visible {
    outline: 2px solid var(--sv-accent);
    outline-offset: 2px;
    border-radius: 3px;
}

.sv-gloss dl {
    margin: var(--sv-s2) 0 0;
    display: grid;
    gap: var(--sv-s1);
}

/* Назва й пояснення в один рядок на десктопі, у два — на вузькому екрані:
   назви етапів довгі, і фіксована колонка їх би обрізала. */
.sv-gloss dl > div {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s1) var(--sv-s2);
}

.sv-gloss dt {
    font-weight: 700;
    color: var(--sv-text);
    min-width: 10rem;
}

.sv-gloss dd {
    margin: 0;
    flex: 1 1 14rem;
    color: var(--sv-text-muted);
}

/* --- Розкривачка з поясненням (.sv-notes) ---
   Той самий .sv-gloss, лише з абзацами замість <dl>: маркер, кегль, фокус
   і поведінка спільні, бо це той самий елемент інтерфейсу. Тут — тільки
   те, чим абзац відрізняється від списку термінів. */
.sv-notes p {
    /* 0.75rem і --sv-text-muted — рівно те, чим був .sv-panel-note, який
       ця розкривачка замінила: згортається розташування, не оформлення. */
    margin: var(--sv-s2) 0 0;
    font-size: 0.75rem;
    color: var(--sv-text-muted);
    line-height: 1.5;
    /* Панелі на всю ширину дають рядок під 140 знаків — на такому око
       губить початок наступного рядка. */
    max-width: 78ch;
}

/* Підсумок став на місце колишнього підпису під заголовком (панелі або
   підзаголовка секції), тож і відступи успадковує ті самі, а не типові
   для .sv-gloss у кінці панелі. */
.sv-panel-head + .sv-notes,
.sv-subhead + .sv-notes {
    margin-top: 0;
    margin-bottom: var(--sv-s3);
}

/* У шапці зони все центроване — підсумок теж. Ширина ФІКСОВАНА (68ch, як
   у .sv-zone-note, який тут стояв): шапка зони — flex-елемент по вмісту,
   тож із самим лише коротким підсумком вона б звузилась до ширини
   заголовка, а на розкритті стрибнула назад до ширини абзацу й посунула
   заголовок. Стала ширина знімає стрибок. */
.sv-zone-head .sv-notes {
    margin-top: var(--sv-s1);
    width: 68ch;
    max-width: 100%;
}

/* Згорнутий <details> на друк не потрапляє — а сховали ми саме те, що
   пояснює числа, тобто роздруківка втратила б застереження й лишила самі
   цифри. ::details-content розкриває вміст лише для друку, не чіпаючи
   екран. Підтримка — Chromium 131+, тобто ті браузери, з яких панель і
   відкривають; в інших рушіях роздруківка лишиться без цих абзаців.
   Це відома межа згортання, а не недогляд: на екрані текст нікуди не
   дівся, він у DOM і за один клік. */
@media print {
    .sv-gloss::details-content {
        content-visibility: visible;
    }
}

/* ============ Когортна теплокарта ============ */

.sv-heat th, .sv-heat td { text-align: center; }
.sv-heat thead th:first-child, .sv-heat tbody th { text-align: left; }

.sv-heat tbody th {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.sv-heat-n {
    color: var(--sv-text-muted);
    font-variant-numeric: tabular-nums;
}

/* Заливка пропорційна значенню, але зі СТЕЛЕЮ 42%, а не 100%.
   Заміряно на реальному тлі сторінки для клітинки зі 100%:
     стеля 55% -> 3.61:1 у світлій темі (нижче порога 4.5:1)
     стеля 42% -> 4.95:1 у світлій, 5.89:1 у темній
   Градієнт при 42% лишається цілком читабельним. Альтернатива — міняти
   колір тексту на кожній клітинці залежно від заливки, але це означало б
   рахувати яскравість у шаблоні. */
.sv-heat-cell {
    background: color-mix(in srgb, var(--sv-funnel-bar) calc(var(--fill, 0%) * 0.42), transparent);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    transition: box-shadow var(--sv-dur-fast) var(--sv-ease);
}

/* Обідок усередині клітинки замість фону рядка: у клітинки власна заливка
   (частка когорти), і рядковий фон її перекрив би — селектор рядка важить
   більше. Заодно це точніше: у теплокарті читають КЛІТИНКУ (тиждень ×
   стадія), а не рядок. */
.sv-heat-cell:hover {
    box-shadow: inset 0 0 0 2px var(--sv-accent);
}

/* Когорта, що ще рухається: НЕ opacity на клітинках. Заміряно: заливка
   й текст клітинки вже мають граничний контраст 5.28:1 при 100% (той самий
   --sv-primary і як заливка, і як колір тексту — за задумом, лінія тренду
   й теплокарта одна палітра), і opacity:0.72 зверху топив його до 2.91:1 —
   нижче порога WCAG AA. Маркер незрілості лишається на підписі тижня
   (колір + курсив) і на зірочці з підказкою — обидва не чіпають контраст
   клітинок з даними. */
.sv-heat tr.is-immature th {
    color: var(--sv-text-muted);
    font-style: italic;
}

.sv-heat-dot {
    color: var(--sv-accent);
    font-weight: 800;
    cursor: help;
}

/* Блок активного семестру в керуванні розподілом Buddy: значення +
   бейдж джерела (з календаря / перевизначено) в один рядок. */
.sv-buddy-semester-info {
    display: flex;
    align-items: baseline;
    gap: var(--sv-s2);
    margin-bottom: var(--sv-s4);
}

/* --- Панель керування Buddy: рядок = людина ---
   Сітка, а не flex: у всіх рядків мають збігатися колонки метрик, інакше
   око не може порівняти двох бадді між собою — а саме заради порівняння
   цей блок і існує. */
.sv-manage-list {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s2);
}

.sv-manage-row {
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-lg);
    background: var(--sv-surface-alt);
    overflow: hidden;
}

/* Рядок людини підсвічується при наведенні так само, як рядок новачка
   всередині (.sv-manage-student:hover) — раніше світились лише студенти. */
.sv-manage-head {
    transition: background var(--sv-dur) var(--sv-ease);
}

/* Фокусний фон — лише з клавіатури: після кліку мишкою рядок лишався
   «підсвіченим», бо фокус залишався на <summary>. */
.sv-manage-head:hover,
html:not([data-input="pointer"]) .sv-manage-head:focus-visible {
    background: var(--sv-row-hover);
}

/* Ширини колонок ФІКСОВАНІ, а не за вмістом: метрики мають стояти одна під
   одною в усіх рядках, інакше числа різної довжини зсувають колонку і
   порівнювати людей поглядом стає неможливо. */
.sv-manage-head {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 270px 200px;
    align-items: center;
    gap: var(--sv-s3);
    padding: var(--sv-s3);
    /* Запас праворуч під шеврон нижче — абсолютний, а не рядком у гріді:
       так само працює і в desktop-сітці (4 колонки), і в мобільному
       однoколоночному стеку, без окремих правил у кожному media-запиті. */
    padding-left: calc(var(--sv-s4) + 24px);
    position: relative;
    cursor: pointer;
    user-select: none;
}

/* Шеврон — той самий візуальний прийом, що на колонках дошки (.sv-column):
   без нього незрозуміло, що рядок узагалі можна розгорнути (саме це й
   було зауважено — на відміну від дошки, тут рядки виглядали як звичайні
   картки, не як щось клікабельне). */
.sv-manage-head::after {
    content: "";
    position: absolute;
    top: 50%;
    left: var(--sv-s4);
    width: 8px;
    height: 8px;
    margin-top: -6px;
    border-right: 2px solid var(--sv-text-muted);
    border-bottom: 2px solid var(--sv-text-muted);
    /* Згорнуто — вправо, розгорнуто — вниз (design.md «Розгортка»). */
    transform: rotate(-45deg);
    transition: transform var(--sv-dur) var(--sv-ease);
    opacity: 0.6;
}

.sv-manage-row[open] > .sv-manage-head::after {
    transform: rotate(45deg);
    margin-top: -6px;
}

.sv-manage-id {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
    flex-wrap: wrap;
    min-width: 0;
}

.sv-manage-name {
    font-weight: 700;
}

.sv-manage-metrics {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--sv-s2);
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

/* Число над підписом, а не поруч: підписи різної довжини («новачків» /
   «не написано») інакше розганяють числа по горизонталі. */
.sv-manage-metric {
    display: flex;
    flex-direction: column;
    line-height: 1.25;
    min-width: 0;
}

.sv-manage-metric > b {
    color: var(--sv-text);
    font-size: var(--sv-fs-sm);
}

.sv-manage-metric > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sv-manage-warn {
    color: var(--sv-warning);
}

.sv-manage-tg {
    justify-content: center;
    width: 100%;
    white-space: nowrap;
}

/* Доступна копія Telegram-посилання з рядка (див. _buddy_manage.html):
   невидима, доки на неї не прийде фокус з клавіатури, — тоді стає
   звичайним рядком-посиланням над списком команди. */
.sv-manage-tg-kbd:not(:focus) {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.sv-manage-tg-kbd:focus {
    display: block;
    margin: 0 var(--sv-s3) 0 calc(var(--sv-s4) + var(--sv-s5));
    font-size: var(--sv-fs-sm);
    font-weight: 600;
    color: var(--sv-primary);
}

/* Причина відмови в «Винятках входу» (сервер кладе в #buddy-override-error). */
.sv-override-error {
    margin: var(--sv-s2) 0 0;
    padding: var(--sv-s2) var(--sv-s3);
    border-left: 2px solid var(--sv-maroon);
    border-radius: var(--sv-radius-sm);
    background: color-mix(in srgb, var(--sv-maroon) 6%, var(--sv-surface));
    color: var(--sv-maroon);
    font-size: var(--sv-fs-sm);
}

/* Другий (колірно інший) бейдж: кому пише команда — «Старші студенти». */
.sv-badge-students {
    background: var(--sv-tint-amber);
    color: var(--sv-warning);
}

/* Зверху — той самий проміжок, що між рядками всередині (gap списку), щоб
   перший бадді не прилипав до рядка лідера. */
.sv-manage-children {
    padding: var(--sv-s2) var(--sv-s3) var(--sv-s3) calc(var(--sv-s4) + 24px);
}

/* Новачки всередині розгорнутого бадді — щільний список, не плитки:
   на цьому рівні потрібне лише ім'я й стадія. */
.sv-manage-students {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s05);
}

/* min-height 44px — мінімальна ціль дотику: рядок новачка клікабельний, і
   на телефоні 39px давали промахи по сусідньому. */
.sv-manage-student {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
    width: 100%;
    min-height: 44px;
    padding: var(--sv-s2) var(--sv-s3);
    border: 0;
    border-radius: var(--sv-radius-sm);
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.sv-manage-student:hover,
html:not([data-input="pointer"]) .sv-manage-student:focus-visible {
    background: var(--sv-row-hover);
}

.sv-manage-student-name {
    flex: 1 1 auto;
    min-width: 0;
}

@media (max-width: 720px) {
    /* Чотири колонки у 375px не вміщаються — усе в один стовпчик. Позиції
       задані явно, бо в DOM кнопка телеграма йде ОСТАННЬОЮ; поруч з іменем
       вона забирала ~160px із 375 і лишала тому два слова на два рядки. */
    .sv-manage-head {
        grid-template-columns: minmax(0, 1fr);
        row-gap: var(--sv-s2);
    }

    .sv-manage-id {
        grid-row: 1;
    }

    .sv-manage-metrics {
        grid-row: 2;
    }

    /* На телефоні кнопки доростають до мінімальної цілі дотику; на десктопі
       лишається компактний .sv-btn-sm, як усюди в панелі. */
    .sv-manage-tg {
        grid-row: 3;
        min-height: 44px;
    }

    .sv-buddy-actions .sv-btn {
        min-height: 44px;
    }

    .sv-manage-children {
        padding-left: var(--sv-s2);
        padding-right: var(--sv-s2);
    }

    /* Шеврон — на рівні імені, а не посередині високого стовпчика: на
       телефоні рядок займає 4 «поверхи», і стрілка посередині висіла
       біля метрик, нічого не позначаючи. Відступ під нього менший —
       інакше вкладені рівні з'їдали третину ширини екрана. */
    .sv-manage-head {
        padding-left: calc(var(--sv-s3) + 20px);
    }

    .sv-manage-head::after {
        top: calc(var(--sv-s3) + 9px);
        left: var(--sv-s3);
        margin-top: 0;
    }

    .sv-manage-row[open] > .sv-manage-head::after {
        margin-top: var(--sv-s1);
    }

    /* Новачок: ім'я першим рядком, група й статус — під ним; інакше
       довге ім'я ламалось на два рядки, а «Older» налазив на нього. */
    .sv-manage-student {
        flex-wrap: wrap;
        row-gap: var(--sv-s05);
    }

    .sv-manage-student-name {
        flex-basis: 100%;
    }

    /* Плитки черг — однакової висоти навіть коли підпис переноситься на
       два рядки, інакше сітка виглядає рваною. */
    .sv-buddy-queues {
        grid-auto-rows: 1fr;
    }

    /* У бадді плиток рівно три (фази без «нерозподілених»): у 2-колоночній
       сітці третя лишалась би сама й займала лише ліву колонку. */
    .sv-buddy-queues .sv-tile:nth-child(3):last-child {
        grid-column: 1 / -1;
    }
}

/* На найвужчих екранах підпис метрики переноситься, а не обрізається: три
   колонки по ~76px не вміщають «опрацьовано». Числа лишаються вирівняними,
   бо колонки однакові, а рядки ростуть однаково. */
/* Метрик три й підписи короткі («новачків», «написати», «відповіли») — тож
   і на найвужчих екранах лишаються три колонки: дві залишали третю метрику
   саму в окремому рядку. Підпис переноситься, а не обрізається. */
@media (max-width: 400px) {

    .sv-manage-metric > span {
        white-space: normal;
    }
}

/* --- Смуга черг дій ---
   Плитка = число зверху, підпис під ним. Окремий layout, бо звичайні
   .sv-tile на дашборді інлайнові й ставлять підпис попереду значення. */
.sv-buddy-queues .sv-tile {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sv-s05);
    border-radius: var(--sv-radius-lg);
}

.sv-buddy-queues .sv-tile-value {
    font-size: 1.6rem;
    line-height: 1.1;
}

.sv-buddy-queues .sv-tile .sv-btn {
    margin-top: var(--sv-s2);
}

/* Плитка фази — перемикач, тож це <button>: скидаємо браузерні стилі кнопки
   до вигляду звичайної плитки. Обрана фаза — смуга й контур акцентом, як
   активна вкладка; фаза, що ще не відкрилась, — приглушена. */
.sv-phase-tile {
    position: relative;
    width: 100%;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.sv-phase-tile.is-active {
    border-color: var(--sv-accent);
    border-top-color: var(--sv-accent);
    box-shadow: var(--sv-shadow-md);
}

/* Неактивна плитка тьмяніє ЧИСЛОМ, а не цілком: прозорість усієї плитки
   опускала підписи до 2.7:1, хоча саме підпис пояснює, коли фаза відкриється. */
:is(.sv-phase-tile.is-closed:not(.is-active), .sv-buddy-queues .sv-tile.is-empty) .sv-tile-value {
    opacity: 0.45;
}

.sv-phase-tile:focus-visible {
    outline: 2px solid var(--sv-focus);
    outline-offset: 2px;
}

.sv-phase-opens {
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
}

/* Кружечок «треба діяти» в правому верхньому куті плитки фази. Червоний —
   коли щось уже прострочено, інакше акцентний: сама наявність задач ще не
   тривога. */
.sv-phase-badge {
    position: absolute;
    top: var(--sv-s3);
    right: var(--sv-s3);
    min-width: 24px;
    height: 24px;
    padding: 0 7px;
    border-radius: 12px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--sv-fs-xs);
    font-weight: 800;
    color: var(--sv-on-tint);
    background: var(--sv-tint-sage);
}

.sv-phase-badge.is-overdue {
    color: var(--sv-strong-text);
    background: var(--sv-maroon);
}

.sv-column[data-stage="silent"] .sv-column-accent {
    background: var(--sv-maroon);
}

/* --- Налаштування й звіти Buddy (/buddy/settings, /inactive, /summary) --- */
.sv-bs .sv-panel {
    margin-bottom: var(--sv-s5);
}

.sv-bs-back {
    display: inline-flex;
    align-items: center;
    min-height: 24px;   /* ціль натискання WCAG 2.5.8 */
    color: var(--sv-text-muted);
    text-decoration: none;
    font-size: 0.86rem;
}

.sv-bs-h1 {
    font-size: 1.4rem;
    margin: var(--sv-s2) 0 var(--sv-s4);
}

.sv-bs-h3 {
    font-size: 0.95rem;
    margin: var(--sv-s4) 0 var(--sv-s2);
}

.sv-bs-flash {
    margin-bottom: var(--sv-s4);
    padding: var(--sv-s3) var(--sv-s4);
    border: 1px solid var(--sv-sage);
    border-radius: var(--sv-radius);
    background: var(--sv-tint-sage);
    color: var(--sv-on-tint);
}

/* Чат зведення бадді на сторінці System: поле й кнопка в один ряд. */
.sv-digest-chat {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--sv-s3);
}

.sv-digest-chat .sv-field {
    flex: 0 1 280px;
}

#digest-chat-panel :is(.sv-bs-flash, .sv-alert) {
    margin: var(--sv-s3) 0 0;
}

.sv-bs-form {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s3);
    margin-top: var(--sv-s3);
}

.sv-bs-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
    gap: var(--sv-s3);
}

.sv-bs-pair {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
    gap: var(--sv-s3);
}

.sv-bs-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s2);
}

/* Друга дія секції (перечитати календар, тест зведення) — окремо від
   «Зберегти» основної форми, а не впритул під нею. */
.sv-bs-form + :is(form, .sv-bs-actions) {
    margin-top: var(--sv-s4);
}

/* Кнопка з поясненням поруч: на широкому — в рядок, на вузькому підпис
   переноситься цілим блоком під кнопку, а не обтікає її. */
.sv-bs-inline {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2) var(--sv-s3);
}

.sv-bs-dates {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s3);
    margin: var(--sv-s3) 0;
}

.sv-bs-date-card {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s05);
    padding: var(--sv-s3);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    font-size: 0.82rem;
    min-width: 180px;
}

.sv-bs-warn {
    color: var(--sv-warning);
    font-size: var(--sv-fs-xs);
}

.sv-bs-preview {
    white-space: pre-wrap;
    font: inherit;
    font-size: 0.82rem;
    margin: var(--sv-s2) 0 0;
    padding: var(--sv-s3);
    border-radius: var(--sv-radius-sm);
    background: var(--sv-surface-alt);
}

.sv-bs-testlinks {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s2);
    margin: var(--sv-s3) 0;
    padding: var(--sv-s3);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
}

.sv-bs-testlink {
    display: grid;
    grid-template-columns: 180px minmax(0, 1fr) auto auto;
    gap: var(--sv-s2);
    align-items: center;
    font-size: 0.86rem;
}

.sv-bs-testlink code {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--sv-text-muted);
}

@media (max-width: 720px) {
    .sv-bs-testlink {
        grid-template-columns: 1fr auto auto;
    }

    .sv-bs-testlink > span {
        grid-column: 1 / -1;
    }
}

.sv-bs-question {
    display: grid;
    grid-template-columns: 24px 1fr 140px auto;
    gap: var(--sv-s2);
    align-items: center;
}

.sv-bs-question.is-new {
    opacity: 0.8;
}

.sv-bs-qnum {
    color: var(--sv-text-muted);
    font-weight: 700;
    text-align: center;
}

.sv-bs-grants {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.86rem;
}

.sv-bs-grants th,
.sv-bs-grants td {
    padding: var(--sv-s2);
    border-bottom: 1px solid var(--sv-border);
    text-align: left;
}

.sv-bs-grants td:not(:first-child),
.sv-bs-grants th:not(:first-child) {
    text-align: center;
}

@media (max-width: 720px) {
    .sv-bs-question {
        grid-template-columns: 24px 1fr;
    }
}

/* --- Звіти Buddy: «Неактивні» і «Підсумок» --- */
.sv-bi-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--sv-s3);
}

.sv-bi-picker {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
    margin-bottom: var(--sv-s4);
}

.sv-bi-table td {
    vertical-align: top;
}

.sv-bi-form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
}

.sv-bi-form .sv-input:not(select) {
    min-width: 180px;
    flex: 1;
}

.sv-bi-ok {
    color: var(--sv-sage);
    font-size: var(--sv-fs-xs);
}

.sv-link-btn {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    font-weight: 700;
    color: var(--sv-text);
    cursor: pointer;
    text-align: left;
}

.sv-link-btn:hover {
    text-decoration: underline;
}

.sv-bsum-q {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sv-s2) var(--sv-s3);
    padding: var(--sv-s3) 0;
    border-bottom: 1px solid var(--sv-border);
}

.sv-bsum-q > b {
    flex: 1 1 100%;
    font-weight: 600;
}

.sv-bsum-avg {
    font-size: 1.2rem;
    font-weight: 800;
}

.sv-bsum-answers {
    margin: var(--sv-s2) 0 0;
    padding-left: var(--sv-s4);
    font-size: 0.86rem;
    white-space: pre-wrap;
}

/* Посилання на сторінки Buddy над плитками фаз — тихі, праворуч: це
   навігація для хеда/власника, а не частина роботи бадді. */
.sv-buddy-nav {
    display: flex;
    justify-content: flex-end;
    gap: var(--sv-s3);
    margin-bottom: var(--sv-s3);
    font-size: 0.86rem;
}

.sv-buddy-nav a {
    display: inline-flex;
    align-items: center;
    min-height: 24px;   /* ціль натискання WCAG 2.5.8 */
    color: var(--sv-text-muted);
    text-decoration: none;
}

.sv-buddy-nav a:hover {
    color: var(--sv-text);
}

/* --- Публічна форма фідбеку (/f/…) ---
   Одна вузька колонка з великими цілями для пальця: її відкривають з
   телефона за посиланням із повідомлення бадді. */
.sv-fb-page {
    background: var(--sv-bg);
    color: var(--sv-text);
}

.sv-fb {
    max-width: 560px;
    margin: 0 auto;
    padding: var(--sv-s5) var(--sv-s4) var(--sv-s6, 48px);
}

.sv-fb .sv-brand-logo {
    width: 160px;
    margin: 0 auto var(--sv-s4);
}

.sv-fb-test {
    margin-bottom: var(--sv-s4);
    padding: var(--sv-s2) var(--sv-s3);
    border: 1px dashed var(--sv-warning);
    border-radius: var(--sv-radius-sm);
    color: var(--sv-warning);
    font-size: 0.86rem;
}

.sv-fb-intro {
    font-size: var(--sv-fs-md);
    line-height: 1.55;
    margin-bottom: var(--sv-s5);
}

.sv-fb-form {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s4);
}

.sv-fb-q {
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    background: var(--sv-surface);
    padding: var(--sv-s4);
    margin: 0;
}

.sv-fb-q.is-missing {
    border-color: var(--sv-maroon);
}

.sv-fb-q legend {
    font-weight: 700;
    line-height: 1.4;
    padding: 0;
    margin-bottom: var(--sv-s3);
    float: left;
    width: 100%;
}

.sv-fb-q textarea {
    clear: both;
    width: 100%;
    font: inherit;
    /* 16px: з меншим кеглем iOS Safari зумить сторінку при фокусі поля. */
    font-size: var(--sv-fs-md);
    padding: var(--sv-s2) var(--sv-s3);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-sm);
    background: var(--sv-surface-alt);
    color: var(--sv-text);
}

.sv-fb-scale {
    clear: both;
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: var(--sv-s2);
}

.sv-fb-point input {
    position: absolute;
    opacity: 0;
}

.sv-fb-point span {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 48px;
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-sm);
    font-weight: 700;
    cursor: pointer;
    background: var(--sv-surface-alt);
}

/* Обрана оцінка — суцільна заливка, а не золота рамка: рамка на світлому
   тлі мала 1.9:1 і вибір було видно лише при уважному погляді. Текст —
   найтемніший тон бренду в обох темах (~8:1 на золоті). */
.sv-fb-point input:checked + span {
    border-color: var(--sv-accent);
    background: var(--sv-accent);
    color: var(--sv-primary-dark);
}

.sv-fb-q textarea:hover {
    border-color: var(--sv-text-muted);
}

.sv-fb-q.is-missing textarea {
    border-color: var(--sv-maroon);
}

.sv-fb-point input:focus-visible + span {
    outline: 2px solid var(--sv-focus);
    outline-offset: 2px;
}

.sv-fb-scale-hint {
    display: flex;
    justify-content: space-between;
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    margin-top: var(--sv-s2);
}

/* Рядок під помилку є завжди (порожній, поки помилки немає) — висота
   зарезервована, тож поява тексту нічого не зсуває. */
.sv-fb-error {
    clear: both;
    min-height: 1.4em;
    margin: var(--sv-s1) 0 0;
    color: var(--sv-maroon);
    font-size: var(--sv-fs-sm);
    line-height: 1.4;
}

/* Зведення після «Надіслати»: що саме лишилось, з переходом до питання. */
.sv-fb-summary {
    padding: var(--sv-s3) var(--sv-s4);
    border: 1px solid var(--sv-maroon);
    border-radius: var(--sv-radius);
    background: color-mix(in srgb, var(--sv-maroon) 6%, var(--sv-surface));
    color: var(--sv-text);
}

.sv-fb-summary ul {
    margin: var(--sv-s2) 0 0;
    padding-left: var(--sv-s5);
}

.sv-fb-summary a {
    color: var(--sv-maroon);
}

.sv-fb-submit[aria-busy="true"] {
    opacity: 0.7;
    cursor: progress;
}

.sv-fb-submit {
    min-height: 48px;
    font-size: var(--sv-fs-md);
}

/* --- Блок фази в картці новачка: історія відповідей і наступний крок --- */
.sv-buddy-step {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s3);
}

.sv-buddy-step-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sv-s3);
}

.sv-buddy-step-head h4 {
    margin-bottom: 0;
}

/* Один ритм відступів у блоці: усе, що тут стоїть, розділяє тільки gap
   колонки. Загальне `.sv-modal-section > form + form` додавало ще й margin
   між формами — звідси й нерівні проміжки. */
.sv-buddy-step form,
.sv-buddy-step form + form {
    margin: 0;
}

/* Кнопки кроку — в один рядок, переносяться на вузькому екрані. */
.sv-buddy-step-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--sv-s2);
}

.sv-buddy-step-actions > form {
    flex: 0 0 auto;
}

/* Форма відповіді "розчиняється": її textarea займає рядок, а кнопка
   «Отримав відповідь» стає в один ряд зі «Скасувати». */
.sv-buddy-step-actions > .sv-buddy-reply {
    display: contents;
}

.sv-buddy-step-actions > .sv-buddy-reply > textarea,
.sv-buddy-step-actions > .sv-buddy-reply > p:not([hidden]) {
    flex: 1 1 100%;
}

/* Відповідь учня — цитата з лінією зліва, а не ще одна рамка: це вже
   сказане, а не поле, яке треба заповнити. */
.sv-buddy-quote {
    margin: 0;
    border-left: 3px solid var(--sv-sage);
    border-radius: 0;
    padding: var(--sv-s2) var(--sv-s3);
    background: var(--sv-surface-alt);
}

.sv-buddy-quote figcaption {
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    margin-bottom: var(--sv-s1);
}

.sv-buddy-quote blockquote {
    margin: 0;
    white-space: pre-wrap;
    font-size: 0.86rem;
}

/* Готове повідомлення — рамка, яку видно як «ось що надіслати»; текст
   зберігає переноси рядків шаблону. */
.sv-buddy-message {
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    background: var(--sv-surface-alt);
    padding: var(--sv-s3);
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sv-s2);
}

.sv-buddy-message pre {
    margin: 0;
    white-space: pre-wrap;
    word-break: break-word;
    font: inherit;
    font-size: 0.86rem;
    line-height: 1.5;
}

.sv-buddy-reply {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sv-s2);
}

.sv-buddy-reply textarea {
    width: 100%;
}

.sv-buddy-reply textarea[aria-invalid="true"] {
    border-color: var(--sv-maroon);
}

/* Своє пояснення замість системної бульбашки «Заповніть це поле». */
.sv-buddy-reply-err {
    margin: 0;
    font-size: var(--sv-fs-xs);
    color: var(--sv-danger-text, var(--sv-maroon));
}

.sv-buddy-reply-err[hidden] {
    display: none;
}

.sv-input + .sv-buddy-alt {
    margin-top: var(--sv-s2);
}

.sv-buddy-alt > summary {
    cursor: pointer;
    font-size: 0.82rem;
    color: var(--sv-text-muted);
}

.sv-buddy-alt[open] > summary {
    margin-bottom: var(--sv-s2);
}

.sv-buddy-feedback .sv-modal-row > span {
    flex-shrink: 1;
}

.sv-buddy-queues .sv-seg-track {
    width: 100%;
    margin-top: var(--sv-s2);
}

/* Смуга цифр і панель керування — два різні блоки, а не одна строката
   стрічка: між ними потрібен виразний повітряний проміжок. */
.sv-buddy-queues {
    margin-bottom: var(--sv-s5);
}

.sv-buddy-manage {
    margin-bottom: var(--sv-s5);
}

/* --- Повітря в картці новачка ---
   `.sv-panel-note` має margin:0 (він розрахований на щільні панелі), і в
   модалці пояснення прилипало до кнопок над ним. Так само друга форма в
   секції (зняти призначення) стояла впритул до першої. */
.sv-modal-body .sv-panel-note + .sv-panel-note {
    margin-top: var(--sv-s2);
}

.sv-modal-section > form + form,
.sv-modal-section > .sv-confirm-row + form,
.sv-modal-section > .sv-panel-note + form {
    margin-top: var(--sv-s3);
}

/* --- «Хто веде» в картці новачка ---
   Роль | ім'я | текстове посилання. Раніше це жило бейджами в шапці
   модалки, де ім'я й кнопка не вміщались і наїжджали одне на одне. */
.sv-team-row {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--sv-s2);
    padding: var(--sv-s1) 0;
}

.sv-team-role {
    flex: 0 0 64px;
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    font-weight: 700;
}

.sv-team-name {
    flex: 1 1 auto;
    min-width: 0;
    font-weight: 700;
}

.sv-team-link {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    min-height: 24px;               /* ціль натискання WCAG 2.5.8 */
    font-size: var(--sv-fs-xs);
    color: var(--sv-accent-text);   /* золото як текст на світлому тілі модалки (було 2:1) */
}

/* Список і обидві кнопки — одним рядком. Вкладена форма теж flex, інакше
   select і «Призначити» лягали б одне під одне всередині неї. */
.sv-team-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
    margin-top: var(--sv-s3);
}

.sv-team-actions form {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
    min-width: 0;
}

/* Список стискається, щоб усі три контроли влізли в один рядок модалки;
   на вузькому екрані вони все одно переносяться — і хай, бо стиснути їх
   до нечитабельного гірше. Був нативний <select> — тепер .sv-dselect
   (той самий кастомний dropdown, що й усюди в панелі): нативний і сам
   рендерився по-різному в кожній ОС, і на телефоні довге ім'я налазило
   на її власну стрілку впритул, без жодного контролю з нашого боку. */
.sv-team-actions .sv-dselect {
    flex: 1 1 120px;
    min-width: 0;
    max-width: 168px;
}

.sv-team-actions .sv-dselect .sv-select {
    width: 100%;
}

/* Історія дій у картці новачка — той самий "не щоденний вжиток, тримати
   згорнутим" прийом, що ручний склад команд. Список подій (.sv-access-log*)
   перевикористовує класи з журналу змін доступів адміна — та сама форма
   рядка (коли | хто | що), лише вміст "що" тут інший. */
.sv-buddy-history > summary {
    cursor: pointer;
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    user-select: none;
}

.sv-buddy-history[open] > summary {
    margin-bottom: var(--sv-s2);
}

/* Історія дій у картці студента (_profile_modal.html) — той самий прийом і
   ті самі класи списку (.sv-access-log*), що й вище для баді. */
.sv-audit-history > summary {
    cursor: pointer;
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    user-select: none;
}

.sv-audit-history[open] > summary {
    margin-bottom: var(--sv-s2);
}

/* Рядок лідера в конфігу має ВЛАСНУ сітку, а не subgrid від .sv-admin-list:
   там шість колонок під список адмінів, і три дитини цього рядка лягали в
   1-2-3, де друга — `minmax(0,1fr)`. Через це перший селект розтягувало до
   ~1000px, а другий лишався 90px. */
.sv-lead-row {
    grid-template-columns: minmax(0, 1fr) 170px 170px;
    column-gap: var(--sv-s3);
}

.sv-lead-field {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s1);
    min-width: 0;
}

.sv-lead-field .sv-dselect,
.sv-lead-field form {
    width: 100%;
}

@media (max-width: 720px) {
    .sv-lead-row {
        grid-template-columns: minmax(0, 1fr);
        row-gap: var(--sv-s2);
    }
}

/* Посилання на телеграм бадді поруч із бейджем «кому призначено». */
.sv-claim-link {
    text-decoration: none;
    cursor: pointer;
}

.sv-claim-link:hover,
.sv-claim-link:focus-visible {
    text-decoration: underline;
}

/* Заголовки секцій усередині «Керування розподілом» читаються як підзаголовки
   панелі, а не як дрібні службові підписи: глобальний .sv-section-divider —
   приглушений капс 0.72rem, і три такі рядки поспіль зливались у сірий шум.
   Звужено саме до цієї панелі, щоб не міняти вигляд /admins без потреби. */
.sv-buddy-config .sv-section-divider {
    color: var(--sv-text);
    font-size: 0.92rem;
    font-weight: 800;
    text-transform: none;
    letter-spacing: 0;
    /* Лінія тепер ПЕРЕД заголовком (border-top), а не праворуч від нього:
       базовий .sv-section-divider::after малював сірий хвостик збоку від
       напису, і той не читався як межа між блоками — лінія над заголовком
       і є та межа, яку тут просили. */
    margin-top: var(--sv-s5);
    padding-top: var(--sv-s4);
    border-top: 1px solid var(--sv-border);
}

.sv-buddy-config .sv-section-divider::after {
    content: none;
}

/* Підпис над списком активних винятків — щоб не зливався з формою над ним
   (details ручного додавання) чи з підказкою пошуку. */
.sv-buddy-config p.sv-field-label {
    margin: var(--sv-s4) 0 var(--sv-s2);
}

/* Білі акценти всередині блоків: цифра важить більше, ніж слово біля неї. */
.sv-buddy-config .sv-admin-email b,
.sv-buddy-config .sv-panel-note b {
    color: var(--sv-text);
    font-weight: 800;
}

/* Пошук над колонками стадій — не на всю ширину дошки: дошка горизонтально
   гортається (.sv-board-wrap), і поле на всю ширину контенту виглядало б
   вужчим за фактичний viewport щоразу, як колонок більше, ніж влазить. */
.sv-buddy-card-search {
    max-width: 340px;
    margin-bottom: var(--sv-s4);
}

.sv-buddy-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s3);
    margin-bottom: var(--sv-s2);
}

/* Ручний склад команд — <details>, згорнутий: 50 рядків із селектами
   розпихали решту сторінки вниз, а основний шлях тепер автоматичний. */
.sv-buddy-roster-manual {
    margin-top: var(--sv-s4);
}

.sv-buddy-roster-manual > summary {
    cursor: pointer;
    font-size: var(--sv-fs-xs);
    color: var(--sv-text-muted);
    font-weight: 700;
    user-select: none;
    /* Було 0: заголовок і підказка під ним стояли впритул одне до одного
       (і так само підказка до поля пошуку нижче) — текст читався як єдина
       злипла стрічка замість трьох окремих елементів. */
    margin-bottom: var(--sv-s2);
}

/* Прямі діти <details> — підказка (завжди) і попередження «нема бадді»
   (коли список порожній): обидві йдуть одразу під заголовком і потребують
   того самого відступу зверху. */
.sv-buddy-roster-manual > .sv-panel-note {
    margin-top: var(--sv-s1);
}

.sv-buddy-roster-search {
    margin-top: var(--sv-s3);
    /* Менше, ніж було: пошук — це елемент керування списком нижче, і має
       читатись як його частина, а не як окрема секція з чималим проміжком
       до перших тайлів. */
    margin-bottom: var(--sv-s1);
}

/* .sv-admin-list сам по собі має margin-top:var(--sv-s4) (загальний відступ
   секції) — тут його зменшено: тайли мають бути ВІЗУАЛЬНО прив'язані саме
   до поля пошуку над ними, а не висіти окремим блоком. */
.sv-buddy-roster-search + .sv-admin-list {
    margin-top: var(--sv-s2);
}

/* .sv-admin-row задає display:grid без умов — самого [hidden] на елементі
   недостатньо, бо однакова специфічність з UA-стилем програє авторському
   правилу. Клієнтський пошук ставить .hidden властивість напряму, тож це
   правило мусить існувати саме тут, інакше приховані рядки лишаться grid. */
.sv-buddy-roster-row[hidden] {
    display: none;
}


/* ============ Цілі натискання (WCAG 2.5.8; 44px на телефоні) ============
   Знахідки патруля tools/ui-qa (small-target, axe target-size). Малі текстові
   й іконкові кнопки лишаються візуально такими ж — на десктопі їм дано
   мінімум 24px висоти, на телефоні дотик ловить невидимий ::after 44×44
   (той самий прийом, що вже стоїть на .sv-btn-sm, .sv-tab, .sv-chip). */
.sv-linklike,
.sv-ef-input,
.sv-lang-btn,
.sv-tagline,
.sv-gloss > summary,
.sv-buddy-alt > summary,
.sv-buddy-roster-manual > summary {
    min-height: 24px;
}

/* Чекбокс у клітинці таблиці: ціль — уся клітинка, а не сам квадратик. */
.sv-cell-check {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 28px;
    cursor: pointer;
}

.sv-cell-check input {
    accent-color: var(--sv-check);
    cursor: pointer;
}

@media (max-width: 767px) {
    /* Поля вводу й пошук на телефоні — повні 44px, як і кнопки (.sv-btn
       уже 44px у мобільному блоці вище): поруч вони стояли на 2–6px нижче. */
    :root {
        --sv-ctrl-h: 44px;
    }

    .sv-search-input {
        min-height: var(--sv-ctrl-h);
    }

    .sv-fresh-btn,
    .sv-dash-export,
    .sv-linklike,
    .sv-ef-input,
    .sv-bs-back,
    .sv-modal-close,
    .sv-gloss > summary,
    .sv-buddy-alt > summary,
    .sv-buddy-roster-manual > summary,
    .sv-buddy-nav a,
    .sv-team-link,
    .sv-board-refresh {
        position: relative;
    }

    .sv-fresh-btn::after,
    .sv-dash-export::after,
    .sv-linklike::after,
    .sv-ef-input::after,
    .sv-bs-back::after,
    .sv-modal-close::after,
    .sv-gloss > summary::after,
    .sv-buddy-alt > summary::after,
    .sv-buddy-roster-manual > summary::after,
    .sv-buddy-nav a::after,
    .sv-team-link::after,
    .sv-board-refresh::after {
        content: "";
        position: absolute;
        top: 50%;
        left: 50%;
        width: max(100%, 44px);
        height: max(100%, 44px);
        transform: translate(-50%, -50%);
    }
}

/* ============ Communications: форми ============ */

/* Список і сторінка форми — це читання й правка тексту, а не дашборд на
   всю ширину: 1100px тримає рядки форм і майбутній конструктор у зручній
   для очей колонці. */
.sv-comms {
    max-width: 1100px;
}

/* «Скоро» — те, що вже має своє місце в інтерфейсі, але ще не працює.
   Сіре й неклікабельне, з короткою позначкою, а не сховане: видно, куди
   модуль росте, і сторінка не перебудовується, коли функція з'явиться. */
.sv-soon {
    display: inline-block;
    margin-left: var(--sv-s1);
    padding: var(--sv-pill-pad-top) var(--sv-s15) var(--sv-pill-pad-bottom);
    border-radius: var(--sv-radius-pill);
    background: var(--sv-bg);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    line-height: 1;
    font-weight: 700;
    vertical-align: middle;
}

.sv-tab-soon {
    display: inline-flex;
    align-items: center;
    opacity: 0.55;
    cursor: default;
}

.sv-tab-soon:hover {
    color: var(--sv-text-muted);
}

.sv-comms-new {
    grid-column: 3;
    justify-self: end;
}

.sv-forms-notice {
    margin: 0 0 var(--sv-s3);
    padding: var(--sv-s2) var(--sv-s3);
    border-radius: var(--sv-radius-sm);
    background: var(--sv-tint-sage);
    color: var(--sv-on-tint);
    font-size: var(--sv-fs-sm);
}

.sv-forms-notice.is-error {
    background: var(--sv-tint-red);
}

/* «Telegram-бот»: колір кнопки — кружечок кольору Telegram перед назвою
   (зелена, синя, червона; «Звичайна» — сіра рамка). */
.sv-fs-swatch {
    display: inline-block;
    width: 0.7em;
    height: 0.7em;
    margin-right: var(--sv-s1);
    border-radius: 50%;
    vertical-align: calc(0.5cap - 0.35em);
}

.sv-fs-swatch.is-success {
    background: var(--sv-tg-success);
}

.sv-fs-swatch.is-primary {
    background: var(--sv-tg-primary);
}

.sv-fs-swatch.is-danger {
    background: var(--sv-tg-danger);
}

.sv-fs-swatch.is-plain {
    border: 1px solid var(--sv-text-muted);
}

/* «Налаштування» форми: залежний блок підтягується фоном (forms.js
   refreshLive) — поки так, список притушено. */
.sv-fs-links-panel[aria-busy="true"] :is(.sv-fs-links-list, .sv-fs-hint) {
    opacity: 0.55;
    transition: opacity var(--sv-dur) var(--sv-ease);
}

.sv-forms-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sv-s2) var(--sv-s4);
    margin-bottom: var(--sv-s3);
}

/* «Мої / Усі» — вкладки з лінією під вибраною; «Активні / Архів» —
   перемикач праворуч і тоді, коли вкладок немає (без права на всі форми). */
.sv-forms-status {
    margin-left: auto;
}

.sv-forms-view {
    display: inline-flex;
    gap: var(--sv-s4);
}

.sv-forms-view-tab {
    padding: var(--sv-s1) 0;
    border-bottom: 2px solid transparent;
    color: var(--sv-text-muted);
    font-weight: 700;
    text-decoration: none;
}

.sv-forms-view-tab.is-active {
    border-bottom-color: var(--sv-sage);
    color: var(--sv-text);
}

.sv-forms-view-n {
    color: var(--sv-text-muted);
    font-weight: 600;
}

/* Сегменти: кілька коротких варіантів одним рядком (список форм «Мої / Усі»,
   короткі варіанти в налаштуваннях форми). */
.sv-seg-control,
.sv-fs-seg {
    display: inline-flex;
    flex-wrap: wrap;
    justify-self: start;
    max-width: 100%;
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-sm);
    overflow: hidden;
}

.sv-seg-option,
.sv-fs-seg > .sv-fs-radio {
    position: relative;
    display: inline-flex;
    align-items: center;
    min-height: 34px;
    margin: 0;
    padding: 0 var(--sv-s3);
    border: 0;
    border-radius: 0;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
}

.sv-seg-option + .sv-seg-option,
.sv-fs-seg > .sv-fs-radio + .sv-fs-radio {
    border-left: 1px solid var(--sv-border);
}

.sv-seg-option.is-active,
.sv-fs-seg > .sv-fs-radio:has(input:checked) {
    background: var(--sv-tint-sage);
    color: var(--sv-on-tint);
}

/* Кружечок сегмента схований самою будовою — без рамки, тла й крапки, — а не
   лише прозорістю: «Тьмяне в тьмяному» повертає вимкненим полям повну
   непрозорість, і кружечки вилазили поверх назв (Паша 21.10, колір кнопки). */
.sv-fs-seg > .sv-fs-radio input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    border: 0;
    border-radius: 0;
    background: none;
    opacity: 0;
    cursor: pointer;
}

.sv-fs-seg > .sv-fs-radio input::before {
    content: none;
}

.sv-fs-seg > .sv-fs-radio:has(input:focus-visible) {
    outline: 2px solid var(--sv-focus);
    outline-offset: -2px;
}

.sv-fs-seg b {
    font-weight: inherit;
}

/* Меню «⋯» у шапці форми: рідші дії. */
.sv-form-more {
    position: relative;
}

.sv-form-more > summary {
    list-style: none;
}

.sv-form-more > summary::-webkit-details-marker {
    display: none;
}

.sv-form-more-menu {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    z-index: 20;
    display: grid;
    min-width: 200px;
    padding: var(--sv-s1);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    background: var(--sv-surface);
    box-shadow: var(--sv-shadow-md, 0 8px 24px rgba(0, 0, 0, 0.18));
}

.sv-form-more-menu form {
    margin: 0;
}

.sv-form-more-item {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
    width: 100%;
    min-height: 36px;
    padding: 0 var(--sv-s2);
    border: 0;
    border-radius: var(--sv-radius-sm);
    background: none;
    color: var(--sv-text);
    font: inherit;
    font-size: var(--sv-fs-sm);
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}

.sv-form-more-item:hover {
    background: var(--sv-bg);
}

.sv-form-more-item.is-danger {
    color: var(--sv-maroon);
}

/* Вкладки форми на телефоні — один рядок із прокруткою, а не сітка 2×2
   (v2 робить так дашборду; форма — інша навігація). */
@media (max-width: 600px) {
    .sv-dash-bar.sv-form-bar > .sv-tabs-scroll {
        display: flex;
        flex-wrap: nowrap;
        width: 100%;
        overflow-x: auto;
    }
}

/* Рядок форми: уся ліва частина — посилання на форму (велика ціль),
   дії — окремі кнопки праворуч, щоб клік по «Дублювати» не відкривав форму. */
.sv-forms-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.sv-form-row {
    display: flex;
    align-items: center;
    gap: var(--sv-s3);
    padding: var(--sv-s3) 0;
    border-top: 1px solid var(--sv-border);
}

.sv-form-row-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--sv-s1);
    color: var(--sv-text);
    text-decoration: none;
    border-radius: var(--sv-radius-sm);
}

.sv-form-row-title {
    font-weight: 700;
    overflow-wrap: anywhere;
}

.sv-form-row-main:hover .sv-form-row-title {
    color: var(--sv-link);
    text-decoration: underline;
}

.sv-form-row-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /* 8px між рядками: на телефоні бейдж «Є неопубліковані зміни» переносить
       решту в наступний рядок, і 4px злипались з ним. */
    gap: var(--sv-s2) var(--sv-s3);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
}

.sv-form-row-actions,
.sv-form-head-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
}

.sv-form-row-actions form,
.sv-form-head-actions form {
    margin: 0;
}

/* «Опублікувати зміни», «Скасувати зміни» й позначка в шапці — приховані,
   доки немає неопублікованих правок (forms.js показує їх без перезавантаження);
   клас бейджа з display інакше перебив би hidden. */
[data-dirty-show][hidden] {
    display: none !important;
}

/* Ціль натискання — не лише рядок тексту: висота компактного контролу, а на
   телефоні ще й невидима зона дотику (::after нижче), як у .sv-btn-sm. */
.sv-forms-back {
    position: relative;
    display: inline-flex;
    align-items: center;
    min-height: var(--sv-ctrl-h-sm);
    margin-bottom: var(--sv-s2);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    font-weight: 700;
    text-decoration: none;
}

.sv-forms-back:hover {
    color: var(--sv-text);
}

/* Шапка форми: назва з підсумком ліворуч, дії праворуч; на вузькому
   екрані дії переносяться під назву, а не стискають її. */
.sv-form-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sv-s3) var(--sv-s4);
    margin-bottom: var(--sv-s4);
}

.sv-form-head-main {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--sv-s2);
}

.sv-form-title {
    font-size: var(--sv-fs-xl);
    font-weight: 800;
    overflow-wrap: anywhere;
}

/* Вкладки форми — ліворуч під назвою, як розділи документа, а не по
   центру, як перемикач аналітики на дашборді. */
/* Вкладки форми й стан збереження — одним рядком: вкладки ліворуч, стан
   праворуч і по нижньому краю — ближче до форми під ним (Паша 16.10). */
.sv-form-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2) var(--sv-s3);
}

.sv-form-bar > .sv-qb-status {
    align-self: flex-end;
}

.sv-form-soon .sv-panel-head h2 {
    display: inline-flex;
    align-items: center;
}

.sv-form-access-table td {
    padding: var(--sv-s3) var(--sv-s4);
    border-bottom: 1px solid var(--sv-border);
    vertical-align: middle;
}

.sv-form-access-table tr:last-child td {
    border-bottom: none;
}

.sv-form-access-role {
    width: 220px;
}

/* Дії в рядку списку — лише іконки з підказкою: три підписані кнопки на
   кожному рядку перетворювали список на стіну кнопок. Підписані варіанти
   — у шапці самої форми. */
.sv-btn.sv-btn-icon {
    width: var(--sv-ctrl-h-sm);
    padding: 0;
    justify-content: center;
}

.sv-form-access-foot {
    display: flex;
    justify-content: flex-end;
    padding-top: var(--sv-s4);
}

/* «Нова форма»: шаблони — картки-кнопки на всю ширину. */
.sv-modal.sv-form-new-modal {
    max-width: 560px;
}

.sv-form-presets {
    display: grid;
    gap: var(--sv-s2);
    padding: var(--sv-s4) 0;
}

/* Строк зберігання відповідей над шаблонами: налаштування, а не ще один
   варіант — відділено від карток. */
.sv-form-new-retention {
    margin-bottom: var(--sv-s2);
}

.sv-form-preset {
    display: flex;
    flex-direction: column;
    gap: var(--sv-s1);
    width: 100%;
    padding: var(--sv-s3) var(--sv-s4);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    background: var(--sv-surface);
    color: var(--sv-text);
    text-align: left;
    font: inherit;
    cursor: pointer;
    transition: border-color var(--sv-dur-fast) var(--sv-ease), background var(--sv-dur-fast) var(--sv-ease);
}

button.sv-form-preset:hover {
    border-color: var(--sv-sage);
    background: var(--sv-surface-alt);
}

.sv-form-preset.is-soon {
    opacity: 0.55;
    cursor: default;
}

.sv-form-preset-title {
    font-weight: 700;
}

.sv-form-preset-desc {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    line-height: 1.45;
}

@media (max-width: 767px) {
    .sv-form-row {
        flex-direction: column;
        align-items: stretch;
    }

    .sv-forms-back::after {
        content: "";
        position: absolute;
        inset: calc(var(--sv-s15) * -1) 0;
    }

    .sv-form-access-role {
        width: auto;
    }
}

/* Як «Експорт» на дашборді: на вузькому екрані — окремим рядком під вкладками. */
@media (max-width: 760px) {
    .sv-comms-new {
        grid-column: 1;
    }
}

/* ============ Communications: конструктор форми ============ */

.sv-qb {
    display: grid;
    gap: var(--sv-s3);
}

/* Рядок над конструктором: пояснення до щойно зробленої зміни ліворуч,
   стан збереження праворуч (static/js/forms.js). */
.sv-qb-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sv-s2) var(--sv-s3);
}

/* Без пояснення до зміни рядок не займає місця. */
.sv-qb-bar:has(> .sv-qb-notice[hidden]) {
    display: none;
}

.sv-qb-notice {
    margin: 0;
    padding: var(--sv-s2) var(--sv-s3);
    border-radius: var(--sv-radius-sm);
    background: var(--sv-tint-sage);
    color: var(--sv-on-tint);
    font-size: var(--sv-fs-sm);
}

.sv-qb-status {
    margin-left: auto;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
}

.sv-qb-status[data-state="failed"] {
    color: var(--sv-maroon);
}

/* Шапка форми — як титульний аркуш: смуга зверху, назва крупніше. */
.sv-qb-header {
    position: relative;
    display: grid;
    gap: var(--sv-s2);
    box-shadow: inset 0 3px 0 var(--sv-primary);
}

/* Розділи (Паша 20.10, як у Google Forms): коли форма має розриви сторінки,
   шапка — «Розділ 1 з N», кожен розрив — свій розділ: вкладка над карткою,
   золота смуга зверху й більший відступ від попереднього розділу — щоб розділи
   не губились серед питань. */
.sv-qb-section-tab {
    display: none;
}

.sv-qb-header.has-sections,
.sv-qb-card.is-page {
    border-top: 6px solid var(--sv-accent);
    border-top-left-radius: 0;
    box-shadow: none;
}

.sv-qb-header.has-sections {
    margin-top: var(--sv-s6);
}

/* Над розділом (Паша 20.10): вкладка «Розділ N з M» ліворуч, «Після розділу
   N − 1» праворуч — одним рядком, тож місця між розділами не більшає. На
   телефоні не вміщаються — «Після розділу» рядком вище (wrap-reverse:
   вкладка лишається біля картки). */
.sv-qb-card.is-page {
    --sv-qb-page-gap: calc(var(--sv-s6) + var(--sv-s5));
    margin-top: var(--sv-qb-page-gap);
}

/* Смуга на весь проміжок від попередньої картки (відступ розділу + проміжок
   списку + золота смуга): вкладка — внизу, на смузі; «Після розділу» —
   угорі, під картками свого розділу (Паша 21.10: він про них, а не про
   наступний). Сама смуга кліків не перехоплює. */
.sv-qb-section-top {
    position: absolute;
    bottom: 100%;
    left: -1px;
    right: 0;
    display: flex;
    flex-wrap: wrap-reverse;
    /* wrap-reverse перевертає поперечну вісь: flex-start — це низ, біля картки. */
    align-items: flex-start;
    align-content: stretch;
    gap: var(--sv-s2) var(--sv-s3);
    height: calc(var(--sv-qb-page-gap) + var(--sv-s2) + 6px);
    pointer-events: none;
}

.sv-qb-section-top > * {
    pointer-events: auto;
}

.sv-qb-after {
    display: flex;
    /* Підпис і поле — одним рядком; тісно — поле обрізається трикрапкою. */
    flex-wrap: nowrap;
    align-items: center;
    align-self: flex-end;
    gap: var(--sv-s2) var(--sv-s3);
    max-width: 100%;
    margin: var(--sv-s2) 0 0 auto;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
}

@media (max-width: 767px) {
    .sv-qb-card.is-page {
        --sv-qb-page-gap: calc(var(--sv-s6) + var(--sv-s5) + var(--sv-ctrl-h-sm) + var(--sv-s2));
    }
}

.sv-qb-after .sv-dselect {
    flex: 0 1 auto;
    min-width: 0;
    max-width: 100%;
}

.sv-qb-after-label {
    flex-shrink: 0;
    white-space: nowrap;
}

/* Між картками — на тлі сторінки: поле біле, як у картці, а не зливається з
   тлом. Шеврон — ліворуч, як у розгортках (Паша 21.10; спільний блок
   «Розгортка»), тож стрілки поля праворуч немає й місця під неї теж. */
.sv-qb-after .sv-dselect > summary {
    padding-inline: var(--sv-s2);
    background-color: var(--sv-surface);
    background-image: none;
}

.sv-qb-header.has-sections > .sv-qb-section-tab,
.sv-qb-card.is-page .sv-qb-section-tab {
    position: absolute;
    bottom: 100%;
    left: -1px;
    display: block;
    padding: var(--sv-s1) var(--sv-s3);
    border-radius: var(--sv-radius) var(--sv-radius) 0 0;
    background: var(--sv-accent);
    color: var(--sv-primary-dark);
    font-size: var(--sv-fs-sm);
    font-weight: 700;
    white-space: nowrap;
}


.sv-qb-header-form {
    display: grid;
    gap: var(--sv-s2);
}

.sv-qb-form-title {
    font-size: var(--sv-fs-md);
    font-weight: 700;
}

.sv-qb-form-desc,
.sv-qb-desc {
    resize: none;
    overflow: hidden;
    line-height: 1.45;
}

.sv-qb-list {
    display: grid;
    /* minmax(0, …): картка не ширша за список, хоч би який довгий чіп чи
       рядок усередині (інакше на телефоні сторінка їздила вбік, Паша 20.10). */
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sv-s2);
}

.sv-qb-empty {
    margin: 0;
    padding: var(--sv-s4);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    text-align: center;
}

.sv-qb-list:has(.sv-qb-card) > .sv-qb-empty {
    display: none;
}

/* Картка: згорнута — назва й чіпи; відкрита — сейджева рамка й смуга
   ліворуч, як активне питання в Google Forms. */
.sv-qb-card {
    position: relative;
    background: var(--sv-surface);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    transition: border-color var(--sv-dur) var(--sv-ease), box-shadow var(--sv-dur) var(--sv-ease);
}

.sv-qb-card.is-open {
    border-color: var(--sv-sage);
    box-shadow: inset 3px 0 0 var(--sv-sage), var(--sv-shadow-sm);
}

/* Відкритий розділ — сейджева рамка, але смуга зверху лишається золотою. */
.sv-qb-card.is-page.is-open {
    border-top-color: var(--sv-accent);
}

.sv-qb-card-head {
    display: flex;
    align-items: center;
}

/* Відкрита картка: назва стає полем на тому самому місці — рядок-підсумок
   ховається, а не дублює назву над полем (Паша 20.10). */
.sv-qb-card.is-open > .sv-qb-card-head {
    display: none;
}

/* Ручка картки — зверху по центру, видно при наведенні й у відкритій
   картці (як у Google Forms): ліворуч вона з'їдала місце й зсувала назву. */
.sv-qb-card > .sv-qb-handle {
    position: absolute;
    top: 3px;
    left: 50%;
    z-index: 1;
    padding: 0 var(--sv-s2);
    transform: translateX(-50%);
    opacity: 0;
    transition: opacity var(--sv-dur) var(--sv-ease);
}

.sv-qb-card:is(:hover, .is-open) > .sv-qb-handle {
    opacity: 0.55;
}

.sv-qb-card > .sv-qb-handle:hover {
    opacity: 1;
}

/* Телефон (Паша 20.10): ручка більша й ширша — під палець; довге натискання
   на ній не виділяє текст і не відкриває меню. Підхоплена картка (після
   затримки, forms.js TOUCH_HOLD) — з тінню, щоб було видно, що вже тягнеш. */
@media (hover: none) {
    .sv-qb-card > .sv-qb-handle {
        top: 0;
        padding: var(--sv-s1) var(--sv-s5);
        opacity: 0.55;
    }

    .sv-qb-card > .sv-qb-handle svg {
        width: 28px;
        height: 16px;
    }

    /* Ручка вища — і місця під нею більше; згорнута назва — там само, де
       текст у полі відкритої картки. */
    .sv-qb-card > .sv-qb-body {
        padding-top: calc(var(--sv-s4) + var(--sv-s3));
    }

    .sv-qb-card .sv-qb-summary {
        padding-top: calc(var(--sv-s5) + var(--sv-s3));
    }

    .sv-qb-handle,
    .sv-qb-ohandle {
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        user-select: none;
    }
}

.sv-qb-card.is-chosen,
.sv-qb-option.is-chosen {
    box-shadow: var(--sv-shadow-md);
}

.sv-qb-handle,
.sv-qb-ohandle {
    display: inline-flex;
    flex-shrink: 0;
    color: var(--sv-text-muted);
    cursor: grab;
    opacity: 0.55;
    touch-action: none;
}

.sv-qb-card:hover .sv-qb-handle,
.sv-qb-option:hover .sv-qb-ohandle {
    opacity: 1;
}

.sv-qb-ohandle.is-blank {
    width: 15px;
    cursor: default;
}

.sv-qb-summary {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s1) var(--sv-s2);
    /* Текст назви — там само, де він буде в полі відкритої картки (поле: відступ
       картки + рамка + внутрішній відступ). */
    padding: calc(var(--sv-s4) + var(--sv-s3)) calc(var(--sv-s4) + var(--sv-s3)) calc(var(--sv-s4) + var(--sv-s1));
    border: none;
    background: none;
    color: var(--sv-text);
    font: inherit;
    text-align: left;
    cursor: text;
}

.sv-qb-title {
    flex: 1 1 12rem;
    min-width: 0;
    font-weight: 600;
    overflow-wrap: anywhere;
}

.sv-qb-title.is-empty {
    color: var(--sv-text-muted);
    font-weight: 500;
}

.sv-qb-chips {
    display: inline-flex;
    flex-wrap: wrap;
    gap: var(--sv-s1);
    /* Не ширші за картку: довгий чіп умови на телефоні розпирав сторінку
       вшир — вона ходила вліво-вправо (Паша 20.10). */
    min-width: 0;
    max-width: 100%;
}

/* У рядку картки чіпи — праворуч, тип — крайній, і коли умова переносить їх
   на другий рядок (Паша 20.10). */
.sv-qb-summary > .sv-qb-chips {
    justify-content: flex-end;
    margin-left: auto;
}

.sv-qb-chip {
    padding: var(--sv-pill-pad-top) var(--sv-s2) var(--sv-pill-pad-bottom);
    border-radius: var(--sv-radius-pill);
    background: var(--sv-bg);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    white-space: nowrap;
}

/* Обов'язкове питання — золота зірочка одразу після назви, як в учня. */
.sv-qb-card.is-required .sv-qb-title::after,
.sv-req-mark {
    color: var(--sv-required);
    font-weight: 800;
}

.sv-qb-card.is-required .sv-qb-title::after {
    content: "\00a0*";
}

/* Тіло відкритої картки — сітка: текст питання, іконки медіа й тип в одному
   рядку (як у Google Forms), решта на всю ширину. Форма автозбереження
   розчиняється в сітці (display: contents), тож тип і іконки (окремі форми)
   стають поруч із полем питання. */
.sv-qb-body {
    display: none;
    grid-template-columns: minmax(0, 1fr) auto 210px;
    gap: var(--sv-s2) var(--sv-s3);
    /* Зверху — місце для ручки. */
    padding: calc(var(--sv-s4) + var(--sv-s1)) var(--sv-s4) var(--sv-s4);
}

/* is-closing — картка згортається (forms.js): поля ще видно, поки висота
   плавно меншає. */
.sv-qb-card:is(.is-open, .is-closing) .sv-qb-body {
    display: grid;
}

.sv-qb-body .sv-qb-autosave {
    display: contents;
}

.sv-qb-body > *,
.sv-qb-body .sv-qb-autosave > * {
    grid-column: 1 / -1;
}

.sv-qb-body .sv-qb-q {
    grid-column: 1;
    grid-row: 1;
    font-weight: 600;
}

/* Розрив сторінки — без медіа: назва займає й місце іконок. */
.sv-qb-body:not(:has(> .sv-qb-media-icons)) .sv-qb-q {
    grid-column: 1 / 3;
}

.sv-qb-body .sv-qb-media-icons {
    grid-column: 2;
    grid-row: 1;
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
}

/* Іконки картинки й аудіо — квадрати висотою з рядок назви (Паша 16.10). */
.sv-qb-media-icons .sv-qb-media-pick {
    display: inline-grid;
    place-items: center;
    width: 40px;
    height: 40px;
    min-height: 40px;
    padding: 0;
}

.sv-qb-body .sv-qb-type {
    grid-column: 3;
    grid-row: 1;
}

/* Назва питання й тип — однієї висоти: поруч різна висота виглядала
   неохайно (Паша 16.10). Назва в кілька рядків росте вниз, тип — угорі. */
.sv-qb-body .sv-qb-q,
.sv-qb-body .sv-qb-type .sv-dselect > summary {
    min-height: 40px;
}

.sv-qb-body .sv-qb-type .sv-dselect > summary {
    height: 40px;
}

.sv-qb-body .sv-qb-q {
    padding-top: 9px;
    padding-bottom: 9px;
}

.sv-qb-body .sv-qb-type,
.sv-qb-body .sv-qb-media-icons {
    align-self: start;
}

.sv-qb-options {
    display: grid;
    gap: var(--sv-s1);
    margin: 0;
    padding: 0;
    list-style: none;
}

.sv-qb-option {
    display: flex;
    align-items: center;
    gap: var(--sv-s2);
}

/* Значок варіанта — яким його побачить учень: коло, квадрат чи номер. */
.sv-qb-omark {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    border: 2px solid var(--sv-border);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
    line-height: 12px;
    text-align: center;
}

.sv-qb-omark.is-choice {
    border-radius: 50%;
}

.sv-qb-omark.is-multi {
    border-radius: 4px;
}

.sv-qb-omark.is-dropdown {
    width: auto;
    min-width: 16px;
    height: auto;
    border: none;
}

.sv-qb-olabel {
    flex: 1 1 auto;
    min-width: 0;
}

.sv-qb-card.is-page > .sv-qb-section-top > .sv-qb-section-tab {
    position: static;
}

/* Перехід за відповіддю: куди веде варіант — праворуч у рядку; видно, лише
   коли увімкнено перемикач (без перемальовування картки). */
.sv-qb-ojump {
    flex: 0 1 16rem;
    min-width: 0;
}

.sv-qb-card:not(:has([data-jump-switch]:checked)) .sv-qb-ojump {
    display: none;
}

@media (max-width: 767px) {
    /* Телефон: варіант і хрестик — одним рядком, «куди» — під ними. */
    .sv-qb-option:has(.sv-qb-ojump) {
        flex-wrap: wrap;
    }

    .sv-qb-option:has(.sv-qb-ojump) > :is(.sv-qb-olabel, .sv-qb-other-label) {
        flex: 1 1 0;
    }

    .sv-qb-ojump {
        order: 1;
        flex-basis: 100%;
        padding-left: calc(var(--sv-s6) + var(--sv-s1));
    }
}

.sv-qb-other-label {
    flex: 1 1 auto;
    padding: 0 var(--sv-s3);
    color: var(--sv-text-muted);
    font-style: italic;
}

.sv-qb-oremove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: var(--sv-ctrl-h-sm);
    height: var(--sv-ctrl-h-sm);
    border: none;
    border-radius: var(--sv-radius-sm);
    background: none;
    color: var(--sv-text-muted);
    cursor: pointer;
}

.sv-qb-oremove:hover {
    background: var(--sv-bg);
    color: var(--sv-maroon);
}

/* Ключ відповіді — варіанти окремими полями в рядок, з переносом (key_list). */
.sv-qb-keylist-items {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
}

.sv-qb-keychip {
    display: inline-flex;
    align-items: center;
    max-width: 100%;
}

.sv-qb-keychip .sv-input {
    width: auto;
    min-width: 0;
    max-width: 100%;
}

/* Таблиця в конструкторі (Паша 20.10, як у Google Forms): рядки ліворуч,
   стовпці праворуч; на вузькому — одне під одним. */
.sv-qb-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sv-s3) var(--sv-s5);
}

.sv-qb-grid-part {
    display: grid;
    align-content: start;
    gap: var(--sv-s1);
}

@media (max-width: 640px) {
    .sv-qb-grid {
        grid-template-columns: 1fr;
    }
}

.sv-qb-oadd {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
    padding-left: var(--sv-s5);
    font-size: var(--sv-fs-sm);
}

.sv-qb-oadd .sv-linklike {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s1);
}

.sv-qb-or {
    color: var(--sv-text-muted);
}

/* Як виглядатиме поле відповіді в учня — лише підказка. */
.sv-qb-preview {
    max-width: 60%;
    margin: 0;
    padding: var(--sv-s2) 0;
    border-bottom: 1px dashed var(--sv-border);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
}

.sv-qb-scale {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
}

.sv-qb-scale-range {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s2);
}

.sv-qb-scale .sv-input {
    flex: 1 1 12rem;
    width: auto;
}

.sv-qb-more > summary {
    color: var(--sv-link);
    font-size: var(--sv-fs-sm);
    font-weight: 700;
    cursor: pointer;
}

.sv-qb-more-body {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr));
    align-items: end;
    gap: var(--sv-s3);
    padding-top: var(--sv-s2);
}

.sv-qb-flags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s2) var(--sv-s5);
}

.sv-qb-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s1);
    padding-top: var(--sv-s3);
    border-top: 1px solid var(--sv-border);
}

.sv-qb-actions-gap {
    flex: 1 1 auto;
}

.sv-qb-add {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s2);
}

/* SortableJS: місце, куди впаде картка, — напівпрозоре. */
.sv-qb .sortable-ghost {
    opacity: 0.4;
}

/* Перегляд без права правити. */
.sv-qb-ro-head h2 {
    margin-bottom: var(--sv-s1);
}

.sv-qb-ro-desc {
    margin: 0;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    white-space: pre-line;
}

.sv-qb-ro-list {
    display: grid;
    gap: var(--sv-s3);
    margin: var(--sv-s4) 0 0;
    padding: 0;
    list-style: none;
}

.sv-qb-ro-item {
    padding-top: var(--sv-s3);
    border-top: 1px solid var(--sv-border);
}

.sv-qb-ro-line {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
}

.sv-qb-ro-title {
    font-weight: 600;
}

.sv-qb-ro-options {
    margin: var(--sv-s2) 0 0;
    padding-left: var(--sv-s6);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
}

@media (max-width: 767px) {
    /* Зверху — місце для ручки (вона над полем назви, а не на ньому). */
    .sv-qb-body {
        grid-template-columns: minmax(0, 1fr);
        padding: calc(var(--sv-s4) + var(--sv-s1)) var(--sv-s3) var(--sv-s3);
    }

    /* Назва згорнутої картки — там само, де текст у полі (поля картки вужчі). */
    .sv-qb-summary {
        padding-inline: calc(var(--sv-s3) + var(--sv-s3));
    }

    .sv-qb-body {
        grid-template-columns: minmax(0, 1fr) auto;
    }

    .sv-qb-body .sv-qb-q,
    .sv-qb-body:not(:has(> .sv-qb-media-icons)) .sv-qb-q {
        grid-column: 1 / -1;
    }

    .sv-qb-body .sv-qb-type {
        grid-column: 1;
        grid-row: 2;
    }

    .sv-qb-body .sv-qb-media-icons {
        grid-column: 2;
        grid-row: 2;
    }

    .sv-qb-body:not(:has(> .sv-qb-media-icons)) .sv-qb-type {
        grid-column: 1 / -1;
    }

    .sv-qb-preview {
        max-width: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .sv-qb-card,
    .sv-qb-card > .sv-qb-handle {
        transition: none;
    }
}

/* --- «… видалено · Скасувати» (Паша 20.10, templates/_undo_bar.html) ---
   Завжди в нижньому лівому куті області вмісту — і на короткій сторінці
   теж, а не одразу під списком; поруч із бічною панеллю v2, а не поверх
   неї (зсув — v2.css, за --v2-side-w). Кольори головної дії бренду — в
   обох темах; тонкий обвід, щоб у темній темі смуга не зливалась із тлом. */
.sv-undo-anchor {
    position: fixed;
    bottom: calc(var(--sv-s4) + env(safe-area-inset-bottom, 0px));
    left: var(--sv-s4);
    right: var(--sv-s4);
    z-index: calc(var(--sv-z-modal) - 1);
    display: flex;
    pointer-events: none;
}

.sv-undo {
    display: flex;
    align-items: center;
    gap: var(--sv-s3);
    max-width: 100%;
    padding: var(--sv-s1) var(--sv-s1) var(--sv-s1) var(--sv-s4);
    pointer-events: auto;
    border-radius: var(--sv-radius);
    background: var(--sv-primary);
    color: var(--sv-on-primary);
    font-size: var(--sv-fs-sm);
    box-shadow: var(--sv-shadow-lg), inset 0 0 0 1px color-mix(in srgb, var(--sv-on-primary) 14%, transparent);
    animation: sv-undo-in var(--sv-dur) var(--sv-ease) both;
}

.sv-undo[hidden] {
    display: none;
}

.sv-undo-text {
    min-width: 0;
    overflow-wrap: anywhere;
}

.sv-undo-form {
    flex-shrink: 0;
    margin: 0;
}

.sv-undo-btn {
    min-height: var(--sv-ctrl-h-sm);
    padding: 0 var(--sv-s3);
    border: 0;
    border-radius: var(--sv-radius-sm);
    background: transparent;
    color: var(--sv-accent);
    font: inherit;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    cursor: pointer;
}

.sv-undo-btn:hover,
.sv-undo-btn:focus-visible {
    background: color-mix(in srgb, var(--sv-on-primary) 12%, transparent);
}

.sv-undo.is-leaving {
    animation: sv-undo-out var(--sv-dur) var(--sv-ease) forwards;
    pointer-events: none;
}

@keyframes sv-undo-in {
    from {
        opacity: 0;
        transform: translateY(6px);
    }
}

@keyframes sv-undo-out {
    to {
        opacity: 0;
        transform: translateY(6px);
    }
}

@media (max-width: 767px) {
    .sv-undo {
        flex: 1;
        justify-content: space-between;
    }
}

@media (prefers-reduced-motion: reduce) {
    .sv-undo {
        animation: none;
    }
}

/* Вікно «Видалити розділ?» (forms.js): у v2 — як інші підтвердження
   (.v2-confirm), а це — те саме для v1, без рамки браузера. Скільки там
   чого — під питанням, тихіше. */
.v2-confirm.sv-qb-del {
    /* Три кнопки з довгими назвами — в один рядок на ноутбуці. */
    width: min(600px, calc(100vw - 2 * var(--sv-s4)));
    padding: var(--sv-s5);
    border: 0;
    border-radius: var(--sv-radius-lg);
    background: var(--sv-surface);
    color: var(--sv-text);
    box-shadow: var(--sv-shadow-lg);
}

.sv-qb-del::backdrop {
    background: var(--sv-overlay);
}

.sv-qb-del .v2-confirm-q {
    margin: 0 0 var(--sv-s2);
    font-weight: 600;
}

.sv-qb-del .v2-confirm-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--sv-s2);
}

.sv-qb-del-has {
    margin: 0 0 var(--sv-s5);
    color: var(--sv-text-muted);
}

/* М'якша з двох небезпечних дій («Видалити й залишити вміст», Паша 20.10):
   бордова рамка й матова бордова підкладка — той самий заміряний рецепт,
   що й «гучна» плитка (.sv-tile.is-loud): у темній темі чистий тінт злився
   б із поверхнею, домішка маруну тримає різницю в обох темах. */
.sv-btn.sv-btn-soft-danger {
    border-color: var(--sv-maroon);
    background: color-mix(in srgb, var(--sv-maroon) 8%, var(--sv-tint-red));
    color: var(--sv-maroon);
}

.sv-btn.sv-btn-soft-danger:hover {
    border-color: var(--sv-maroon-hover);
    background: color-mix(in srgb, var(--sv-maroon) 16%, var(--sv-tint-red));
}

/* --- Конструктор: умови показу й перевірка перед публікацією --- */

/* Умова в рядку-підсумку — сейджева, щоб логіку форми було видно без
   розгортання карток. Довга умова обрізається, повна — в редакторі. */
.sv-qb-chip.is-rule {
    min-width: 0;
    max-width: min(22rem, 100%);
    overflow: hidden;
    text-overflow: ellipsis;
    background: var(--sv-tint-sage);
    color: var(--sv-on-tint);
}

.sv-qb-rule-wrap:not(:has(form)) {
    display: none;
}

.sv-qb-rule-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
}

/* Списки умови — не ширші за картку: довга назва питання обрізається
   трикрапкою, а не розпирає сторінку (на телефоні вона від цього
   зменшувалась, Паша 20.10). */
.sv-qb-rule-row > :is(.sv-dselect, .sv-qb-rule-values),
.sv-qb-rule-row .sv-mselect {
    min-width: 0;
    max-width: 100%;
}

.sv-qb-rule-row :is(.sv-dselect, .sv-mselect) > summary {
    max-width: 100%;
}

.sv-qb-rule-if {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    font-weight: 700;
}

.sv-qb-rule .sv-panel-note {
    margin-top: var(--sv-s2);
}

/* Що заважає опублікувати — під рядком-підсумком, видно й у згорнутій
   картці; сама картка — з бордовою смугою, як «погано» деінде в панелі. */
.sv-qb-issues {
    display: grid;
    gap: var(--sv-s05);
    margin: 0;
    padding: 0 var(--sv-s4) var(--sv-s3) var(--sv-s6);
    list-style: none;
    color: var(--sv-maroon);
    font-size: var(--sv-fs-sm);
    font-weight: 600;
}

.sv-qb-issues:empty {
    display: none;
}

.sv-qb-card:has(> .sv-qb-issues > li) {
    border-color: var(--sv-maroon);
    box-shadow: inset 3px 0 0 var(--sv-maroon);
}

.sv-qb-card.is-open:has(> .sv-qb-issues > li) {
    box-shadow: inset 3px 0 0 var(--sv-maroon), var(--sv-shadow-sm);
}

/* Зведення перевірки над конструктором: що лишилось виправити (з переходом
   до картки), а коли все гаразд — сейджеве «можна публікувати». */
.sv-qb-check {
    padding: var(--sv-s3) var(--sv-s4);
    border: 1px solid var(--sv-maroon);
    border-radius: var(--sv-radius);
    background: var(--sv-tint-red);
    color: var(--sv-text);
    font-size: var(--sv-fs-sm);
}

.sv-qb-check.is-ready {
    border-color: var(--sv-sage);
    background: var(--sv-tint-sage);
    color: var(--sv-on-tint);
}

.sv-qb-check-title {
    margin: 0;
    font-weight: 700;
}

.sv-qb-check ul {
    display: grid;
    gap: var(--sv-s1);
    margin: var(--sv-s2) 0 0;
    padding-left: var(--sv-s5);
}

.sv-qb-check-link {
    color: inherit;
    font-weight: 700;
}

.sv-qb-check-codes {
    display: block;
    color: var(--sv-maroon);
}

/* --- Вкладка «Налаштування» форми --- */
/* Стан прийому — першим рядком вкладки, без підкладки: заголовок з крапкою,
   дія — рядком нижче (Паша 16.10). */
.sv-fs-stateline {
    display: grid;
    justify-items: start;
    gap: var(--sv-s3);
    margin: 0 0 var(--sv-s4);
}

.sv-fs-stateline form {
    margin: 0;
}

.sv-fs-state-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2) var(--sv-s3);
}

/* «Доступно будь-кому з посиланням» — легка жовта мітка біля стану. */
.sv-fs-open-chip {
    padding: var(--sv-pill-pad-top) var(--sv-s2) var(--sv-pill-pad-bottom);
    border-radius: var(--sv-radius-pill);
    background: var(--sv-tint-amber);
    color: var(--sv-warning);
    font-size: var(--sv-fs-xs);
    font-weight: 700;
}

.sv-fs-state-title {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s2);
    margin: 0;
}


/* Крапка стану: зелена — приймає, бурштинова — чекає розкладу, сіра — ні. */
.sv-fs-dot {
    flex-shrink: 0;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--sv-success);
}

.sv-fs-dot.is-wait {
    background: var(--sv-warning);
}

.sv-fs-dot.is-off {
    background: var(--sv-text-muted);
}

/* Розсилка ботом (core/forms/broadcast) — під командою бота в секції
   «Розсилка»: текст, кому, дві кнопки, далі історія. */
.sv-bc {
    display: grid;
    gap: var(--sv-s3);
    margin-top: var(--sv-s4);
    padding-top: var(--sv-s4);
    border-top: 1px solid var(--sv-border);
}

/* Тіло блоку — fieldset без рамки: до публікації форми він disabled (усе
   неактивне одним атрибутом) і притушений, як .sv-fs-dep.is-off. */
.sv-bc-body {
    display: grid;
    gap: var(--sv-s3);
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
}

.sv-bc.is-off .sv-bc-body {
    opacity: 0.45;
}

.sv-bc-title {
    display: flex;
    align-items: center;
    gap: var(--sv-s1);
    margin: 0;
    font-size: var(--sv-fs-base);
}

/* «Кому → Вибрати учнів»: кружечок, кнопка списку як жирний текст рядка
   (підпис — у ній самій), шеврон ліворуч — спільний CSS «Розгортка»;
   кружечок, текст і «i» — по центру першого рядка, як у решті .sv-fs-radio
   (розкритий список не тягне кружечок униз). «i» тут — елемент flex, тож
   центруємо відступом від висоти рядка, а не vertical-align. */
.sv-bc-pick {
    flex-wrap: wrap;
}

.sv-bc-pick > .sv-info {
    line-height: inherit;
    margin-top: calc((1lh - var(--sv-info-size)) / 2);
}

/* display: flex (не inline-flex) — інакше рядок тексту навколо кнопки додає
   місце під виносні елементи й рядок «Вибрати учнів» вищий за сусідів. */
.sv-bc-pick .sv-mselect > summary.sv-bc-pick-trigger {
    display: flex;
    width: auto;
    min-height: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    color: var(--sv-text);
    font-size: var(--sv-fs-sm);
    font-weight: 700;
    line-height: inherit;
    cursor: pointer;
}

.sv-bc-pick-trigger .sv-dselect-label,
.sv-bc-pick-trigger .sv-dselect-label.is-placeholder {
    flex: none;
    color: inherit;
    font-weight: inherit;
}

.sv-bc-pick .sv-mselect-list {
    width: min(320px, calc(100vw - 2 * var(--sv-s4)));
}

.sv-bc-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
}

.sv-bc-history {
    display: grid;
    gap: var(--sv-s2);
}

.sv-bc-log[open] {
    display: grid;
    gap: var(--sv-s2);
}

.sv-bc-history:empty {
    display: none;
}

.sv-bc-item {
    display: grid;
    gap: var(--sv-s1);
    padding: var(--sv-s2) var(--sv-s3);
    border-radius: var(--sv-radius-sm);
    background: var(--sv-bg);
}

.sv-bc-head {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s1) var(--sv-s3);
    font-size: var(--sv-fs-sm);
    font-weight: 600;
}

.sv-bc-when,
.sv-bc-by {
    color: var(--sv-text-muted);
    font-weight: 500;
}

.sv-bc-progress,
.sv-bc-counts {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s1) var(--sv-s3);
    margin: 0;
    font-size: var(--sv-fs-sm);
}

.sv-bc-progress {
    color: var(--sv-text-muted);
}

/* Надіслано — звичайним текстом; не дійшло — приглушено, «помилка» — тривогою. */
.sv-bc-count:not(.is-sent) {
    color: var(--sv-text-muted);
}

.sv-bc-count.is-failed {
    color: var(--sv-maroon);
}

.sv-bc-log > summary,
.sv-bc-missed > summary {
    color: var(--sv-link);
    font-size: var(--sv-fs-sm);
    font-weight: 700;
    cursor: pointer;
}

.sv-bc-missed[open] {
    display: grid;
    gap: var(--sv-s2);
}

.sv-bc-missed-list {
    display: grid;
    gap: var(--sv-s1);
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: var(--sv-fs-sm);
}

.sv-bc-missed-list li {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--sv-s3);
}

/* «Відкрити вручну» і «i» поруч — одним рядком. */
.sv-fs-state-action {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s1);
}

.sv-fs-form {
    display: grid;
    gap: var(--sv-s3);
    margin: var(--sv-s3) 0;
}

.sv-fs-fieldset {
    display: grid;
    gap: var(--sv-s3);
    min-width: 0;
    margin: 0;
    padding: 0;
    border: none;
}

.sv-fs-fieldset > .sv-panel {
    display: grid;
    gap: var(--sv-s3);
}

.sv-fs-fieldset > .sv-panel > .sv-panel-head {
    margin-bottom: 0;
}

/* Відступи в розділі дає сітка — без власного margin примітки. */
.sv-fs-fieldset > .sv-panel > .sv-panel-note {
    margin: 0;
}

.sv-fs-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
    gap: var(--sv-s3);
}

/* Тексти в налаштуваннях і конструкторі форми — рядок, що росте з текстом
   (field-sizing; без нього — forms.js), без ручки розтягування. */
.sv-fs-form textarea.sv-input,
.sv-qb textarea {
    field-sizing: content;
    overflow: hidden;
    resize: none;
    line-height: 1.45;
}

.sv-fs-hint {
    display: block;
    margin: 0;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 500;
    line-height: 1.45;
    white-space: normal;
}

.sv-fs-readonly {
    padding: var(--sv-s2) var(--sv-s3);
    border-radius: var(--sv-radius-sm);
    background: var(--sv-bg);
}

/* Галочка з поясненням під нею: підпис і пояснення — одним блоком праворуч. */
/* Перемикач (switch) у налаштуваннях: діє одразу, без «Зберегти» (Solar
   design.md «Контроли вибору»). Увесь рядок — ціль натиску, ≥ 44px. */
.sv-fs-row {
    display: flex;
    align-items: center;
    gap: var(--sv-s1);
}

/* Доріжка — на першому рядку підпису, однорядковий підпис — по центру
   своїх 44 px, хай який шрифт (Паша 21.10). */
.sv-fs-switch {
    display: inline-flex;
    align-items: flex-start;
    gap: var(--sv-s3);
    min-height: var(--sv-tap-h);
    padding-block: calc((var(--sv-tap-h) - 1lh) / 2);
    cursor: pointer;
}

.sv-fs-switch input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    /* Те саме: не прозорістю єдиною (вимкнений у вимкненому блоці — непрозорий). */
    clip-path: inset(50%);
}

.sv-fs-switch-track {
    position: relative;
    flex-shrink: 0;
    width: 36px;
    height: var(--sv-switch-h);
    margin-top: calc((1lh - var(--sv-switch-h)) / 2);
    border-radius: var(--sv-radius-pill);
    background: color-mix(in srgb, var(--sv-text-muted) 38%, transparent);
    transition: background var(--sv-dur) var(--sv-ease);
}

.sv-fs-switch-track::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: #FFFFFF;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
    transition: transform var(--sv-dur) var(--sv-ease);
}

.sv-fs-switch input:checked + .sv-fs-switch-track {
    background: var(--sv-primary);
}

.sv-fs-switch input:checked + .sv-fs-switch-track::after {
    transform: translateX(16px);
}

.sv-fs-switch input:focus-visible + .sv-fs-switch-track {
    outline: 2px solid var(--sv-focus);
    outline-offset: 2px;
}

.sv-fs-switch input:disabled + .sv-fs-switch-track,
.sv-fs-switch:has(input:disabled) {
    cursor: not-allowed;
}

/* Залежні налаштування — з відступом під головним; поки він вимкнений,
   притушені й не клікаються (forms.js вимикає поля). */
.sv-fs-dep {
    display: grid;
    gap: var(--sv-s1);
    margin: 0 0 var(--sv-s2) var(--sv-sub-indent);
    padding-left: var(--sv-s4);
    border-left: 2px solid var(--sv-border);
    transition: opacity var(--sv-dur) var(--sv-ease);
}

.sv-fs-dep.is-off,
.sv-fs-row.is-locked .sv-fs-switch {
    opacity: 0.45;
}

/* Тьмяне в тьмяному не тьмяніє вдруге (Паша 20.10): прозорість множиться —
   вимкнений блок у вимкненому з полем, що теж «disabled», давав
   0,45 × 0,45 × 0,5 ≈ 0,1, майже невидимо. Тьмяним робить лише
   найзовнішній вимкнений блок; усе всередині — як є (менше вже не стане). */
:is(.sv-fs-dep.is-off, .sv-bc.is-off .sv-bc-body, .sv-fs-radios.is-locked .sv-fs-radio,
    .sv-fs-row.is-locked .sv-fs-switch, [aria-busy="true"].v2-busy-region)
    :is(.sv-fs-dep.is-off, .sv-bc-body, .sv-fs-radio, .sv-fs-switch, :disabled, [aria-disabled="true"]) {
    opacity: 1;
}

.sv-fs-dep.is-off * {
    pointer-events: none;
}

/* Статус автозбереження — внизу по центру, як тост: «Збережено» зникає сам,
   помилка лишається, доки її не виправлять. */
/* «Збережено» — по центру вкладки, а не всього вікна: left ставить forms.js
   за шириною <main> (бічна панель згорнута чи ні); 50% — без JS. */
.sv-fs-status {
    position: fixed;
    left: 50%;
    bottom: var(--sv-s5);
    z-index: 30;
    margin: 0;
    padding: var(--sv-s2) var(--sv-s4);
    border-radius: var(--sv-radius-pill);
    background: var(--sv-primary);
    color: #FFFFFF;
    font-size: var(--sv-fs-sm);
    font-weight: 600;
    transform: translateX(-50%);
}

.sv-fs-status.is-error {
    background: var(--sv-maroon);
}

/* «i» з підказкою на наведенні (templates/_info.html) — для v1 і v2. */
.sv-info {
    position: relative;
    display: inline-grid;
    place-items: center;
    width: var(--sv-info-size);
    height: var(--sv-info-size);
    margin-left: var(--sv-s1);
    padding: 0;
    border: 0;
    background: none;
    color: var(--sv-text-muted);
    /* Середина значка — на середині великих літер за будь-якого розміру
       шрифту; сталий -2px пасував одному розміру. */
    vertical-align: calc(0.5cap - var(--sv-info-size) / 2);
    cursor: help;
}

/* На сенсорі зона натиску — 44px (невидима), як у .v2-info. */
@media (pointer: coarse) {
    .sv-info::after {
        content: "";
        position: absolute;
        inset: -14px;
    }
}

.sv-info::after {
    content: "";
    position: absolute;
    inset: -6px;
}

.sv-info:focus-visible {
    outline: 2px solid var(--sv-focus);
    outline-offset: 2px;
    border-radius: 50%;
}

@media (hover: hover) {
    .sv-info:hover {
        color: var(--sv-text);
    }
}

.sv-info-pop {
    position: fixed;
    inset: auto;
    width: max-content;
    max-width: min(320px, calc(100vw - 16px));
    margin: 0;
    padding: var(--sv-s2) var(--sv-s3);
    border: 0;
    border-radius: var(--sv-radius-sm);
    background: var(--sv-surface);
    color: var(--sv-text);
    box-shadow: var(--sv-shadow-lg);
    font-size: var(--sv-fs-sm);
    font-weight: 400;
    line-height: 1.45;
    text-align: left;
    text-transform: none;
    letter-spacing: normal;
}

.sv-fs-check {
    align-items: flex-start;
    font-size: var(--sv-fs-sm);
    white-space: normal;
}

.sv-fs-radios {
    display: grid;
    gap: var(--sv-s2);
    min-width: 0;
    margin: 0;
    padding: 0;
    border: none;
}

.sv-fs-radios legend {
    margin-bottom: var(--sv-s1);
    padding: 0;
}

.sv-fs-radio {
    display: flex;
    align-items: flex-start;
    gap: var(--sv-s2);
    padding: var(--sv-s2) var(--sv-s3);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-sm);
    font-size: var(--sv-fs-sm);
    cursor: pointer;
}

.sv-fs-radio:has(input:checked) {
    border-color: var(--sv-sage);
    background: var(--sv-tint-sage);
}

/* Вибір, який уже не змінити (є відповіді): сірим, без руки-курсора. */
.sv-fs-radios.is-locked .sv-fs-radio {
    opacity: 0.55;
    cursor: not-allowed;
}

/* Факт, а не перемикач: як форма відповідає зараз (у фазі 1 — лише за
   посиланням) і що звідси випливає для приватності. */
.sv-fs-fact {
    display: flex;
    align-items: flex-start;
    gap: var(--sv-s2);
    font-size: var(--sv-fs-sm);
}

.sv-fs-fact-icon {
    display: inline-flex;
    margin-top: var(--sv-s05);
    color: var(--sv-sage);
}

/* Майбутні перемикачі — сірі, з «скоро» і тим, що вони дадуть. */
.sv-fs-soon-list {
    display: grid;
    gap: var(--sv-s2);
}

.sv-fs-soon {
    display: grid;
    gap: var(--sv-s05);
    padding: var(--sv-s2) var(--sv-s3);
    border: 1px dashed var(--sv-border);
    border-radius: var(--sv-radius-sm);
    color: var(--sv-text-muted);
}

.sv-fs-soon-label {
    font-size: var(--sv-fs-sm);
    font-weight: 600;
}

.sv-fs-later .sv-fs-soon-list {
    grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
}

.sv-fs-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s3);
}


/* Посилання для учнів — у блоці прийому відповідей: «приймає — ось за цим
   посиланням», тут же «Копіювати» й «Видати нове посилання». */
/* Блок «Посилання»: підпис, «Показати всі» зі списком, унизу кнопки —
   завантажити ліворуч, перевипустити праворуч (Паша 16.10). */
.sv-fs-links-summary {
    margin: 0 0 var(--sv-s3);
    color: var(--sv-text-muted);
}

.sv-fs-link-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2) var(--sv-s4);
}

.sv-fs-link-actions > .sv-fs-reissue {
    margin-left: auto;
}

/* «Запрацює після публікації» під рядком посилання — не впритул (патруль v2 18.10). */
.sv-fs-link-actions + .sv-fs-hint {
    margin-top: var(--sv-s2);
}


.sv-fs-link {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
}

.sv-fs-url {
    padding: var(--sv-s1) var(--sv-s2);
    border-radius: var(--sv-radius-sm);
    background: var(--sv-bg);
    font-size: var(--sv-fs-sm);
    overflow-wrap: anywhere;
}

.sv-fs-reissue {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
    margin: 0;
}

/* «Вигляд»: вибір банера мініатюрами — так, як його побачать учні. */
.sv-fs-banners {
    display: grid;
    gap: var(--sv-s2);
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
}

/* legend у fieldset не стає елементом сітки — gap на нього не діє. */
.sv-fs-banners > legend {
    margin-bottom: var(--sv-s2);
}

.sv-fs-banner-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr));
    gap: var(--sv-s3);
}

.sv-fs-banner-pick {
    display: grid;
    gap: var(--sv-s1);
    cursor: pointer;
}

.sv-fs-banner-pick input {
    position: absolute;
    opacity: 0;
}

.sv-fs-banner-swatch {
    display: block;
    aspect-ratio: 3 / 1;
    border: 2px solid transparent;
    border-radius: var(--sv-radius);
    background: var(--sv-brand-banner) center / auto 78% no-repeat, var(--banner, var(--sv-banner-brand));
    transition: border-color var(--sv-dur) var(--sv-ease);
}

.sv-fs-banner-pick input:checked + .sv-fs-banner-swatch {
    border-color: var(--sv-accent);
}

.sv-fs-banner-pick input:focus-visible + .sv-fs-banner-swatch {
    outline: 2px solid var(--sv-focus);
    outline-offset: 2px;
}

.sv-fs-banner-name {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    font-weight: 600;
}

.sv-fs-banner-pick input:checked ~ .sv-fs-banner-name {
    color: var(--sv-text);
}

/* --- Налаштування форми, фаза 2: хто відповідає, квіз, посилання --- */

/* display: grid перебив би атрибут hidden (пастка, що вже кусала). */
.sv-fs-radios[hidden],
.sv-fs-audience[hidden] {
    display: none !important;
}

/* «Хто відповідає» — без власної підкладки: блок і так на картці (Паша 16.10). */
.sv-fs-audience {
    display: grid;
    gap: var(--sv-s4);
    margin-top: var(--sv-s3);
}

.sv-fs-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s2);
    margin: var(--sv-s2) 0;
    padding: 0;
    list-style: none;
}

/* «Умови для учнів» і «Окремі учні» — окремими групами з відступом між
   ними, а не впритул (Паша 16.10). */
.sv-fs-aud-pick {
    gap: var(--sv-s5);
}

/* Порожній список учнів не тримає місця над пошуком (і після того, як
   прибрали останнього, — без перезавантаження). */
.sv-fs-chips:not(:has(li)) {
    display: none;
}

.sv-fs-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s1);
    min-height: 32px;
    padding: 0 var(--sv-s1) 0 var(--sv-s3);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-pill);
    background: var(--sv-surface);
    font-size: var(--sv-fs-base);
    font-weight: 600;
}

.sv-fs-chip-x {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border: 0;
    border-radius: var(--sv-radius-pill);
    background: transparent;
    color: var(--sv-text-muted);
    cursor: pointer;
}

.sv-fs-chip-x:hover {
    background: var(--sv-bg);
    color: var(--sv-text);
}

.sv-fs-aud-search {
    max-width: 420px;
}

.sv-fs-aud-list {
    display: grid;
    gap: var(--sv-s1);
    max-width: 520px;
    margin: var(--sv-s2) 0 0;
    padding: 0;
    list-style: none;
}

.sv-fs-aud-add {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sv-s1) var(--sv-s2);
    width: 100%;
    min-height: 40px;
    padding: var(--sv-s2) var(--sv-s3);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    background: var(--sv-surface);
    color: var(--sv-text);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.sv-fs-aud-add:hover {
    border-color: var(--sv-accent);
}

.sv-fs-aud-name {
    font-weight: 700;
}

.sv-fs-rules {
    display: grid;
    gap: var(--sv-s2);
    margin-top: var(--sv-s2);
}

.sv-fs-level {
    display: grid;
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) minmax(0, 1fr) 34px;
    gap: var(--sv-s2);
    align-items: center;
}

.sv-fs-level-rows {
    display: grid;
    gap: var(--sv-s2);
}

.sv-fs-level-add {
    justify-self: start;
}

/* Рядок умови: поле | його оператор і значення | «×». */
.sv-fs-rule {
    display: grid;
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 2fr) auto;
    gap: var(--sv-s2);
    align-items: center;
}

.sv-fs-rule-kit {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--sv-s2);
    min-width: 0;
}

.sv-fs-rule-add {
    align-self: flex-start;
}

.sv-fs-rules:empty {
    display: none;
}

/* Виняток поза умовами — ледь помітна позначка, не тривога. */
.sv-fs-chip-note {
    display: none;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 500;
}

.sv-fs-chip.is-outside .sv-fs-chip-note {
    display: inline;
}

.sv-fs-chip-test {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s1);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 500;
}

/* «Лише серед цих учнів»: хто не проходить умов, не відповідатиме — помітніше. */
.sv-fs-chip.is-pool.is-outside {
    border-color: var(--sv-warning);
}

.sv-fs-chip.is-pool.is-outside .sv-fs-chip-note {
    color: var(--sv-warning);
}

.sv-fs-aud-count {
    margin: 0;
    font-weight: 700;
}

/* Через бота: галочка й команда «/english_test» під списком тих, хто відповідає. */
.sv-fs-bot {
    display: grid;
    gap: var(--sv-s3);
    padding-top: var(--sv-s3);
    border-top: 1px solid var(--sv-border);
}

.sv-fs-bot-input {
    display: flex;
    align-items: center;
    gap: var(--sv-s1);
    max-width: 22rem;
    font-weight: 700;
}

.sv-fs-bot-input .sv-input {
    flex: 1;
    min-width: 0;
}

.sv-fs-levels {
    display: grid;
    gap: var(--sv-s2);
    min-width: 0;
    margin: var(--sv-s3) 0 0;
    padding: 0;
    border: 0;
}

.sv-fs-levels > legend {
    margin-bottom: var(--sv-s2);
}

.sv-fs-level.is-head {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    font-weight: 700;
}

.sv-fs-links {
    margin: 0;
}

/* «Показати всі посилання» — кнопка, що розгортається (стрілка повертається);
   усередині вгорі — дії текстом, риска, далі учні (Паша 16.10). */
.sv-fs-links > summary {
    gap: var(--sv-s1);
    list-style: none;
    cursor: pointer;
}

.sv-fs-links > summary::-webkit-details-marker {
    display: none;
}

.sv-fs-links-tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sv-s2) var(--sv-s4);
    margin-top: var(--sv-s3);
    padding-bottom: var(--sv-s2);
    border-bottom: 1px solid var(--sv-border);
    font-size: var(--sv-fs-sm);
}

.sv-fs-links-tools form {
    margin: 0;
}

.sv-fs-links-list {
    display: grid;
    gap: var(--sv-s1);
    max-height: 420px;
    margin: var(--sv-s2) 0 0;
    padding: 0;
    overflow: auto;
    list-style: none;
}

.sv-fs-links-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: var(--sv-s2);
    align-items: center;
    padding: var(--sv-s1) 0;
    border-bottom: 1px solid var(--sv-border);
}

.sv-fs-links-name {
    font-weight: 600;
    overflow-wrap: anywhere;
}

/* Номер учня — тихо, сірим, у стовпчик (однакова ширина цифр). */
.sv-fs-links-n {
    display: inline-block;
    min-width: 3ch;
    margin-right: var(--sv-s2);
    color: var(--sv-text-muted);
    font-weight: 400;
    font-variant-numeric: tabular-nums;
}

.sv-fs-links-url {
    /* Посилання не показуємо (довге й однакове на вигляд) — його копіює кнопка. */
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* Тестові акаунти — під списком учнів, окремо (Паша 19.10). */
.sv-fs-links-tests {
    display: grid;
    gap: var(--sv-s1);
    margin-top: var(--sv-s4);
}

.sv-fs-links-tests .sv-fs-links-list {
    margin-top: 0;
}

.sv-fs-links-tests .sv-fs-reissue {
    margin: 0;
}

.sv-fs-links-status {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
}

.sv-fs-links-status.is-done {
    color: var(--sv-success);
    font-weight: 700;
}

.sv-fs-links-status.is-active {
    color: var(--sv-warning);
    font-weight: 700;
}

@media (max-width: 640px) {
    .sv-fs-level {
        grid-template-columns: 1fr;
    }

    .sv-fs-rule {
        grid-template-columns: minmax(0, 1fr) auto;
    }

    .sv-fs-rule-kit {
        grid-column: 1 / -1;
        grid-row: 2;
    }
}

/* --- Конструктор, фаза 2: ключ відповіді, проходження сторінки, медіа --- */

.sv-qb-okey {
    position: relative;
    display: inline-flex;
    flex-shrink: 0;
    cursor: pointer;
}

.sv-qb-okey input {
    position: absolute;
    opacity: 0;
}

.sv-qb-okey-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-pill);
    color: transparent;
    transition: background-color var(--sv-dur) var(--sv-ease), color var(--sv-dur) var(--sv-ease);
}

.sv-qb-okey input:checked + .sv-qb-okey-mark {
    border-color: var(--sv-success);
    background: var(--sv-tint-sage);
    color: var(--sv-success);
}

.sv-qb-okey input:focus-visible + .sv-qb-okey-mark {
    outline: 2px solid var(--sv-focus);
    outline-offset: 2px;
}

.sv-qb-key,
.sv-qb-audio {
    display: grid;
    gap: var(--sv-s2);
    margin-top: var(--sv-s3);
    padding: var(--sv-s3);
    border-radius: var(--sv-radius);
    background: var(--sv-bg);
}

/* Бали — коробкою праворуч у блоці відповіді (варіанти чи поля ключа), а
   не окремою секцією на всю ширину (Паша 17.10). Вузький екран — під
   блоком, праворуч, рядком «Балів [1]». */
.sv-qb-answer.has-points,
.sv-qb-key.has-points {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: start;
    gap: var(--sv-s3);
}

.sv-qb-key-fields {
    display: grid;
    gap: var(--sv-s2);
    min-width: 0;
}

/* Бали — одним рядком і дрібно: «Бали (i) [1]» (Паша 18.10). */
.sv-qb-points-box {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s15);
    padding: var(--sv-s1) var(--sv-s1) var(--sv-s1) var(--sv-s2);
    border-radius: var(--sv-radius-sm);
    background: var(--sv-bg);
    white-space: nowrap;
}

.sv-qb-key .sv-qb-points-box {
    background: var(--sv-surface);
}

.sv-qb-points-head {
    display: flex;
    align-items: center;
    gap: var(--sv-s1);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 600;
}

.sv-qb-points-box .sv-input {
    width: 44px;
    min-height: 0;
    height: 28px;
    padding: 0 var(--sv-s1);
    text-align: center;
}

.sv-qb-not-scored {
    margin-top: var(--sv-s3);
}

@media (max-width: 560px) {
    .sv-qb-answer.has-points,
    .sv-qb-key.has-points {
        grid-template-columns: minmax(0, 1fr);
    }

    .sv-qb-points-box {
        justify-self: end;
    }
}

/* «Як учень проходить цю сторінку» — стовпчиком, а не рядом полів, що
   налазили одне на одне (Паша 16.10). */
.sv-qb-flow-body {
    display: grid;
    gap: var(--sv-s4);
    max-width: 560px;
    padding-top: var(--sv-s3);
}

.sv-qb-gate {
    display: grid;
    gap: var(--sv-s2);
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
}

.sv-qb-gate > legend {
    padding: 0;
    color: var(--sv-text);
}

/* «Скільки помилок» вузьке, сторінка — решта рядка. */
.sv-qb-gate-row {
    display: grid;
    grid-template-columns: minmax(0, 150px) minmax(0, 1fr);
    gap: var(--sv-s2) var(--sv-s3);
    align-items: start;
}

.sv-qb-media-form {
    margin: 0;
}

.sv-qb-media-current {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2) var(--sv-s3);
}

.sv-qb-media-pick {
    position: relative;
    cursor: pointer;
}

.sv-qb-media-thumb {
    max-width: 160px;
    max-height: 90px;
    border-radius: var(--sv-radius);
    object-fit: cover;
}

.sv-qb-media-audio {
    max-width: 280px;
    height: 36px;
}

.sv-qb-media-name {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    overflow-wrap: anywhere;
}

/* --- Відповіді, фаза 2: учень, бали, сигнали, правильно / ні --- */

.sv-fr-student {
    display: grid;
    gap: var(--sv-s1);
    margin: var(--sv-s3) 0;
    padding: var(--sv-s3) var(--sv-s4);
    border-radius: var(--sv-radius);
    background: var(--sv-bg);
}

.sv-fr-student-name {
    font-size: var(--sv-fs-md);
}

.sv-fr-score {
    font-weight: 700;
}

.sv-fr-groups {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sv-s1) var(--sv-s4);
    margin: 0;
    padding: 0;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    list-style: none;
}

.sv-fr-signals {
    margin: 0;
    color: var(--sv-warning);
    font-size: var(--sv-fs-sm);
}

.sv-fr-mark {
    font-weight: 800;
}

.sv-fr-answers dd.is-right .sv-fr-mark {
    color: var(--sv-success);
}

.sv-fr-answers dd.is-wrong .sv-fr-mark {
    color: var(--sv-maroon);
}

.sv-fr-key {
    display: block;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
}

.sv-fs-later {
    display: grid;
    gap: var(--sv-s3);
}

.sv-fs-later > .sv-panel-head {
    margin-bottom: 0;
}

/* --- Вкладка «Відповіді» форми ---
   Смуги зведення — та сама мова, що позначки дашборда: сейджева заливка на
   спокійній доріжці, число й відсоток праворуч; без бібліотек графіків. */
.sv-fr-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2) var(--sv-s4);
    margin-bottom: var(--sv-s3);
}

.sv-fr-count {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sv-s1) var(--sv-s2);
}

.sv-fr-muted {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
}

.sv-fr-views {
    display: inline-flex;
    gap: var(--sv-s1);
    padding: var(--sv-s05);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-pill);
}

.sv-fr-view {
    display: inline-flex;
    align-items: center;
    min-height: var(--sv-ctrl-h-sm);
    padding: 0 var(--sv-s3);
    border-radius: var(--sv-radius-pill);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    font-weight: 700;
    text-decoration: none;
}

.sv-fr-view:hover {
    color: var(--sv-text);
}

.sv-fr-view.is-active {
    background: var(--sv-primary);
    color: var(--sv-on-primary);
}

.sv-fr-filter {
    margin: 0;
    min-width: 0;
}

.sv-fr-export {
    display: inline-flex;
    gap: var(--sv-s2);
    margin-left: auto;
}

.sv-fr-list {
    display: grid;
    gap: var(--sv-s3);
}

/* Хто відповів (Паша 20.10): картки сіткою в обмеженому блоці, що
   гортається всередині, — як список відповідей у Google Forms. */
.sv-fr-people-panel {
    display: grid;
    gap: var(--sv-s3);
    margin-bottom: var(--sv-s3);
}

.sv-fr-people-title {
    margin: 0;
    font-size: var(--sv-fs-md);
    font-weight: 700;
}

.sv-fr-people {
    max-height: 24rem;
    overflow-y: auto;
    overscroll-behavior: contain;
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
}

/* Таблиця «Хто відповів» (Паша 20.10): компактно, без горизонтальної
   прокрутки; рядок цілком — посилання на відповідь (::after назви). */
.sv-fr-ptable {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--sv-fs-sm);
    font-variant-numeric: tabular-nums;
}

.sv-fr-ptable th {
    position: sticky;
    top: 0;
    z-index: 1;
    padding: var(--sv-s2) var(--sv-s1);
    border-bottom: 1px solid var(--sv-border);
    background: var(--sv-surface);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 600;
    line-height: 1.2;
    text-align: left;
    vertical-align: bottom;
}

.sv-fr-ptable td {
    padding: var(--sv-s2) var(--sv-s1);
    border-bottom: 1px solid var(--sv-border);
    vertical-align: middle;
}

.sv-fr-ptable tbody tr:last-child td {
    border-bottom: 0;
}

.sv-fr-ptable :is(th, td).is-num {
    text-align: right;
}

/* Числа — не розриваються; заголовки стовпців — у два рядки, якщо тісно. */
.sv-fr-ptable td.is-num {
    white-space: nowrap;
}

.sv-fr-ptable :is(th, td):first-child {
    padding-left: var(--sv-s3);
}

/* Між стовпцями — повітря (Паша 21.10): ім'я й так займає решту ширини,
   а числа праворуч стояли впритул одне до одного. */
.sv-fr-ptable :is(th, td) + :is(th, td) {
    padding-left: var(--sv-s4);
}

.sv-fr-ptable tr {
    position: relative;
}

.sv-fr-ptable tbody tr:hover {
    background: var(--sv-bg);
}

.sv-fr-ptable tr.is-excluded {
    opacity: 0.55;
}

.sv-fr-pname {
    width: 100%;
    min-width: 8rem;
}

.sv-fr-plink {
    color: var(--sv-text);
    font-weight: 600;
    text-decoration: none;
    overflow-wrap: anywhere;
}

.sv-fr-plink::after {
    content: "";
    position: absolute;
    inset: 0;
}

.sv-fr-plink:focus-visible {
    outline: none;
}

.sv-fr-ptable tr:has(.sv-fr-plink:focus-visible) {
    outline: 2px solid var(--sv-focus, var(--sv-sage));
    outline-offset: -2px;
}

.sv-fr-pmax {
    color: var(--sv-text-muted);
}

/* Сигнали чесності, що є (виходи, час поза сторінкою, вставки), — жовтим,
   але тим самим накресленням, що й сусідні числа (Паша 21.10). */
.sv-fr-ptable td.is-warn {
    color: var(--sv-warning);
}

.sv-fr-pflags {
    padding-right: var(--sv-s3);
}

.sv-fr-flag {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s1);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    white-space: nowrap;
}

.sv-fr-flag.is-warn {
    color: var(--sv-warning);
}

.sv-fr-flag + .sv-fr-flag {
    margin-left: var(--sv-s2);
}

/* Телефон: рядок у два — ім'я зверху, решта підписами в рядок. */
@media (max-width: 640px) {
    .sv-fr-ptable thead {
        display: none;
    }

    .sv-fr-ptable tbody tr {
        display: flex;
        flex-wrap: wrap;
        align-items: baseline;
        gap: var(--sv-s1) var(--sv-s3);
        padding: var(--sv-s2);
        border-bottom: 1px solid var(--sv-border);
    }

    .sv-fr-ptable tbody td {
        padding: 0;
        border: 0;
    }

    /* Порожнє й нульові вставки / копіювання — без підписів-нулів. */
    .sv-fr-ptable tbody td:is(:empty, .is-zero) {
        display: none;
    }

    /* padding-left: 0 — ім'я на одній лінії з рештою рядка (правило
       :first-child вище сильніше за «td без відступів»). */
    .sv-fr-ptable td.sv-fr-pname {
        flex-basis: 100%;
        padding-left: 0;
    }

    .sv-fr-ptable td[data-label]::before {
        content: attr(data-label) " ";
        color: var(--sv-text-muted);
        font-weight: 400;
    }

    .sv-fr-pflags {
        white-space: normal;
    }
}


.sv-fr-q {
    display: grid;
    gap: var(--sv-s2);
}

/* Прибране питання — пунктиром: воно вже не в формі, лише в історії. */
.sv-fr-q.is-removed {
    border-style: dashed;
}

.sv-fr-qhead {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s1) var(--sv-s2);
}

.sv-fr-qtitle {
    margin: 0;
    font-size: var(--sv-fs-md);
    font-weight: 700;
    overflow-wrap: anywhere;
}

.sv-fr-answered {
    margin: 0;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 600;
}

.sv-qb-chip.is-warn {
    background: var(--sv-tint-amber);
    color: var(--sv-warning);
}

.sv-fr-bars {
    display: grid;
    gap: var(--sv-s2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.sv-fr-row {
    display: grid;
    grid-template-columns: minmax(8rem, 2fr) minmax(0, 3fr) auto;
    align-items: center;
    gap: var(--sv-s3);
    font-size: var(--sv-fs-sm);
}

.sv-fr-bars.is-scale .sv-fr-row {
    grid-template-columns: 2.5rem minmax(0, 1fr) auto;
}

.sv-fr-label {
    min-width: 0;
    overflow-wrap: anywhere;
}

.sv-fr-track {
    display: block;
    height: 10px;
    border-radius: var(--sv-radius-pill);
    background: var(--sv-bg);
    overflow: hidden;
}

.sv-fr-fill {
    display: block;
    height: 100%;
    border-radius: var(--sv-radius-pill);
    background: var(--sv-sage);
}

.sv-fr-num {
    color: var(--sv-text-muted);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    text-align: right;
}

.sv-fr-stat {
    margin: 0;
    font-size: var(--sv-fs-sm);
}

/* Ціль натискання — не лише рядок тексту: висота компактного контролу
   (на телефоні — 44px, нижче). */
.sv-fr-more > summary {
    display: flex;
    align-items: center;
    min-height: var(--sv-ctrl-h-sm);
    color: var(--sv-link);
    font-size: var(--sv-fs-sm);
    font-weight: 700;
    cursor: pointer;
}

.sv-fr-more[open] > summary {
    margin-bottom: var(--sv-s2);
}

/* Текстові відповіді — цитатами зі смугою ліворуч, переноси як написано. */
.sv-fr-texts {
    display: grid;
    gap: var(--sv-s1);
    margin: 0;
    padding: 0;
    list-style: none;
}

.sv-fr-texts > li {
    padding: var(--sv-s2) var(--sv-s3);
    border-left: 3px solid var(--sv-sage);
    border-radius: 0 var(--sv-radius-sm) var(--sv-radius-sm) 0;
    background: var(--sv-surface-alt);
    font-size: var(--sv-fs-sm);
    line-height: 1.5;
    white-space: pre-line;
    overflow-wrap: anywhere;
}

.sv-fr-texts.is-inline {
    display: flex;
    flex-wrap: wrap;
}

.sv-fr-texts.is-inline > li {
    border-left: 0;
    border-radius: var(--sv-radius-pill);
}

/* Зведення таблиці: рядки × стовпці, найчастіший вибір рядка — виділено. */
.sv-fr-grid-wrap {
    overflow-x: auto;
}

.sv-fr-grid {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--sv-fs-sm);
}

.sv-fr-grid :is(th, td) {
    padding: var(--sv-s2) var(--sv-s3);
    border-bottom: 1px solid var(--sv-border);
    text-align: center;
}

.sv-fr-grid thead th {
    color: var(--sv-text-muted);
    font-weight: 600;
    vertical-align: bottom;
}

.sv-fr-grid th[scope="row"] {
    /* Не вужче за кілька слів: на телефоні назва рядка ламалась по літері
       в стовпчик; тісно — таблиця гортається вбік у своїй рамці. */
    min-width: 8rem;
    font-weight: 600;
    text-align: left;
    overflow-wrap: break-word;
}

.sv-fr-grid td {
    white-space: nowrap;
}

.sv-fr-grid td small {
    color: var(--sv-text-muted);
}

.sv-fr-grid td.is-top {
    background: var(--sv-tint-sage);
    font-weight: 700;
}

.sv-fr-dates {
    display: grid;
    gap: var(--sv-s1);
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: var(--sv-fs-sm);
}

.sv-fr-dates > li {
    display: flex;
    justify-content: space-between;
    gap: var(--sv-s3);
    max-width: 16rem;
}

/* --- По одній --- */
.sv-fr-one {
    display: grid;
    gap: var(--sv-s3);
}

.sv-fr-one.is-excluded {
    border-color: var(--sv-warning);
}

.sv-fr-pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sv-s2);
}

.sv-fr-pager .is-disabled {
    opacity: 0.45;
    pointer-events: none;
}

.sv-fr-pos {
    font-variant-numeric: tabular-nums;
}

.sv-fr-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
    margin: 0;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
}

.sv-fr-answers {
    display: grid;
    gap: var(--sv-s1);
    margin: 0;
}

.sv-fr-answers dt {
    font-weight: 700;
    overflow-wrap: anywhere;
}

.sv-fr-answers dd {
    margin: 0 0 var(--sv-s3);
    overflow-wrap: anywhere;
}

.sv-fr-answers dd.is-long {
    white-space: pre-line;
    line-height: 1.5;
}

.sv-fr-answers dd.is-empty {
    color: var(--sv-text-muted);
    font-style: italic;
}

.sv-fr-exclude {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2) var(--sv-s3);
    margin: 0;
    padding-top: var(--sv-s3);
    border-top: 1px solid var(--sv-border);
}

@media (max-width: 600px) {
    /* Підпис — над смугою: на телефоні довгий варіант не стискає доріжку в нитку. */
    .sv-fr-row {
        grid-template-columns: minmax(0, 1fr) auto;
    }

    .sv-fr-row .sv-fr-label {
        grid-column: 1 / -1;
    }

    .sv-fr-bars.is-scale .sv-fr-row {
        grid-template-columns: 2rem minmax(0, 1fr) auto;
    }

    .sv-fr-bars.is-scale .sv-fr-row .sv-fr-label {
        grid-column: auto;
    }

    .sv-fr-export {
        margin-left: 0;
    }

    .sv-fr-more > summary,
    .sv-fr-filter .sv-select {
        min-height: 44px;
    }
}

/* Бейдж під поштою у вкладці «Доступ» — з відступом, а не впритул. */
.sv-form-access-table td > .sv-table-badge {
    display: inline-block;
    margin-top: var(--sv-s2);
}

/* --- Сторінка форми для учня (перегляд і /forms/<id>) ---
   Одна вузька колонка, великі цілі для пальця (≥ 44px): її відкривають з
   телефона. Мова — з налаштувань форми; тон — як у форми Buddy (.sv-fb). */
.sv-fp-page {
    background: var(--sv-bg);
    color: var(--sv-text);
}

/* JS ховає кнопки «Назад / Далі / Надіслати», смугу прогресу й питання
   атрибутом hidden — а .sv-btn та інші класи з display його перебивали б. */
.sv-fp [hidden] {
    display: none !important;
}

.sv-fp {
    max-width: 640px;
    margin: 0 auto;
    padding: var(--sv-s5) var(--sv-s4) var(--sv-s6, 48px);
}

/* Плашка перегляду — для того, хто править форму; учень її не бачить. */
.sv-fp-preview {
    display: grid;
    gap: var(--sv-s2);
    margin-bottom: var(--sv-s4);
    padding: var(--sv-s3);
    border: 1px dashed var(--sv-warning);
    border-radius: var(--sv-radius-sm);
    color: var(--sv-text);
    font-size: var(--sv-fs-sm);
}

/* Плашка перегляду: текст — жовтим (як і рамка, «це перегляд»), вкладки —
   сегменти з рамкою, дії — кнопки: інакше текст і вкладки зливались (Паша 16.10). */
.sv-fp-preview p {
    margin: 0;
    color: var(--sv-warning);
    font-weight: 700;
}

.sv-fp-preview-nav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s1) var(--sv-s2);
}

.sv-fp-preview-label {
    color: var(--sv-text-muted);
}

.sv-fp-preview-nav a {
    display: inline-flex;
    align-items: center;
    min-height: 32px;
    padding: 0 var(--sv-s3);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-pill);
    background: var(--sv-surface);
    color: var(--sv-text);
    font-weight: 600;
    text-decoration: none;
}

@media (hover: hover) {
    .sv-fp-preview-nav a:hover {
        border-color: var(--sv-text-muted);
    }
}

.sv-fp-preview-nav a[aria-current="page"] {
    border-color: var(--sv-sage);
    background: var(--sv-tint-sage);
    color: var(--sv-on-tint);
}

.sv-fp-preview-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2) var(--sv-s4);
}

.sv-fp-preview-actions form {
    margin: 0;
}

/* Коли праворуч від колонки форми (640) є місце — плашка там, а не згори:
   не забирає верх сторінки (Паша 18.10). Вужче — як була, над формою. */
@media (min-width: 1240px) {
    .sv-fp-preview {
        position: fixed;
        top: var(--sv-s5);
        left: calc(50% + 320px + var(--sv-s5));
        z-index: 5;
        width: 260px;
        max-height: calc(100vh - 2 * var(--sv-s5));
        margin: 0;
        overflow-y: auto;
        background: var(--sv-bg);
    }
}

/* Логотип школи (сторінки для учнів): картинка з токена --sv-brand-logo,
   тож темна тема міняє файл без зміни розмітки. Пропорції — як у файлі. */
.sv-brand-logo {
    display: block;
    aspect-ratio: 540 / 498;
    background: var(--sv-brand-logo) center / contain no-repeat;
}

/* Банер — окремо від назви (як у Google Forms): градієнт і логотип по
   центру. Кадр логотипа вже центрований по напису, тож center — оптичний. */
.sv-fp-banner {
    height: 180px;
    margin-bottom: var(--sv-s3);
    border-radius: var(--sv-radius-lg);
    background: var(--sv-brand-banner) center / auto 82% no-repeat, var(--banner, var(--sv-banner-brand));
}

/* Який градієнт — клас sv-banner-<назва> (банер учня й мініатюра в налаштуваннях). */
.sv-banner-brand { --banner: var(--sv-banner-brand); }
.sv-banner-dawn { --banner: var(--sv-banner-dawn); }
.sv-banner-meadow { --banner: var(--sv-banner-meadow); }
.sv-banner-night { --banner: var(--sv-banner-night); }
.sv-banner-deep { --banner: var(--sv-banner-deep); }

/* Назва, опис і обіцянка приватності — своя картка під банером. */
.sv-fp-hero {
    display: grid;
    justify-items: start;
    gap: var(--sv-s2);
    margin-bottom: var(--sv-s4);
    padding: var(--sv-s5);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-lg);
    background: var(--sv-surface);
    color: var(--sv-text);
}


.sv-fp-title {
    margin: 0;
    color: inherit;
    font-size: var(--sv-fs-xl);
    font-weight: 800;
    line-height: 1.2;
    letter-spacing: -0.01em;
    overflow-wrap: anywhere;
    text-wrap: balance;
}

.sv-fp-desc {
    margin: 0;
    color: var(--sv-text);
    line-height: 1.6;
    white-space: pre-line;
    overflow-wrap: anywhere;
}

/* Обіцянка приватності — чіп у шапці. Може бути у два рядки, тож радіус
   числом, не пігулкою. */
.sv-fp-privacy {
    display: flex;
    align-items: flex-start;
    gap: var(--sv-s2);
    margin: var(--sv-s2) 0 0;
    padding: var(--sv-s2) var(--sv-s3);
    border-radius: var(--sv-radius);
    background: var(--sv-tint-sage);
    color: var(--sv-on-tint);
    font-size: var(--sv-fs-sm);
    font-weight: 600;
    line-height: 1.45;
}

.sv-fp-privacy svg {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    margin-top: var(--sv-s05);
    color: var(--sv-success);
}

.sv-fp-required-note {
    margin: var(--sv-s1) 0 0;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
}


/* Після першої сторінки (кроку) — лише банер, назва й «обов'язкове», як у
   Google Forms: опис, «Відповідає» й обіцянку приватності учень уже бачив. */
.sv-fp.is-later .sv-fp-student,
.sv-fp.is-later .sv-fp-desc,
.sv-fp.is-later .sv-fp-privacy {
    display: none;
}

/* Тонкий рядок під карткою назви: смуга прогресу й таймер праворуч. Лише він
   закріплюється вгорі, поки гортаєш питання (тло сторінки під ним). */
.sv-fp-bar {
    position: sticky;
    top: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    gap: var(--sv-s3);
    margin: 0 0 var(--sv-s4);
    padding: var(--sv-s2) 0;
    background: var(--sv-bg);
}

.sv-fp-bar > .sv-fp-progress {
    flex: 1 1 auto;
    min-width: 0;
}

.sv-fp-bar > .sv-fp-timer {
    margin-left: auto;
}

/* Тест (Паша 21.10): угорі в одну висоту «РОЗДІЛ 7 З 7» ліворуч і час
   праворуч, під ними смуга на всю ширину тесту. Без розділів (одна
   сторінка) — лише час праворуч. */
.sv-fp-bar.has-head {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--sv-s2) var(--sv-s3);
}

.sv-fp-bar.has-head > .sv-fp-stage-label {
    grid-area: 1 / 1;
}

.sv-fp-bar.has-head > .sv-fp-timer {
    grid-area: 1 / 2;
    margin-left: 0;
}

.sv-fp-bar.has-head > .sv-fp-progress {
    grid-area: 2 / 1 / 3 / -1;
}

/* Розділ і час — один стиль: дрібний сірий капс однакового кольору й
   розміру (Паша 21.10). */
.sv-fp-stage-label,
.sv-fp-timer {
    margin: 0;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 600;
    line-height: 1;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

/* Пояснення від сервера над формою (наприклад, «надішли ще раз»). */
.sv-fp-notice {
    margin: 0 0 var(--sv-s4);
    padding: var(--sv-s3) var(--sv-s4);
    border: 1px solid var(--sv-warning);
    border-radius: var(--sv-radius);
    background: var(--sv-tint-amber);
    color: var(--sv-text);
}

/* «На цьому пристрої є недописане» — питання, а не підставлені відповіді:
   «Продовжити» чи «Почати заново» (form_public.js). */
.sv-fp-restored {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2) var(--sv-s3);
    padding: var(--sv-s3) var(--sv-s4);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    background: var(--sv-surface);
    font-size: var(--sv-fs-sm);
}

.sv-fp-restored p {
    flex: 1 1 100%;
    margin: 0;
}

.sv-fp-restart {
    min-height: 44px;
}

/* Поле-пастка для ботів: за межами екрана, а не display:none — частина
   ботів поля з display:none пропускає. Людина й читач екрана його не бачать. */
.sv-fp-hp {
    position: absolute;
    left: -10000px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.sv-fp-form {
    --sv-fp-mark: var(--sv-check);
    /* Кружечки без свого стилю (таблиця варіантів) — тим самим кольором
       вибору, що й варіанти: у тесті золото (Паша 21.10, одноманітно). */
    --sv-radio: var(--sv-fp-mark);
    display: grid;
    gap: var(--sv-s4);
}

.sv-fp-form.is-quiz {
    --sv-fp-mark: var(--sv-accent);
    /* Галочка на золоті — темна (загальний чекбокс, --sv-check-ink). */
    --sv-check-ink: var(--sv-primary-dark);
}

/* Сторінки: без JS видно всі підряд; з JS — лише поточна. */
.sv-fp-section {
    display: grid;
    gap: var(--sv-s4);
    outline: none;
}

.sv-fp-form.is-paged .sv-fp-section:not(.is-current) {
    display: none;
}

.sv-fp-section-head {
    padding: var(--sv-s4);
    border-radius: var(--sv-radius);
    background: var(--sv-primary);
    color: var(--sv-on-primary);
}

.sv-fp-section-head h2 {
    margin: 0;
    font-size: var(--sv-fs-md);
    color: inherit;
}

.sv-fp-section-head .sv-fp-section-desc {
    margin: var(--sv-s2) 0 0;
    line-height: 1.5;
    white-space: pre-line;
}

.sv-fp-block {
    padding: var(--sv-s5);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    background: var(--sv-surface);
}

/* Заголовок текстового блоку й заголовок питання — однакові (Паша 14.10:
   жирність різнилась, 700 і 800); текст блоку й пояснення під питанням —
   звичайним накресленням, помітно тонше за заголовки. Текст блоку — основним
   кольором, як опис сторінки: це те, що читають (тексти Reading); сірим —
   лише пояснення під питанням (Паша 16.10). */
.sv-fp-block-title {
    margin: 0;
    font-size: var(--sv-fs-md);
    font-weight: 700;
    line-height: 1.4;
    letter-spacing: -0.01em;
}

.sv-fp-block-text {
    margin: var(--sv-s2) 0 0;
    font-weight: 400;
    line-height: 1.55;
    white-space: pre-line;
    overflow-wrap: anywhere;
}

.sv-fp-q {
    min-width: 0;
    margin: 0;
    padding: var(--sv-s5);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    background: var(--sv-surface);
    transition: box-shadow var(--sv-dur) var(--sv-ease), border-color var(--sv-dur) var(--sv-ease);
}

/* Питання, в якому зараз курсор, — сейджева смужка зліва: видно, де ти. */
.sv-fp-q:focus-within {
    box-shadow: inset 3px 0 0 var(--sv-sage);
}

.sv-fp-q.is-invalid {
    border-color: var(--sv-maroon);
}

.sv-fp-q.is-invalid:focus-within {
    box-shadow: inset 3px 0 0 var(--sv-maroon);
}

/* Питання — головне в картці (Title з DESIGN.md): помітно більше й жирніше
   за варіанти відповіді. */
.sv-fp-legend {
    float: left;
    width: 100%;
    margin-bottom: var(--sv-s3);
    padding: 0;
    font-size: var(--sv-fs-md);
    font-weight: 700;
    line-height: 1.4;
    letter-spacing: -0.01em;
    overflow-wrap: anywhere;
    text-wrap: pretty;
    white-space: pre-line;
}

/* Підказка тримається свого питання, а не поля під нею. */
.sv-fp-q:has(> .sv-fp-qdesc) > .sv-fp-legend {
    margin-bottom: var(--sv-s1);
}

.sv-fp-num,
.sv-fp-untitled {
    color: var(--sv-text-muted);
}

.sv-fp-untitled {
    font-weight: 500;
    font-style: italic;
}

.sv-fp-req {
    color: var(--sv-required);
}

/* Лише для читачів екрана: «(обов'язкове)» замість зірочки. */
.sv-fp-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

.sv-fp-qdesc {
    clear: both;
    margin: 0 0 var(--sv-s3);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-base);
    font-weight: 400;
    line-height: 1.5;
    white-space: pre-line;
}

.sv-fp-options {
    clear: both;
    display: grid;
    gap: var(--sv-s2);
}

/* Варіант — уся смуга з рамкою натискається (≥ 48px), а не лише кружечок:
   на телефоні видно, що це кнопки вибору. Обраний — сейджем. */
.sv-fp-option {
    --sv-control-size: 20px;

    display: flex;
    align-items: center;
    gap: var(--sv-s3);
    min-height: var(--sv-option-h);
    /* Однорядковий варіант — по центру висоти; довший росте вниз. Вирівнювання
       контрола — «Контрол поруч із підписом» у кінці файлу. */
    /* --sv-s05 — дві рамки по 1px: висота рахується разом із ними. */
    padding: calc((var(--sv-option-h) - var(--sv-s05) - 1lh) / 2) var(--sv-s3);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-sm);
    background: var(--sv-surface-alt);
    cursor: pointer;
    overflow-wrap: anywhere;
    transition: border-color var(--sv-dur) var(--sv-ease), background var(--sv-dur) var(--sv-ease);
    /* Без синього спалаху Android при дотику — вибір і так видно. */
    -webkit-tap-highlight-color: transparent;
}

/* Ховер — лише там, де є миша: на телефоні він «залипає» після дотику. */
@media (hover: hover) {
    .sv-fp-option:hover {
        border-color: var(--sv-sage);
    }

    .sv-fp-input:hover {
        border-color: var(--sv-text-muted);
    }
}

.sv-fp-option:has(input:checked) {
    border-color: var(--sv-sage);
    background: var(--sv-tint-sage);
    font-weight: 600;
}

/* Свій кружечок і квадратик, як у Google Forms: тонке кільце, у вибраного —
   крапка всередині (галочка в квадратику). Штатний accent-color у Chrome
   малює товсте темне кільце й світлішає при наведенні (Паша 16.10). Колір —
   --sv-fp-mark: у формі — основний, у тесті — золото. */
.sv-fp-option input:is([type="radio"], [type="checkbox"]) {
    flex-shrink: 0;
    display: inline-grid;
    place-items: center;
    width: var(--sv-ctl);
    height: var(--sv-ctl);
    border: 2px solid var(--sv-text-muted);
    background: var(--sv-surface);
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    transition: border-color var(--sv-dur) var(--sv-ease), background-color var(--sv-dur) var(--sv-ease);
}

.sv-fp-option input[type="radio"] {
    border-radius: 50%;
}

.sv-fp-option input[type="radio"]::before {
    content: "";
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--sv-fp-mark);
    transform: scale(0);
    transition: transform var(--sv-dur) var(--sv-ease);
}

.sv-fp-option input[type="checkbox"] {
    border-radius: 4px;
}

.sv-fp-option input:is([type="radio"], [type="checkbox"]):checked {
    border-color: var(--sv-fp-mark);
}

.sv-fp-option input[type="radio"]:checked::before {
    transform: scale(1);
}

/* Галочка — із загального чекбокса (::before, колір --sv-check-ink). */
.sv-fp-option input[type="checkbox"]:checked {
    background: var(--sv-fp-mark);
}

.sv-fp-option input:is([type="radio"], [type="checkbox"]):focus-visible {
    outline: 2px solid var(--sv-fp-mark);
    outline-offset: 2px;
}

/* Тест (квіз): вибір — брендовим золотом, нейтральним, як жовте на
   світлофорі. Зелене учень прочитав би як «правильно», хоча це лише його
   вибір (Паша 14.10); зелений лишається для майбутнього показу правильних
   відповідей, червоний — для незаповненого. */
.sv-fp-form.is-quiz .sv-fp-option:has(input:checked) {
    border-color: var(--sv-accent);
    background: var(--sv-tint-amber);
}

/* І смужка «де ти зараз» на картці питання — теж золото, не зелене «правильно». */
.sv-fp-form.is-quiz .sv-fp-q:not(.is-invalid):focus-within {
    box-shadow: inset 3px 0 0 var(--sv-accent);
}

@media (hover: hover) {
    .sv-fp-form.is-quiz .sv-fp-option:hover {
        border-color: var(--sv-accent);
    }
}

.sv-fp-option.is-other {
    flex-wrap: wrap;
    cursor: default;
}

.sv-fp-other-pick {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s3);
    min-height: 36px;
    cursor: pointer;
}

.sv-fp-option.is-other .sv-fp-other-text {
    flex: 1 1 12rem;
    min-width: 0;
}

.sv-fp-other-row {
    margin-top: var(--sv-s2);
}

.sv-fp-input {
    clear: both;
    display: block;
    width: 100%;
    min-height: 44px;
    padding: var(--sv-s2) var(--sv-s3);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-sm);
    background: var(--sv-surface-alt);
    color: var(--sv-text);
    font: inherit;
    /* 16px: з меншим кеглем iOS Safari зумить сторінку при фокусі поля. */
    font-size: var(--sv-fs-md);
}

/* Поле, в якому пишеш, — тонка рамка кольором вибору форми (як лінія в
   Google Forms): у формі основний, у тесті золото. Загальне кільце фокуса
   було товстим і золотим навіть у звичайній формі (Паша 16.10). */
.sv-fp-input:focus,
.sv-fp-input:focus-visible {
    outline: none;
    border-color: var(--sv-fp-mark);
    box-shadow: 0 0 0 1px var(--sv-fp-mark);
}

/* Браузерний сірий плейсхолдер у темній темі мав 2.9:1. */
.sv-fp-input::placeholder {
    color: var(--sv-text-muted);
    opacity: 1;
}

/* Довга відповідь — один рядок, що росте з текстом, як у Google Forms: без
   вгадування висоти й без ручки розтягування (Паша 16.10). Де браузер не
   вміє field-sizing, висоту ставить form_public.js. */
textarea.sv-fp-input {
    field-sizing: content;
    overflow: hidden;
    resize: none;
    line-height: 1.5;
}

.sv-fp-number,
.sv-fp-date,
.sv-fp-dd {
    max-width: 280px;
}

/* Список у формі учня (Паша 21.10) — як dselect панелі: поле форми зі
   стрілкою праворуч донизу, список — .sv-dselect-list на всю ширину поля.
   Варіанти — радіо без кружечка, вигляд — як пункти списку панелі. */
/* clear — на самому <details>: легенда питання плаває, і з clear лише на
   полі <details> починався вище за нього (список угору відкривався з
   проміжком). */
.sv-fp-dd {
    position: relative;
    clear: both;
}

.sv-fp-dd > summary.sv-fp-input {
    display: flex;
    align-items: center;
    padding-right: var(--sv-s6);
    line-height: 1.5;
    background-image: var(--sv-select-caret);
    background-repeat: no-repeat;
    background-position: right 14px center;
    background-size: 11px 8px;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.sv-fp-dd[open] > summary.sv-fp-input {
    border-color: var(--sv-fp-mark);
    box-shadow: 0 0 0 1px var(--sv-fp-mark);
}

.sv-fp-dd .sv-dselect-label.is-placeholder {
    color: var(--sv-text-muted);
}

.sv-fp-dd .sv-dselect-list {
    width: 100%;
    min-width: 13rem;
}

/* Смуга прокрутки списку — волосина, як у панелі v2 (v2.css «Смуга
   прокрутки»): видно, коли курсор над списком. */
.sv-fp-dd .sv-dselect-list::-webkit-scrollbar {
    width: 10px;
}

.sv-fp-dd .sv-dselect-list::-webkit-scrollbar-track {
    background: transparent;
}

.sv-fp-dd .sv-dselect-list::-webkit-scrollbar-thumb {
    border: 4px solid transparent;
    border-radius: 999px;
    background-color: transparent;
    background-clip: padding-box;
}

.sv-fp-dd .sv-dselect-list:hover::-webkit-scrollbar-thumb {
    background-color: color-mix(in srgb, var(--sv-text) 34%, transparent);
}

@supports not selector(::-webkit-scrollbar) {
    .sv-fp-dd .sv-dselect-list {
        scrollbar-width: thin;
        scrollbar-color: color-mix(in srgb, var(--sv-text) 34%, transparent) transparent;
    }
}

.sv-fp-dd-option {
    position: relative;
    flex-shrink: 0;
    padding: var(--sv-s2) var(--sv-s3);
    border-radius: 6px;
    color: var(--sv-text);
    cursor: pointer;
    overflow-wrap: anywhere;
    transition: background var(--sv-dur-fast) var(--sv-ease);
    -webkit-tap-highlight-color: transparent;
}

.sv-fp-dd-option input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

@media (hover: hover) {
    .sv-fp-dd-option:hover {
        background: var(--sv-bg);
    }
}

.sv-fp-dd-option:has(input:checked) {
    background: var(--sv-tint-sage);
    color: var(--sv-on-tint);
    font-weight: 700;
}

.sv-fp-form.is-quiz .sv-fp-dd-option:has(input:checked) {
    background: var(--sv-tint-amber);
    color: var(--sv-text);
}

/* Значок календаря — приглушений, як стрілка списку, а не чорний. */
.sv-fp-date::-webkit-calendar-picker-indicator {
    opacity: 0.6;
    cursor: pointer;
}

.sv-fp-dd-option:has(input:focus-visible) {
    outline: 2px solid var(--sv-fp-mark);
    outline-offset: -2px;
}

.sv-fp-q.is-invalid .sv-fp-input {
    border-color: var(--sv-maroon);
}

/* Шкала: точки рівними клітинками; на телефоні довга шкала (> 6) — у два
   ряди, щоб клітинка лишалась завширшки з палець. */
.sv-fp-scale {
    clear: both;
    display: grid;
    grid-template-columns: repeat(var(--points, 5), minmax(0, 1fr));
    gap: var(--sv-s2);
}

.sv-fp-point input {
    position: absolute;
    opacity: 0;
}

.sv-fp-point > span {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 48px;
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius-sm);
    background: var(--sv-surface-alt);
    font-weight: 700;
    cursor: pointer;
}

.sv-fp-point small {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 600;
}

.sv-fp-scale.is-emoji .sv-fp-point > span > span[aria-hidden] {
    font-size: var(--sv-fs-xl);
    line-height: 1.2;
}

.sv-fp-scale.is-stars .sv-fp-point > span > span[aria-hidden] {
    color: var(--sv-border);
    font-size: var(--sv-fs-xl);
    line-height: 1.1;
}

/* Таблиця «рядки × варіанти» (Паша 20.10, як у Google Forms): шапка з
   підписами стовпців, у рядку — назва й кружечки по центру клітинок. На
   телефоні кожен рядок — блок, варіанти списком із підписами. */
.sv-fp-grid {
    --sv-radio: var(--sv-fp-mark);
    clear: both;
    display: grid;
    gap: var(--sv-s1);
}

.sv-fp-grid-head,
.sv-fp-grid-row {
    display: grid;
    grid-template-columns: minmax(7rem, 1.4fr) repeat(var(--cols, 3), minmax(0, 1fr));
    align-items: center;
    gap: var(--sv-s2);
}

.sv-fp-grid-head {
    padding: 0 var(--sv-s3);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    font-weight: 600;
    text-align: center;
    overflow-wrap: anywhere;
}

.sv-fp-grid-row {
    min-height: 48px;
    padding: var(--sv-s1) var(--sv-s3);
    border-radius: var(--sv-radius-sm);
    background: var(--sv-surface-alt);
}

.sv-fp-grid-label {
    overflow-wrap: anywhere;
}

.sv-fp-grid-cell {
    --sv-control-size: 20px;

    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sv-s3);
    min-height: 40px;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

/* Підпис кружечка — для читача екрана; на телефоні — видно (шапки там немає). */
.sv-fp-grid-clabel {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

@media (max-width: 560px) {
    .sv-fp-grid-head {
        display: none;
    }

    .sv-fp-grid-row {
        grid-template-columns: 1fr;
        gap: 0;
        padding: var(--sv-s2) var(--sv-s3);
    }

    .sv-fp-grid-label {
        margin-bottom: var(--sv-s1);
        font-weight: 600;
    }

    .sv-fp-grid-cell {
        justify-content: flex-start;
        min-height: 44px;
    }

    .sv-fp-grid-clabel {
        position: static;
        width: auto;
        height: auto;
        overflow: visible;
        clip: auto;
        white-space: normal;
    }
}

/* Обрана точка — суцільна заливка: рамка на світлому тлі мала б замалий
   контраст. Колір — вибору форми, як у варіантів (Паша 21.10): у формі
   основний, у тесті золото; текст — контрастний до нього (--sv-check-ink). */
.sv-fp-point input:checked + span {
    border-color: var(--sv-fp-mark);
    background: var(--sv-fp-mark);
    color: var(--sv-check-ink);
}

.sv-fp-point input:checked + span small {
    color: inherit;
}

/* Зірки: обрана й усі до неї — зафарбовані. */
.sv-fp-scale.is-stars .sv-fp-point:has(~ .sv-fp-point input:checked) > span > span[aria-hidden],
.sv-fp-scale.is-stars .sv-fp-point input:checked + span > span[aria-hidden] {
    color: var(--sv-accent);
}

.sv-fp-scale.is-stars .sv-fp-point input:checked + span {
    background: var(--sv-surface-alt);
    border-color: var(--sv-accent);
    color: var(--sv-text);
}

.sv-fp-point input:focus-visible + span {
    outline: 2px solid var(--sv-focus);
    outline-offset: 2px;
}

.sv-fp-scale-ends {
    display: flex;
    justify-content: space-between;
    gap: var(--sv-s3);
    margin-top: var(--sv-s2);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
}

.sv-fp-scale-ends span:last-child {
    text-align: right;
}

/* Місце під пояснення є завжди — поява тексту нічого не зсуває. */
/* Помилка під питанням (як у Google Forms): місце не тримаємо — картка
   росте, коли вона з'являється; відступ над нею — як від назви до
   варіантів, під нею — як від верху картки до назви (Паша 16.10). */
.sv-fp-error {
    clear: both;
    margin: 0;
    color: var(--sv-maroon);
    font-size: var(--sv-fs-base);
    line-height: 1.4;
}

.sv-fp-error:not(:empty) {
    margin-top: var(--sv-s3);
}

/* Що ще не заповнено — простим текстом, без підкладки й рамки: червона
   плашка з підкресленими посиланнями виглядала згенеровано й кричала
   (Паша 16.10). Червоним — лише заголовок; самі питання — звичайним текстом. */
.sv-fp-summary {
    color: var(--sv-text);
}

.sv-fp-summary b {
    color: var(--sv-maroon);
}

.sv-fp-summary ul {
    margin: var(--sv-s2) 0 0;
    padding-left: var(--sv-s5);
}

.sv-fp-summary a {
    color: inherit;
    text-decoration: none;
}

@media (hover: hover) {
    .sv-fp-summary a:hover {
        text-decoration: underline;
    }
}

.sv-fp-summary li + li {
    margin-top: var(--sv-s1);
}

/* Смуга прогресу — сегменти (етапи тесту чи сторінки форми) з ледь
   помітним проміжком: читається як одна лінія, але видно, де межа етапу
   (Паша 16.10). Без «N з M» на екрані — це лише для читача з екрана. */
.sv-fp-progress {
    display: flex;
    gap: 3px;
}

.sv-fp-stage {
    flex: 1 1 0;
    height: 6px;
    border-radius: var(--sv-radius-pill);
    background: var(--sv-border);
    overflow: hidden;
}

/* Заповнення — transform, а не width: анімується без перерахунку розкладки. */
.sv-fp-stage > span {
    display: block;
    width: 100%;
    height: 100%;
    background: var(--sv-sage);
    transform: scaleX(var(--fill, 0));
    transform-origin: left;
    transition: transform var(--sv-dur) var(--sv-ease);
}

.sv-fp-stage.is-done > span {
    transform: none;
}

.sv-fp-nav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s3);
}

.sv-fp-nav .sv-btn {
    min-height: 52px;
    padding: 0 var(--sv-s6);
    font-size: var(--sv-fs-md);
}

/* «Далі» й «Надіслати» — праворуч (Паша, 13.10.2026); на телефоні — на всю ширину. */
.sv-fp-nav .sv-fp-next,
.sv-fp-nav .sv-fp-submit {
    margin-left: auto;
}

.sv-fp-submit[aria-busy="true"] {
    opacity: 0.7;
    cursor: progress;
}

.sv-fp-message {
    display: grid;
    justify-items: center;
    gap: var(--sv-s3);
    padding: var(--sv-s6) var(--sv-s5);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    background: var(--sv-surface);
    text-align: center;
}

.sv-fp-message p {
    margin: 0;
    font-size: var(--sv-fs-md);
    line-height: 1.6;
    white-space: pre-line;
    overflow-wrap: anywhere;
}

.sv-fp-message-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: var(--sv-radius-pill);
    background: var(--sv-bg);
    color: var(--sv-text-muted);
}

.sv-fp-message-icon svg {
    width: 28px;
    height: 28px;
}

.sv-fp-message.is-done .sv-fp-message-icon {
    background: var(--sv-tint-sage);
    color: var(--sv-success);
}

.sv-fp-message .sv-fp-message-title {
    font-size: var(--sv-fs-xl);
    font-weight: 800;
    line-height: 1.3;
    text-wrap: pretty;
}

.sv-fp-message .sv-fp-message-hint {
    margin-top: var(--sv-s2);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-base);
}

/* «Завершити тест?» (form_public.html): вікно поверх останнього кроку
   тесту; кнопки — як у навігації кроку, під палець. */
.sv-fp-confirm {
    width: min(460px, calc(100vw - 2 * var(--sv-s4)));
    padding: var(--sv-s5);
    border: 0;
    border-radius: var(--sv-radius-lg);
    background: var(--sv-surface);
    color: var(--sv-text);
    box-shadow: var(--sv-shadow-lg);
    transform: scale(0.96);
}

/* Поява й зникнення — як вікна панелі (v2.css .v2-confirm, Паша 21.10: «має
   плавно з'являтись»): 260 мс швидкий старт і м'яке гальмування, ледь
   зменшене -> на місці; зникає за 180 мс; затемнення проявляється. Без
   @starting-style (старі браузери) — просто без руху. */
.sv-fp-confirm,
.sv-fp-confirm::backdrop {
    opacity: 0;
    transition: opacity var(--sv-dur) var(--sv-ease), transform var(--sv-dur) var(--sv-ease),
        overlay var(--sv-dur) allow-discrete, display var(--sv-dur) allow-discrete;
}

.sv-fp-confirm::backdrop {
    background: var(--sv-overlay);
}

.sv-fp-confirm[open],
.sv-fp-confirm[open]::backdrop {
    opacity: 1;
    transition-duration: var(--sv-dur-enter);
}

.sv-fp-confirm[open] {
    transform: none;
}

@starting-style {
    .sv-fp-confirm[open],
    .sv-fp-confirm[open]::backdrop {
        opacity: 0;
    }

    .sv-fp-confirm[open] {
        transform: scale(0.96);
    }
}

/* «Менше руху» — лише проявлення. */
@media (prefers-reduced-motion: reduce) {
    .sv-fp-confirm,
    .sv-fp-confirm[open] {
        transform: none;
    }
}

.sv-fp-confirm-q {
    margin: 0 0 var(--sv-s2);
    font-size: var(--sv-fs-xl);
    font-weight: 800;
    line-height: 1.3;
}

.sv-fp-confirm-text,
.sv-fp-confirm-skipped {
    margin: 0;
    font-size: var(--sv-fs-md);
    line-height: 1.5;
}

.sv-fp-confirm-text {
    color: var(--sv-text-muted);
}

.sv-fp-confirm-skipped {
    margin-top: var(--sv-s3);
}

.sv-fp-confirm-actions {
    justify-content: flex-end;
    margin-top: var(--sv-s5);
}

/* «Перевіряємо…» після тесту (form_public.html, form_public.js): кільце
   панелі (.sv-ring) з числом, що росте, і підпис кроку під ним; не
   дочекались AI — «Відповіді збережено» на тому самому місці. */
.sv-fp-checking-now,
.sv-fp-checking-late {
    display: grid;
    justify-items: center;
    gap: var(--sv-s3);
}

.sv-fp-checking-now[hidden],
.sv-fp-checking-late[hidden] {
    display: none;
}

/* Цифри однакової ширини — число не тремтить, поки росте. */
.sv-fp-checking .sv-ring-value {
    font-variant-numeric: tabular-nums;
}

/* Новий підпис кроку проявляється на місці старого («Менше руху» — без цього). */
.sv-fp-checking-label.is-swap {
    animation: sv-fade calc(var(--sv-dur) * 1.5) var(--sv-ease);
}

@media (prefers-reduced-motion: reduce) {
    .sv-fp-checking-label.is-swap {
        animation: none;
    }
}

/* Кнопки в картці стану — під палець. */
.sv-fp-message .sv-btn {
    min-height: 44px;
}

.sv-fp-message .sv-fp-note {
    color: var(--sv-warning);
    font-size: var(--sv-fs-sm);
}

/* --- Форма «лише учні» (фаза 2): кроки, медіа, результат, захист --- */

/* Хто відповідає — під назвою: учень одразу бачить, що посилання його. */
.sv-fp-student {
    margin: var(--sv-s2) 0 0;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-base);
    font-weight: 600;
}

.sv-fp-image {
    display: block;
    max-width: 100%;
    height: auto;
    margin: var(--sv-s3) 0;
    border-radius: var(--sv-radius);
    -webkit-touch-callout: none;
}

/* Плеєр без повзунка: шкала лише показує, де запис, перемотати нею не можна. */
.sv-fp-audio {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: var(--sv-s2) var(--sv-s3);
    margin: var(--sv-s3) 0;
    padding: var(--sv-s3) var(--sv-s4);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    background: var(--sv-bg);
}

.sv-fp-audio-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s2);
    min-height: 44px;
    padding: 0 var(--sv-s4);
    border: 0;
    border-radius: var(--sv-radius-pill);
    background: var(--sv-primary);
    color: #FFFFFF;
    font: inherit;
    font-weight: 700;
    cursor: pointer;
}

.sv-fp-audio-btn:disabled {
    background: var(--sv-border);
    color: var(--sv-text-muted);
    cursor: default;
}

.sv-fp-audio-btn:focus-visible {
    outline: 3px solid var(--sv-accent);
    outline-offset: 2px;
}

.sv-fp-audio-i {
    display: inline-flex;
}

.sv-fp-audio-i.is-pause,
.sv-fp-audio.is-playing .sv-fp-audio-i.is-play {
    display: none;
}

.sv-fp-audio.is-playing .sv-fp-audio-i.is-pause {
    display: inline-flex;
}

.sv-fp-audio-bar {
    height: 6px;
    overflow: hidden;
    border-radius: var(--sv-radius-pill);
    background: var(--sv-border);
}

.sv-fp-audio-bar > span {
    display: block;
    height: 100%;
    background: var(--sv-accent);
    transform: scaleX(0);
    transform-origin: left;
}

.sv-fp-audio-time {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    font-variant-numeric: tabular-nums;
}

.sv-fp-audio-rule,
.sv-fp-audio-left {
    grid-column: 1 / -1;
    margin: 0;
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
}

.sv-fp-audio-left:empty {
    display: none;
}

/* Результат квізу — бали й рівень великими цифрами. */
.sv-fp-score {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sv-s4);
    margin: var(--sv-s2) 0 0;
}

.sv-fp-score div {
    min-width: 120px;
    padding: var(--sv-s3) var(--sv-s4);
    border-radius: var(--sv-radius);
    background: var(--sv-bg);
}

.sv-fp-score dt {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
    font-weight: 700;
}

.sv-fp-score dd {
    margin: 0;
    font-size: 2rem;
    font-weight: 800;
    line-height: 1.2;
}

.sv-fp-review {
    margin-top: var(--sv-s4);
    padding: var(--sv-s5);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    background: var(--sv-surface);
}

.sv-fp-review-title {
    margin: 0 0 var(--sv-s3);
    font-size: var(--sv-fs-md);
}

.sv-fp-review-list {
    display: grid;
    gap: var(--sv-s3);
    margin: 0;
    padding-left: var(--sv-s5);
}

.sv-fp-review-row p {
    margin: 0;
    overflow-wrap: anywhere;
    white-space: pre-line;
}

.sv-fp-review-q {
    font-weight: 600;
}

.sv-fp-review-mark {
    display: inline-block;
    min-width: 1.2em;
    font-weight: 800;
}

.sv-fp-review-row.is-right .sv-fp-review-mark {
    color: var(--sv-success);
}

.sv-fp-review-row.is-wrong .sv-fp-review-mark {
    color: var(--sv-maroon);
}

.sv-fp-review-key {
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-sm);
}

/* Захист тесту: текст питань не виділяється й не копіюється (поля відповіді — так). */
.is-nocopy .sv-fp-legend,
.is-nocopy .sv-fp-qdesc,
.is-nocopy .sv-fp-option > span,
.is-nocopy .sv-fp-block,
.is-nocopy .sv-fp-section-head,
.is-nocopy .sv-fp-image {
    -webkit-user-select: none;
    user-select: none;
}

/* AI-перевірка (core/forms/ai_check): перемикач і еталон у картці питання,
   банер і вердикти у «Відповідях». Еталон — лише з увімкненим перемикачем. */
.sv-qb-key:not(:has(input[name="ai"]:checked)) .sv-qb-ai-guide {
    display: none;
}

.sv-fr-ai {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
    margin-bottom: var(--sv-s3);
    padding: var(--sv-s3) var(--sv-s4);
    border: 1px solid var(--sv-border);
    border-radius: var(--sv-radius);
    background: var(--sv-surface);
    font-size: var(--sv-fs-sm);
}

.sv-fr-ai.is-failed {
    border-color: var(--sv-warning);
}

/* У тому ж рядку, що відповідь (чи під правильною відповіддю, що блоком):
   прихований перемикач не лишає порожнього рядка. */
.sv-fr-verdict {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s2);
    margin-left: var(--sv-s2);
    font-size: var(--sv-fs-sm);
    white-space: normal;
    vertical-align: middle;
}

.sv-fr-key + .sv-fr-verdict {
    margin-left: 0;
}

.sv-fr-reason {
    color: var(--sv-text-muted);
}

.sv-fr-verdict-form {
    display: inline;
}

/* «Зарахувати / Не зараховувати» — під кожною з десятків відповідей був би
   шум: на ноутбуці — коли наводиш на відповідь чи переходиш до неї з
   клавіатури; на телефоні (без наведення) — завжди. */
@media (hover: hover) {
    .sv-fr-answers dd:not(:hover):not(:focus-within) .sv-fr-verdict-form {
        visibility: hidden;
    }
}

/* «+ Ім'я учня» під описом у конструкторі (_form_name_token.html). */
.sv-qb-token {
    display: flex;
    align-items: center;
    gap: var(--sv-s1);
    margin-top: var(--sv-s1);
    font-size: var(--sv-fs-sm);
}

/* Водяний знак з іменем учня — поверх сторінки, кліки проходять крізь нього.
   SVG (core/forms/public.watermark_url) — маска, колір — текст теми: знак
   видно і на світлій, і на темній темі. */
.sv-fp-watermark {
    position: fixed;
    inset: 0;
    z-index: 3;
    background-color: var(--sv-text);
    opacity: 0.06;
    -webkit-mask-image: var(--sv-wm);
    mask-image: var(--sv-wm);
    -webkit-mask-repeat: repeat;
    mask-repeat: repeat;
    pointer-events: none;
}

/* Таймер тесту (settings.timer) — простим текстом праворуч, тим самим
   капсом, що й «Розділ» (вище); годинник — між часом і «Сховати», а
   «Сховати» — підкресленим текстом у тому ж стилі (Паша 21.10). Цифри
   однакової ширини — рядок не «тремтить». Останні 5 хвилин — бурштиновий. */
.sv-fp-timer {
    display: flex;
    align-items: center;
    gap: var(--sv-s3);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.sv-fp-timer-body {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s1);
}

/* Годинник — посередині між часом і «Сховати»: s1 (проміжок у body) + s2
   ліворуч = s3 праворуч (проміжок таймера). */
.sv-fp-timer-body svg {
    width: 1.15em;
    height: 1.15em;
    margin-left: var(--sv-s2);
}

.sv-fp-timer.is-low .sv-fp-timer-value {
    color: var(--sv-warning);
}

.sv-fp-timer-toggle {
    padding: var(--sv-s1) 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    text-decoration: underline;
    text-underline-offset: 0.25em;
    cursor: pointer;
    transition: color var(--sv-dur) var(--sv-ease);
}

.sv-fp-timer-toggle:hover {
    color: var(--sv-text);
}

.sv-fp-timer.is-hidden .sv-fp-timer-body {
    display: none;
}

.sv-fp-toast {
    position: fixed;
    left: 50%;
    bottom: var(--sv-s5);
    z-index: 4;
    max-width: min(92vw, 420px);
    margin: 0;
    padding: var(--sv-s3) var(--sv-s4);
    border-radius: var(--sv-radius);
    background: var(--sv-primary);
    color: #FFFFFF;
    font-size: var(--sv-fs-base);
    transform: translateX(-50%);
}

@media (max-width: 480px) {
    .sv-fp {
        padding: var(--sv-s4) var(--sv-s3) var(--sv-s6, 48px);
    }

    .sv-fp-hero {
        padding: var(--sv-s5) var(--sv-s4);
    }

    .sv-fp-banner {
        height: 150px;
    }

    .sv-fp-q {
        padding: var(--sv-s5) var(--sv-s4);
    }

    .sv-fp-block {
        padding: var(--sv-s4);
    }

    /* «Надіслати» — на всю ширину під пальцем; «Назад / Далі» — порівну. */
    .sv-fp-nav .sv-btn {
        flex: 1 1 0;
        padding: 0 var(--sv-s4);
    }

    .sv-fp-nav .sv-fp-submit {
        flex-basis: 100%;
    }

    .sv-fp-scale.is-wide {
        grid-template-columns: repeat(6, minmax(0, 1fr));
    }

    .sv-fp-number,
    .sv-fp-date,
    .sv-fp-dd {
        max-width: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .sv-fp-stage > span,
    .sv-fp-q,
    .sv-fp-option {
        transition: none;
    }
}

/* ============ Розгортка (Solar design.md «Розгортка») ============
   Розгортка на місці — шеврон ліворуч від назви: згорнуто — вправо,
   розгорнуто — вниз. Один знак на весь продукт: і текстові розгортки, і
   кнопки-розгортки («Показати всі посилання»), і рядки-заголовки. Меню й
   списки, що випадають (_dselect, «⋯», експорт, період), — свій шеврон
   праворуч донизу: вони не розгортають вміст на місці. */
:is(.sv-qb-more, .sv-fr-more, .sv-buddy-alt, .sv-gloss, .sv-access-history, .sv-audit-history, .sv-buddy-history, .sv-buddy-roster-manual, .sv-fs-links, .sv-bc-missed, .sv-bc-pick .sv-mselect, .sv-bc-log, .sv-qb-after .sv-dselect) > summary {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s1);
    list-style: none;
}

:is(.sv-qb-more, .sv-fr-more, .sv-buddy-alt, .sv-gloss, .sv-access-history, .sv-audit-history, .sv-buddy-history, .sv-buddy-roster-manual, .sv-fs-links, .sv-bc-missed, .sv-bc-pick .sv-mselect, .sv-bc-log, .sv-qb-after .sv-dselect) > summary::-webkit-details-marker {
    display: none;
}

:is(.sv-qb-more, .sv-fr-more, .sv-buddy-alt, .sv-gloss, .sv-access-history, .sv-audit-history, .sv-buddy-history, .sv-buddy-roster-manual, .sv-fs-links, .sv-bc-missed, .sv-bc-pick .sv-mselect, .sv-bc-log, .sv-qb-after .sv-dselect) > summary::before {
    content: "";
    flex-shrink: 0;
    width: 1.1em;
    height: 1.1em;
    background: currentColor;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 6l6 6-6 6' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 6l6 6-6 6' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
    transition: transform var(--sv-dur) var(--sv-ease);
}

:is(.sv-qb-more, .sv-fr-more, .sv-buddy-alt, .sv-gloss, .sv-access-history, .sv-audit-history, .sv-buddy-history, .sv-buddy-roster-manual, .sv-fs-links, .sv-bc-missed, .sv-bc-pick .sv-mselect, .sv-bc-log, .sv-qb-after .sv-dselect)[open] > summary::before {
    transform: rotate(90deg);
}

/* Варіант D (design.md): секція, що сама є функцією, — перемикач, поля
   з'являються, лише коли він увімкнений; вимкнений — поля сховані й не
   надсилаються (forms.js). */
.sv-qb-reveal {
    display: grid;
    gap: var(--sv-s2);
}

.sv-qb-reveal:not(:has(> .sv-fs-row input:checked)) > .sv-qb-reveal-body {
    display: none;
}

/* Піделемент перемикача — з відступом і лінією ліворуч, ті самі значення, що
   .sv-fs-dep у налаштуваннях (Паша 18.10, design.md «Піделемент перемикача»):
   лінія з-під центру перемикача (18px), тож видно, чий це піделемент. */
.sv-qb-reveal-body,
.sv-qb-ai-guide {
    min-width: 0;
    margin: 0 0 0 var(--sv-sub-indent);
    padding: 0 0 0 var(--sv-s4);
    border: 0;
    border-left: 2px solid var(--sv-border);
}

/* --- Діапазон «від – до» (оператор «між», _range_input.html) ---
   Дві межі в один рядок; у рядку фільтра займає місце звичайного значення. */
.sv-range {
    display: flex;
    align-items: center;
    gap: var(--sv-s15);
    min-width: 0;
}

.sv-range[hidden] {
    display: none;
}

.sv-range > input[data-range-part] {
    flex: 1 1 0;
    min-width: 0;
}

.sv-range-dash {
    color: var(--sv-text-muted);
}

.sv-fcond > .sv-range {
    flex: 1 1 200px;
}

/* --- Опис із форматуванням (templates/_rich_text.html, Паша 20.10) ---------
   Поле редагується на місці; панель під ним висувається, поки поле в фокусі
   (як у Google Forms). Списки й посилання — однаково в конструкторі й учня. */
.sv-rt {
    display: grid;
}

.sv-rt-area {
    line-height: 1.45;
    overflow-wrap: anywhere;
    cursor: text;
}

.sv-rt-area:empty::before {
    content: attr(data-placeholder);
    color: var(--sv-text-muted);
    pointer-events: none;
}

.sv-rt-bar {
    display: none;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sv-s1);
    padding-top: var(--sv-s1);
}

.sv-rt:focus-within .sv-rt-bar {
    display: flex;
    animation: sv-rt-in var(--sv-dur) var(--sv-ease);
}

@keyframes sv-rt-in {
    from {
        opacity: 0;
        transform: translateY(calc(-1 * var(--sv-s1)));
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.sv-rt-btn {
    display: inline-grid;
    place-items: center;
    width: var(--sv-s6);
    height: var(--sv-s6);
    padding: 0;
    border: none;
    border-radius: var(--sv-radius-sm);
    background: none;
    color: var(--sv-text-muted);
    cursor: pointer;
    transition: background var(--sv-dur) var(--sv-ease), color var(--sv-dur) var(--sv-ease);
}

.sv-rt-btn:hover {
    background: var(--sv-bg);
    color: var(--sv-text);
}

.sv-rt-btn.is-active {
    background: var(--sv-tint-sage);
    color: var(--sv-text);
}

.sv-rt-link {
    display: inline-flex;
    align-items: center;
    gap: var(--sv-s1);
    margin-left: var(--sv-s2);
}

.sv-rt-link[hidden] {
    display: none;
}

.sv-rt-link .sv-input {
    width: 16rem;
    min-height: var(--sv-s6);
    padding-block: var(--sv-s1);
}

:is(.sv-rt-area, .sv-rt-content) :is(ul, ol) {
    margin: var(--sv-s1) 0;
    padding-left: var(--sv-s5);
    white-space: normal;
}

:is(.sv-rt-area, .sv-rt-content) a {
    color: var(--sv-link);
    text-decoration: underline;
}

.sv-rt-content :is(p, div) {
    margin: 0;
}

@media (prefers-reduced-motion: reduce) {
    .sv-rt:focus-within .sv-rt-bar {
        animation: none;
    }
}

/* --- Збільшене зображення (forms.js / form_public.js, Паша 20.10) --------------
   Фото в конструкторі й у формі учня: натиснув — на весь екран поверх
   затемнення; клік чи Esc — назад. Водяний знак захищеного тесту лишається
   поверх і збільшеного фото: скріншот без імені учня не зробиш. */
img[data-zoom] {
    cursor: zoom-in;
}

.sv-zoom {
    position: fixed;
    inset: 0;
    z-index: var(--sv-z-modal);
    display: grid;
    place-items: center;
    padding: var(--sv-s4);
    background: var(--sv-overlay);
    backdrop-filter: blur(2px);
    cursor: zoom-out;
    animation: sv-fade var(--sv-dur) var(--sv-ease);
}

.sv-zoom img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    border-radius: var(--sv-radius);
    box-shadow: var(--sv-shadow-md);
}

.sv-zoom-close {
    position: absolute;
    top: var(--sv-s3);
    right: var(--sv-s3);
    display: grid;
    place-items: center;
    width: calc(var(--sv-s6) + var(--sv-s3));
    height: calc(var(--sv-s6) + var(--sv-s3));
    border: 1px solid var(--sv-border);
    border-radius: 50%;
    background: var(--sv-surface);
    color: var(--sv-text);
    font-size: var(--sv-fs-xl);
    line-height: 1;
    cursor: pointer;
}

html.is-zoomed,
html.is-zoomed body {
    overflow: hidden;
}

html.is-zoomed .sv-fp-watermark {
    z-index: calc(var(--sv-z-modal) + 1);
    opacity: 0.12;
    mix-blend-mode: difference;
}

@media (prefers-reduced-motion: reduce) {
    .sv-zoom {
        animation: none;
    }
}

/* ============ Можливий дубль (core/duplicates) ============
   Рядок на картці дошки — як «у роботі», але колір несе лише значок:
   це підказка, а не стан, який треба розрулити просто з дошки. */
.sv-card-dup {
    display: flex;
    align-items: center;
    gap: var(--sv-s1);
    margin-top: var(--sv-s2);
    color: var(--sv-text-muted);
    font-size: var(--sv-fs-xs);
    font-weight: 600;
}

.sv-card-dup svg {
    flex-shrink: 0;
    width: 13px;
    height: 13px;
    color: var(--sv-accent-text);
}

/* Двійник уже зарахований — тривога, а не увага (Паша 21.10): бордовим
   і значок, і текст. «Можливий дубль» з іншою заявкою лишається золотим. */
.sv-card-dup.is-alarm,
.sv-card-dup.is-alarm svg {
    color: var(--sv-maroon);
}

/* Тип документа на картці дошки — рядок, як і решта (Паша 20.10). Зелений
   і значок, і текст: документ є, перевірено ШІ. */
.sv-card-doc {
    display: flex;
    align-items: center;
    gap: var(--sv-s1);
    margin-top: var(--sv-s2);
    color: var(--sv-success);
    font-size: var(--sv-fs-xs);
    font-weight: 600;
}

.sv-card-doc svg {
    flex-shrink: 0;
}

/* Попередження над рішеннями в картці профілю. Анатомія як у v2-callout
   (Solar design.md «Попередження»): заголовок + деталь, значок у своїй
   колонці на рівні першого рядка, рівень — смугою зліва на нейтральному
   тлі. У v2 тло й смугу дає .v2-callout, тут — те саме для v1. */
.sv-dup-callout {
    display: grid;
    grid-template-columns: 1rem minmax(0, 1fr);
    column-gap: var(--sv-s3);
    row-gap: var(--sv-s05);
    flex-shrink: 0;
    padding: var(--sv-s3) var(--sv-s4);
    border-radius: var(--sv-radius-sm);
    background: color-mix(in srgb, var(--sv-text) 4%, transparent);
    box-shadow: inset 3px 0 0 var(--sv-accent-text);
    font-size: var(--sv-fs-base);
    line-height: 1.4;
}

/* Третя колонка — дія «Відкрити картку» праворуч у рядку попередження
   (Solar design.md «Одним рядком»), а не окремим рядком під текстом. */
.sv-dup-callout.v2-callout {
    grid-template-columns: 1rem minmax(0, 1fr) auto;
    margin: var(--sv-s3) var(--sv-s5);
}

.sv-dup-callout > svg {
    grid-row: 1;
    width: var(--sv-info-size);
    height: var(--sv-info-size);
    margin-top: calc((1lh - var(--sv-info-size)) / 2);
    color: var(--sv-accent-text);
}

.sv-dup-callout > p {
    grid-column: 2;
    margin: 0;
}

.sv-dup-callout .v2-callout-title {
    color: var(--sv-text);
    font-weight: 600;
}

.sv-dup-callout .v2-callout-detail {
    color: var(--sv-text-muted);
}

.sv-dup-open {
    grid-column: 3;
    grid-row: 1 / span 2;
    align-self: center;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 var(--sv-s1);
    white-space: nowrap;
    color: var(--sv-text);
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* Телефон: праворуч дії не вистачає місця — заголовок ламався на два
   рядки. Дія йде під текст, ціль лишається 44 px. */
@media (max-width: 599px) {
    .sv-dup-callout.v2-callout {
        grid-template-columns: 1rem minmax(0, 1fr);
    }

    .sv-dup-open {
        grid-column: 2;
        grid-row: auto;
        justify-self: start;
        margin-top: var(--sv-s2);
        padding: 0;
    }
}

.sv-dup-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* Тривога: смуга й значок бордові (у v2 тло дає .v2-callout.is-alarm). */
.sv-dup-callout.is-alarm {
    background: color-mix(in srgb, var(--sv-maroon) 6%, transparent);
    box-shadow: inset 3px 0 0 var(--sv-maroon);
}

.sv-dup-callout.is-alarm > svg {
    color: var(--sv-maroon);
}

/* «Зарахувати?», коли двійник уже учень: без рамки, ледь червонувате тло —
   як плашка «Дата народження не збігається», лише іншого рівня (Паша 21.10). */
.sv-confirm.is-alarm {
    border: 0;
    border-radius: var(--sv-radius-sm);
    background: color-mix(in srgb, var(--sv-maroon) 7%, var(--sv-surface));
}

/* «Це інша людина» у кроці «Зарахувати?». Вирівнювання — спільне правило
   «Контрол поруч із підписом» у кінці файлу. */
.sv-dup-confirm {
    display: flex;
    gap: var(--sv-s2);
    margin-bottom: var(--sv-s3);
    cursor: pointer;
}

/* ============ Контрол поруч із підписом — одне правило (Паша 21.10) ============
   Чекбокс чи радіо — на центрі ПЕРШОГО рядка підпису за будь-якого шрифту й
   розміру тексту. Рахується від справжньої висоти рядка (1lh; шрифт поле бере
   від підпису — font: inherit у глобальних input[type=...] вгорі файлу), а не
   сталим зсувом у px: той пасував одному шрифту, на інших контрол з'їжджав.
   Новий рядок «контрол + підпис» — додати його клас сюди, нічого свого.
   Контроли без підпису (клітинки таблиць: .sv-cell-check, .sv-access-cell,
   .sv-admin-pick, .sv-fp-grid-cell) лишаються просто по центру. */
:is(.sv-fs-check, .sv-fs-radio, .sv-field-check, .sv-field-check-row, .sv-colpicker-item,
    .sv-mselect-option, .sv-fp-option, .sv-bs-del, .sv-dup-confirm) {
    align-items: flex-start;
}

:is(.sv-fs-check, .sv-fs-radio, .sv-field-check, .sv-field-check-row, .sv-colpicker-item,
    .sv-mselect-option, .sv-fp-option, .sv-bs-del, .sv-dup-confirm)
    > input:is([type="checkbox"]:not([role="switch"]), [type="radio"]) {
    margin-top: calc((1lh - var(--sv-ctl)) / 2);
}
