@import url("../eddy/tokens/colors.css");
@import url("../eddy/tokens/typography.css");
@import url("../eddy/tokens/spacing.css");
@import url("../eddy/tokens/radius-shadow.css");

/* Мост между старыми токенами и дизайн-системой ЭДДИ.

   Семнадцать переменных `--crm-*` живут в этом проекте с самого начала и
   стоят в восьми тысячах строк CSS. Переписать все их вхождения на
   `var(--red-500)` — работа на несколько заходов; переопределить сами
   переменные — одна правка, после которой платформа перекрашивается
   целиком. Поэтому здесь не значения, а ссылки на брендовые токены.

   Что важно знать про соответствия:
   * синий `#417690` был не фирменным цветом, а наследством темы
     jazzmin (об этом прямо сказано в дизайн-системе);
   * главное действие красное (`--red-500`), ссылки и активные состояния
     — тёмный «печатный» красный (`--red-600`): рядом с красной кнопкой
     ссылка того же тона сливалась бы с ней;
   * `--crm-teal` был цветом кнопки «Сохранить» — становится красным,
     потому что это и есть главное действие. Нефрит остаётся за
     подтверждением и за деньгами, которые пришли;
   * высота контролов 34 → 40 px по системе (на телефоне 44): 34 были
     плотнее, чем нужно человеку, который тычет в кнопку пальцем.
   Литералы старых значений оставлены в `--legacy-crm-*` внутри
   `tokens/colors.css` — по ним видно, что именно заменено. */
:root {
    --crm-accent: var(--red-600);
    --crm-accent-hover: var(--red-700);
    --crm-teal: var(--red-500);
    --crm-teal-hover: var(--red-600);
    --crm-danger: var(--status-danger);
    --crm-danger-hover: var(--red-700);
    --crm-danger-bg: var(--red-50);
    --crm-warning-text: var(--yellow-600);
    --crm-warning-bg: var(--yellow-50);
    --crm-warning-border: var(--yellow-100);
    --crm-ink: var(--ink-900);
    --crm-muted: var(--ink-500);
    --crm-label: var(--ink-700);
    --crm-line: var(--ink-100);
    --crm-line-strong: var(--ink-200);
    --crm-surface: var(--paper-50);
    --crm-control-h: 40px;
}

@media (max-width: 599px) {
    /* Палец крупнее курсора — на телефоне орган управления выше. */
    :root { --crm-control-h: 44px; }
}

/* Прежний блок токенов жил здесь и повторялся в admin_extra: два списка
   литералов, которые надо было держать одинаковыми руками. Теперь значения
   приходят из дизайн-системы (мост выше), а этот файл их только использует. */

.crm-title {
    font-size: 26px;
    font-weight: 700;
    color: var(--crm-ink);
    margin: 4px 0 20px;
    line-height: 1.2;
}

/* Wraps .crm-title on the staff student/payer card (students/_card_base.html)
   so an avatar can sit to its left — {% block card_avatar %} is empty on
   payer_card.html, so this row degenerates to just the title there. */
.crm-title-row {
    display: flex;
    align-items: center;
    /* Переносится: на телефоне номер встаёт под именем, а не режется
       вместе с ним. Ради этого он сюда и переехал — отдельной строкой
       ниже плашек его не замечали. */
    flex-wrap: wrap;
    gap: 8px 14px;
    margin: 4px 0 20px;
}

/* Контакты у имени: номер крупнее подписи, значки мессенджеров рядом. */
.crm-title-contacts {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    font-size: var(--fs-sm, 14px);
}

.crm-title-contacts a {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

@media (max-width: 599px) {
    /* Номер с новой строки целиком, а не хвостом за именем. */
    .crm-title-contacts {
        flex-basis: 100%;
    }
}

.crm-title-row .crm-title {
    margin: 0;
}

/* Same avatar markup (students/_avatar.html) on the staff card and the
   portal (student_home.html/student_detail.html) — an upload from the
   student's own portal is immediately visible to staff, and vice versa. */
.crm-avatar {
    flex: 0 0 auto;
    border-radius: 50%;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--crm-line);
    color: var(--crm-muted);
    font-weight: 700;
}

.crm-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.crm-avatar-md {
    width: 48px;
    height: 48px;
    font-size: 20px;
    --emoji-size: 30px;
}

.crm-avatar-lg {
    width: 72px;
    height: 72px;
    font-size: 30px;
    --emoji-size: 46px;
}

.crm-avatar-sm {
    width: 32px;
    height: 32px;
    font-size: 14px;
    --emoji-size: 20px;
}

/* Эмодзи-аватар живёт по своим размерам, а не по размеру буквы рядом.
   Буква-заглушка и эмодзи занимают в круге разную долю: при одном
   кегле буква смотрится нормально, а эмодзи — точкой в середине пустого
   круга. Отсюда отдельная переменная у каждого размера. */
.crm-avatar-emoji {
    line-height: 1;
    font-size: var(--emoji-size, 30px);
    font-family: var(--emoji-font);
}

/* Цветные эмодзи одним стеком на весь проект. На технике Apple это
   ровно те же значки, что в айфоне; на Windows и Android — свои
   цветные, но НЕ такие же: системный набор нельзя ни подменить, ни
   разослать вместе со страницей (шрифт Apple лицензионно закрыт, а
   Noto Color Emoji весит десяток мегабайт — дороже, чем польза от
   единообразия). Без этой строки часть браузеров рисует эмодзи
   чёрно-белым служебным шрифтом. */
:root {
    --emoji-font: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji",
                  "Twemoji Mozilla", "Android Emoji", sans-serif;
}

.portal-profile-header {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: 12px;
    /* Перенос обязателен: с карточкой ближайшего занятия в этой строке
       три элемента, и на телефоне она уезжала за правый край экрана —
       видно было только слово «СЛЕДУЮЩЕЕ ЗАН». */
    flex-wrap: wrap;
}

.portal-profile-header h1 {
    margin: 0 0 4px;
}

/* Ближайшее занятие — справа в шапке, у самого имени. Прижато к краю
   (margin-left: auto), потому что это второй смысловой центр экрана, а
   не продолжение подписи под именем. */
.portal-next-lesson {
    margin-left: auto;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 10px 14px;
    border: 1px solid var(--crm-line-strong);
    border-left: 3px solid var(--crm-accent);
    border-radius: 10px;
    background: #fff;
    min-width: 190px;
}

.portal-next-lesson .next-label {
    font-size: 11px;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--crm-muted);
}

.portal-next-lesson .next-when {
    font-size: 17px;
    font-weight: 700;
    color: var(--crm-ink);
}

.portal-next-lesson .next-empty {
    color: var(--crm-muted);
    font-weight: 600;
}

.portal-next-lesson .next-meta {
    font-size: 12px;
    color: var(--crm-muted);
}

@media (max-width: 640px) {
    /* На телефоне шапка складывается в столбец, и прижатая вправо
       карточка оказывалась бы шириной в одно слово. */
    .portal-next-lesson {
        margin-left: 0;
        flex: 1 1 100%;
    }
}

/* Ячейки набираются по ширине, а не жёсткими шестью колонками: в узком
   окне шесть колонок сжимали клетку до размера самого эмодзи, и выбирать
   приходилось из значков размером со шрифт подписи. */
.crm-emoji-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
    gap: 8px;
    margin-top: 6px;
}
/* ── Модальное окно ───────────────────────────────────────────────────
   Само окно рисует `eddy-modal` дизайн-системы; здесь только то, чего
   у кита нет, потому что у него окно — это два вложенных <div> со своей
   подложкой, а у нас настоящий <dialog>, открытый через `showModal()`:
   ловушка фокуса, Esc и подложка достаются от браузера даром, и менять
   это на пару div-ов значило бы написать всё это руками.

   ВАЖНО: `.eddy-modal` у кита объявляет `display: flex` безусловно, а
   правило браузера `dialog:not([open]) { display: none }` — обычной
   силы, и авторское его перебивает при любом весе. Без строки ниже
   КАЖДОЕ закрытое окно страницы становится видимым; на этом проект уже
   обжигался, когда те же правила писались руками. */
dialog.eddy-modal:not([open]) { display: none; }

dialog.eddy-modal {
    border: none;
    padding: 0;
    color: inherit;
}

dialog.eddy-modal::backdrop { background: rgba(22, 24, 26, .48); }

/* Форма внутри окна должна тянуться на всю его высоту, иначе прокрутка
   достаётся не телу окна, а подвалу с кнопками. */
dialog.eddy-modal form {
    display: flex;
    flex-direction: column;
    min-height: 0;
    flex: 1 1 auto;
}

.eddy-modal__close {
    background: none;
    border: none;
    font-size: 20px;
    line-height: 1;
    color: var(--text-subtle, #8e949a);
    cursor: pointer;
    padding: 2px 6px;
    border-radius: var(--r-sm, 6px);
}

.eddy-modal__close:hover {
    background: var(--paper-200, #F5F4F0);
    color: var(--text-body, #16181A);
}

/* Поля в окне — обычные, отрисованные Django: у них нет классов кита,
   поэтому вид задаётся по элементу. */
.eddy-modal__body label {
    display: block;
    font-size: var(--fs-xs, 13px);
    font-weight: var(--fw-semibold, 600);
    color: var(--ink-700, #37393D);
    margin: var(--sp-5, 10px) 0 var(--sp-3, 4px);
}

/* В админке нет глобального `box-sizing: border-box` (в кабинетах есть),
   поэтому `width: 100%` у поля с внутренними отступами даёт ширину
   больше колонки: 672 + 24 отступа + 2 рамки = 698 при 672 доступных.
   Так каждое поле окна вылезало за правый край, а окно получало
   горизонтальную прокрутку. Правило намеренно охватывает всё внутри
   окна и только его: у остальной админки своя вёрстка, и трогать её
   ради этого нельзя. */
dialog.eddy-modal *,
dialog.eddy-modal *::before,
dialog.eddy-modal *::after,
dialog.crm-modal *,
dialog.crm-modal *::before,
dialog.crm-modal *::after {
    box-sizing: border-box;
}

.eddy-modal__body input,
.eddy-modal__body select,
.eddy-modal__body textarea {
    width: 100%;
    padding: 0 var(--sp-5, 10px);
    border: var(--border-w, 1px) solid var(--border-default, #e2e5e9);
    border-radius: var(--r-md, 8px);
    background: var(--surface-card, #fff);
    font: inherit;
    font-size: var(--fs-sm, 14px);
    color: inherit;
}

.eddy-modal__body input:not([type="checkbox"]):not([type="radio"]),
.eddy-modal__body select:not([multiple]) { height: var(--control-h, 40px); }

.eddy-modal__body input[type="checkbox"],
.eddy-modal__body input[type="radio"] { width: auto; }

.eddy-modal__body textarea {
    padding: var(--sp-5, 10px);
    line-height: var(--lh-normal, 1.5);
    min-height: 88px;
    resize: vertical;
}

.eddy-modal__body input:hover,
.eddy-modal__body select:hover,
.eddy-modal__body textarea:hover { border-color: var(--border-strong, #c9ccd1); }

.eddy-modal__body input:focus,
.eddy-modal__body select:focus,
.eddy-modal__body textarea:focus {
    outline: none;
    border-color: var(--border-focus, #16181A);
    box-shadow: var(--ring, 0 0 0 3px rgba(22, 24, 26, .12));
}

/* Разделитель внутри длинной формы: «Служебные поля» и подобное. */
.eddy-modal__section {
    display: block;
    margin: 12px 0 4px;
    padding-top: 8px;
    border-top: 1px solid var(--border-subtle, #eceae6);
    font-size: var(--fs-2xs, 12px);
    font-weight: var(--fw-bold, 700);
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-muted);
}

/* Пояснение под формой — не подпись поля, поэтому не наследует её вид. */
.eddy-modal__note {
    display: block;
    background: var(--paper-200, #F5F4F0);
    border-radius: var(--r-md, 8px);
    padding: var(--sp-4, 8px) var(--sp-5, 10px);
    margin-top: var(--sp-5, 10px);
    font-size: var(--fs-2xs, 12px);
    font-weight: 400;
    color: var(--text-muted);
}


.crm-emoji-option:hover {
    transform: translateY(-2px);
    border-color: var(--crm-accent);
}

/* Номер карточки: заметен, но не спорит с именем — серый на сером,
   моноширинными цифрами, чтобы его удобно было диктовать. */
.crm-title-id {
    align-self: center;
    padding: 2px 8px;
    border-radius: 10px;
    background: var(--crm-line);
    color: var(--crm-muted);
    font-size: 12px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.crm-emoji-option input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.crm-emoji-option:has(input:checked) {
    border-color: var(--crm-accent);
    background: #eef6f8;
}

.crm-toolbar {
    margin-bottom: 20px;
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.crm-toolbar .button {
    padding: 8px 18px;
    border-radius: 6px;
    background: var(--crm-accent);
    color: #fff;
    text-decoration: none;
    border: none;
    cursor: pointer;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.4;
    box-shadow: 0 1px 2px rgba(0, 0, 0, .08);
    transition: background-color .15s ease, transform .05s ease;
}

.crm-toolbar .button:hover {
    background: var(--crm-accent-hover);
}

.crm-toolbar .button:active {
    transform: translateY(1px);
}


.crm-search-section {
    font-size: 15px;
    font-weight: 700;
    color: var(--crm-ink);
    margin: 24px 0 10px;
}

.crm-search-section:first-of-type {
    margin-top: 0;
}

/* buttons outside the toolbar (report links, comm form, grant-access,
   tasks list/kanban page headers, etc.) keep the plain accent look by
   default — semantic modifiers below apply everywhere a .button sits,
   toolbar or not. Written as compound `.button.button-*` selectors
   (specificity 0,2,0) so they reliably beat both `.crm-toolbar .button`
   (also 0,2,0, but earlier in source) and the bare `.button` rule right
   below (0,1,0) regardless of where a button is used. */
/* ── Кнопка: мост на компонент дизайн-системы ──────────────────────────
   `.button` стоит в ста тридцати местах — на карточках, в отчётах, в
   модальных окнах и в кабинетах. Переписывать все вхождения на
   `.eddy-btn` — работа на несколько заходов, а разъезжаться по виду они
   начали бы уже сегодня: разделы платформы переведены на компоненты
   системы. Поэтому здесь не свои значения, а геометрия и палитра
   `eddy-btn`; разметка остаётся прежней, экраны переводятся по одному.

   Смысл вариантов взят из системы, а не из привычки:
   * `.button` — главное действие, красная заливка;
   * `.button-ghost` — вспомогательное, контур;
   * `.button-danger` — разрушающее, КОНТУР с красной подписью: заливкой
     оно спорит за внимание с главным действием на том же экране;
   * `.button-accent` — подтверждение (Сохранить, Применить, Выдать),
     нефрит. Красный остаётся за главным и за разрушающим.

   Высота — `--control-h` (40): это стандартный размер системы для
   форм и карточек. В тулбарах списков стоит размер `sm` (32) — он
   задан там явно классом `eddy-btn--sm`, а не этой переменной. */
.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-4, 6px);
    height: var(--crm-control-h, 40px);
    padding: 0 var(--sp-7, 16px);
    border: var(--border-w, 1px) solid transparent;
    border-radius: var(--r-control, 8px);
    background: var(--accent, #DD3333);
    color: var(--text-inverse, #fff);
    text-decoration: none;
    cursor: pointer;
    font-family: inherit;
    font-size: var(--fs-sm, 14px);
    font-weight: var(--fw-semibold, 600);
    line-height: 1;
    white-space: nowrap;
    box-shadow: var(--sh-xs, 0 1px 2px rgba(0, 0, 0, .08));
    transition: var(--transition-control, all .15s ease);
}

.button:hover {
    background: var(--accent-hover, #C8322A);
    color: var(--text-inverse, #fff);
    text-decoration: none;
}

.button:active {
    background: var(--accent-pressed, #A32620);
}

.button:focus-visible {
    outline: none;
    box-shadow: var(--ring-focus, 0 0 0 3px rgba(221, 51, 51, .25));
}

.button:disabled {
    background: var(--state-disabled-bg, #eef0f2);
    color: var(--state-disabled-text, #8e949a);
    border-color: transparent;
    box-shadow: none;
    cursor: not-allowed;
    pointer-events: none;
}

.button.button-ghost {
    background: var(--surface-card, #fff);
    color: var(--text-body, #16181A);
    border-color: var(--border-default, #e2e5e9);
    box-shadow: none;
}

.button.button-ghost:hover {
    background: var(--paper-100, #FBFAF8);
    border-color: var(--border-strong, #c9ccd1);
    color: var(--text-body, #16181A);
}

.button.button-danger {
    background: var(--surface-card, #fff);
    color: var(--status-danger, #C8322A);
    border-color: var(--red-100, #F6C9C6);
    box-shadow: none;
}

.button.button-danger:hover {
    background: var(--red-50, #FDEDEB);
    border-color: var(--red-200, #EFA9A4);
    color: var(--status-danger, #C8322A);
}

.button.button-accent {
    background: var(--secondary, #2F7A67);
    color: var(--text-inverse, #fff);
}

.button.button-accent:hover {
    background: var(--secondary-hover, #276353);
    color: var(--text-inverse, #fff);
}

.crm-card-layout {
    display: flex;
    gap: 32px;
    align-items: flex-start;
}

.crm-card-main {
    flex: 1 1 auto;
    min-width: 0;
}

.crm-card-side {
    flex: 0 0 300px;
    border-left: 1px solid #eef0f2;
    padding-left: 24px;
}

.crm-card-side p:last-child {
    border-bottom: none;
}

.crm-hint {
    color: var(--crm-muted);
    font-size: 13px;
    word-break: break-all;
}

/* Общее правило: раньше было привязано к рядам карточки и не доставало
   до таблиц (например, график педагога), где тот же признак «не задано»
   встречается сам по себе. Ряды карточки переехали на `eddy-dl`, а
   правило осталось — оно про сам признак, а не про место. Спеty where
   they're already relied on. */
.empty {
    display: inline-block;
    color: var(--crm-warning-text);
    background: var(--crm-warning-bg);
    border: 1px solid var(--crm-warning-border);
    border-radius: 3px;
    padding: 1px 8px;
    font-size: 12px;
    font-weight: 500;
}

.crm-card-side ul {
    margin: 0;
    padding-left: 0;
    list-style: none;
    font-size: 13px;
    color: var(--crm-ink);
}

.crm-card-side ul li {
    padding: 4px 0;
}

.crm-contact-links a {
    color: var(--crm-label);
    text-decoration: none;
    margin-right: 6px;
}

.crm-contact-links a:hover {
    text-decoration: underline;
}

/* Значок контакта. Размер обязателен: SVG без ширины и высоты
   разворачивается на всю доступную ячейку — телефонная трубка в списке
   плательщиков выросла на 155 пикселей и заняла строку целиком.
   Цвет — от текста ссылки: внутри цветного кружка (`.contact-badge`)
   он белый, рядом с номером — обычный. */
.contact-icon {
    width: 14px;
    height: 14px;
    fill: currentColor;
    vertical-align: -2px;
    flex: none;
}

.contact-badge .contact-icon {
    width: 13px;
    height: 13px;
    vertical-align: 0;
}

.contact-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    color: #fff !important;
    text-decoration: none !important;
    font-size: 11px;
    vertical-align: middle;
}

.contact-badge:hover {
    opacity: .85;
}

.contact-badge-whatsapp {
    background: #25d366;
}

.contact-badge-telegram {
    background: #0088cc;
}

/* Своего значка у MAX в наборе иконок нет, поэтому буква в фирменном
   круге — того же размера и формы, что и соседи. */
.contact-badge-max {
    background: #6f4ff2;
    font-weight: 700;
}

.crm-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: var(--type-table-size, 13.5px);
    color: var(--text-body, #16181A);
}

.crm-table th {
    padding: var(--sp-4, 8px) var(--sp-6, 12px);
    background: var(--paper-100, #FBFAF8);
    border-bottom: var(--border-w, 1px) solid var(--border-default, #e2e5e9);
    text-align: left;
    font-size: var(--type-table-head-size, 11.5px);
    font-weight: var(--fw-semibold, 600);
    text-transform: uppercase;
    letter-spacing: var(--ls-wide, .03em);
    color: var(--ink-500, #6b7280);
    white-space: nowrap;
}

.crm-table td {
    padding: 0 var(--sp-6, 12px);
    height: var(--row-h, 44px);
    border-bottom: var(--border-w, 1px) solid var(--border-subtle, #eef0f2);
    vertical-align: middle;
}

.crm-table tbody tr:last-child td {
    border-bottom: 0;
}

.crm-table tbody tr:hover {
    background: var(--paper-100, #FBFAF8);
}

.crm-comm-form {
    margin-bottom: 16px;
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

.crm-comm-form textarea {
    flex: 1;
    padding: 8px 10px;
    min-height: 20px;
    max-height: 200px;
    border: 1px solid #d1d5db;
    border-radius: 4px;
    font-size: 13px;
}

/* Форма из нескольких полей — колонкой. Реквизиты педагога были
   свёрстаны классом .crm-comm-form, а он собран под одну строку
   («добавить комментарий»: поле плюс кнопка). Пять элементов подряд в
   ряд на телефоне превращались в select шириной в один символ и
   текстовое поле, где номер карты шёл по одной цифре в строку. */
.crm-stack-form {
    display: grid;
    gap: 6px;
    max-width: 440px;
    margin-bottom: 16px;
}

.crm-stack-form label {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: .02em;
    color: var(--crm-label);
}

.crm-stack-form select,
.crm-stack-form textarea,
.crm-stack-form input {
    width: 100%;
    padding: 8px 10px;
    border: 1px solid #d1d5db;
    border-radius: 6px;
    font-size: 14px;
    font-family: inherit;
}

.crm-stack-form button {
    justify-self: start;
    margin-top: 4px;
}

.crm-comm-list {
    list-style: none;
    padding: 0;
}

.crm-comm-list li {
    padding: 10px 0;
    border-bottom: 1px solid #f1f2f4;
    font-size: 13px;
    line-height: 1.5;
}

.crm-history {
    list-style: none;
    padding: 0;
    font-size: 13px;
}

.crm-history > li {
    padding: 10px 0;
    border-bottom: 1px solid #f1f2f4;
}

.crm-history ul {
    margin: 4px 0 0;
    padding-left: 18px;
    color: #6b7280;
}

.crm-messages {
    list-style: none;
    padding: 10px 14px;
    margin: 0 0 16px;
    background: #eaf6ea;
    border: 1px solid #b7e0b7;
    border-radius: 6px;
    font-size: 13px;
}

.crm-messages li + li {
    margin-top: 4px;
}

/* Отказ, показанный зелёным, читается как «сохранено» — ровно наоборот.
   Цвет берётся от самой строки: у Django у каждого сообщения есть свой
   уровень, а рамка одна на весь список, поэтому окрашивается строка. */
.crm-messages:has(li.error) {
    background: #fdecea;
    border-color: #f5c2bd;
}

.crm-messages li.error {
    color: #a32b1c;
}


/* Shared chrome for the teacher and student/payer self-service portals
   (templates/crm_portal_base.html). Deliberately its own lightweight top bar,
   NOT jazzmin's admin chrome — teacher/student/payer roles never get /admin/
   access at all, so this is a separate, smaller design surface that borrows
   the same tokens/typography rather than embedding into admin/base_site.html
   the way the staff calendar/kanban pages do. */
body.portal-body {
    /* Системный шрифт, а не фирменный: так требует дизайн-система, и
       по делу — кабинет открывают по двадцать раз в день, а веб-шрифт
       это лишняя загрузка на каждом заходе. Montserrat и Days One
       остаются публичному сайту и логотипам. Lato здесь был случайным
       наследством: файла мы не грузили, и у половины людей он всё
       равно подменялся системным. */
    font-family: var(--font-ui), "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    margin: 0;
    background: var(--surface-page);
    color: var(--crm-ink);
}

.portal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 14px 28px;
    background: #fff;
    border-bottom: 1px solid var(--crm-line-strong);
}

.portal-brand {
    display: flex;
    align-items: center;
    font-size: 17px;
    font-weight: 700;
    color: var(--crm-ink);
    text-decoration: none;
}

.portal-nav {
    display: flex;
    gap: 2px;
    flex-wrap: wrap;
    row-gap: 4px;
}

/* Одиннадцать пунктов в один ряд на 1180 не встают, а прятать их
   прокруткой на широком экране нельзя: невидимая половина меню читается
   как пропавшие разделы (и убирать пункты нельзя — прямая просьба
   владельца). Поэтому ряд переносится, а пункт делается компактнее:
   иначе меню занимает три строки и шапка вырастает до 93 пикселей. */
.portal-nav a, .portal-nav button {
    font: inherit;
    padding: 6px 11px;
    border-radius: 20px;
    color: var(--crm-muted);
    text-decoration: none;
    font-size: 13px;
    font-weight: 500;
    border: none;
    background: transparent;
    cursor: pointer;
    transition: background-color .15s ease, color .15s ease;
}

.portal-nav a:hover, .portal-nav button:hover {
    background: #eef0f2;
    color: var(--crm-label);
}

.portal-nav a.active {
    background: var(--crm-accent);
    color: #fff;
}

.portal-main {
    padding: 28px;
}

/* Opt-in reading-width wrapper for text/table/form content — deliberately not
   the default on .portal-main itself, since the FullCalendar grid page needs
   its own wider layout (calendar.css: .calendar-page already sets 1200px). */
.portal-narrow {
    max-width: 880px;
    margin: 0 auto;
}

.portal-stat {
    display: inline-block;
    margin-right: 40px;
    margin-bottom: 24px;
}

.portal-stat .value {
    font-size: 28px;
    font-weight: bold;
    display: block;
    color: var(--crm-ink);
}

/* Heading scale reused from .crm-title (h1) and .crm-table th (h2) — scoped to
   .portal-main so it can't bleed into the admin-embedded pages that also load
   this file (student/payer cards, task detail), which don't use bare h1/h2. */
.portal-main h1 {
    font-size: 26px;
    font-weight: 700;
    color: var(--crm-ink);
    margin: 4px 0 20px;
    line-height: 1.2;
}

/* Заголовок календаря сюда не попадает: FullCalendar печатает своё
   «24 – 30 авг. 2026 г.» тегом h2, и общее правило подписи раздела
   делало из даты капслок в 28 пунктов — на странице уроков педагога это
   было первым, что бросалось в глаза. */
.portal-main h2:not(.fc-toolbar-title) {
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--crm-label);
    margin: 32px 0 14px;
}

.portal-main p {
    color: var(--crm-ink);
}

.portal-main a {
    color: var(--crm-accent);
    text-decoration: none;
}

.portal-main a:hover {
    text-decoration: underline;
}

/* Кнопка-ссылка внутри кабинета. Правило выше (0,1,1) перебивает по весу
   `.button` (0,1,0), и подпись на акцентной кнопке оказывалась синим
   цветом ссылки на бирюзовом фоне — нечитаемо. Тот же промах уже был у
   кнопок в материалах (lms.css), и лечится он весом селектора, а не
   !important. Подчёркивание при наведении тоже снимается: кнопка не
   ссылка, даже когда она <a>. */
.portal-main a.button,
.portal-main a.eddy-btn--primary,
.portal-main a.eddy-btn--secondary,
.portal-main a.eddy-btn--danger {
    color: #fff;
}

/* Контурная и призрачная кнопки — тёмный текст: у них светлый фон. */
.portal-main a.eddy-btn--outline,
.portal-main a.eddy-btn--outline:hover,
.portal-main a.eddy-btn--ghost,
.portal-main a.eddy-btn--ghost:hover {
    color: var(--text-body);
}

.portal-main a.eddy-btn:hover { text-decoration: none; }

.portal-main a.button:hover {
    color: #fff;
    text-decoration: none;
}

.portal-main a.button.button-ghost,
.portal-main a.button.button-ghost:hover {
    color: var(--crm-label);
}

.hint,
.portal-main .hint {
    color: var(--crm-muted);
    font-weight: normal;
    font-size: 13px;
}

.portal-main label {
    display: block;
    font-weight: 600;
    color: var(--crm-label);
    margin-top: 14px;
    margin-bottom: 4px;
}

.portal-main input[type="text"],
.portal-main input[type="date"],
.portal-main input[type="time"],
.portal-main textarea,
.portal-main select {
    display: block;
    width: 100%;
    box-sizing: border-box;
    padding: 8px 10px;
    margin-top: 4px;
    border: 1px solid #d1d5db;
    border-radius: 4px;
    font-size: 14px;
    font-family: inherit;
}

.portal-main input[type="file"] {
    font-size: 13px;
}

.portal-main .error {
    color: var(--crm-danger);
    margin-bottom: 16px;
}

.portal-inline-form {
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
}

.portal-main .portal-inline-form select,
.portal-main .portal-inline-form input {
    display: inline-block;
    width: auto;
    margin-top: 0;
}

.portal-main .crm-table td form,
.portal-main .eddy-table td form {
    display: inline;
}

.portal-main .crm-table td .button,
.portal-main .eddy-table td .button {
    padding: 4px 10px;
    font-size: 12px;
}

/* Overrides the generic block/bold form-field .portal-main label treatment —
   these are inline radio choices sitting inside a table cell, not field labels. */
.portal-main .crm-table label.portal-radio-label,
.portal-main .eddy-table label.portal-radio-label {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-weight: 400;
    margin: 0 12px 4px 0;
    color: var(--crm-ink);
}

.portal-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.portal-list li {
    padding: 12px 0;
    border-bottom: 1px solid var(--crm-line);
    font-size: 14px;
}

.portal-list li:last-child {
    border-bottom: none;
}

.portal-list a {
    font-weight: 600;
}

/* Sub-section header within a single card tab (teacher_card.html's "Профиль"
   tab has several sections — availability, salary, certificates, ratings —
   unlike student/payer cards where each tab is one content type). Matches
   the existing small-caps muted convention (.crm-table th) rather than a
   bare heading tag. */
.crm-section-title {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--crm-muted);
    margin: 28px 0 10px;
}

.crm-section-title:first-child {
    margin-top: 0;
}

/* Achievement badges (teacher portal home page) — plain numeric threshold
   inside the circle rather than a Unicode/icon glyph: this project already
   hit a font-coverage gap once with raw Unicode symbol characters (tofu
   boxes for ☰/▦ in jazzmin's font stack), and the portal pages don't load
   Font Awesome at all (only admin-embedded pages do), so plain numbers are
   the only zero-risk option here. */
.achievement-row {
    display: flex;
    gap: 20px;
    flex-wrap: wrap;
    margin-bottom: 24px;
}

.achievement {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 84px;
    text-align: center;
}

.achievement-icon {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    font-weight: 700;
    background: var(--crm-accent);
    color: #fff;
    margin-bottom: 6px;
}

.achievement.locked .achievement-icon {
    background: #eef0f2;
    color: #b8bec4;
}

.achievement-label {
    font-size: 12px;
    color: var(--crm-label);
    line-height: 1.3;
}

.achievement.locked .achievement-label {
    color: #b8bec4;
}


/* Student achievement badges (scheduling/services.py:
   student_achievement_summary, templates/students/_badge_icon.html +
   _achievement_badges.html) — Chinese-style trophy/medal/ribbon/seal/flame
   icons shown identically to the student, their payer, and staff (manager/
   founder) on the student card. Tier color comes from one of the classes
   below rather than an inline style specifically so .badge.locked (two
   classes, higher specificity) reliably overrides a tier's color instead
   of an inline style winning by default. */
.badge-category {
    margin-bottom: 24px;
}

.badge-category-title {
    font-size: 14px;
    font-weight: 700;
    color: var(--crm-label);
    text-transform: uppercase;
    letter-spacing: .02em;
    margin: 0 0 12px;
}

.badge-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 18px;
}

.badge {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 76px;
    text-align: center;
}

.badge-icon {
    width: 56px;
    height: 56px;
    margin-bottom: 6px;
}

.badge-label {
    font-size: 11.5px;
    color: var(--crm-label);
    line-height: 1.3;
}

/* bronze -> silver -> gold -> red-gold -> jade rank progression, shared
   across every tiered category (attendance/tenure/homework/streak) —
   scheduling/services.py: TIER_CLASSES assigns these by rank, not category. */
.badge.tier-bronze { --badge-fill: #B08D57; --badge-accent: #8a6d3f; }
.badge.tier-silver { --badge-fill: #A9B4BC; --badge-accent: #7c8791; }
.badge.tier-gold { --badge-fill: #D4AF37; --badge-accent: #a8842c; }
.badge.tier-redgold { --badge-fill: #B3272D; --badge-accent: #D4AF37; }
.badge.tier-jade { --badge-fill: #2F6F4E; --badge-accent: #D4AF37; }

/* HSK badges aren't ranked — a real seal is always red, so level is told
   apart by the number printed on it (see _badge_icon.html's shape="seal"),
   not by hue. */
.badge.hsk-badge { --badge-fill: #B3272D; --badge-accent: #fdf1ee; }

.badge.locked {
    --badge-fill: #d8dce0;
    --badge-accent: #aab0b8;
}

.badge.locked .badge-label {
    color: #aab0b8;
}

/* Shared modal/filter/checkbox-group UI primitives — first built for
   Финансы (payments/views.py, templates/payments/finance_list.html) as
   the first real modal dialog in this project (native <dialog> element,
   built-in centering + ::backdrop, no JS library — unlike the
   .hover-dropdown pattern used for the navbar quick-add/notifications,
   which is a pure CSS :hover panel unsuited to an actual data-entry
   form). Renamed from a finance-* prefix to crm-* once Лиды became the
   second consumer of this exact pattern — genuinely generic, not
   payments-specific (payments-specific hooks like #finance-edit-link/
   .finance-row-check/.finance-filter-trigger keep their own prefix). */
/* Шапка и подвал прижаты (flex: 0 0 auto), прокручивается только .eddy-modal__body
   scrolls — so the Save/Cancel buttons are always reachable even when a
   form (e.g. the lead edit modal, which has more fields than the add
   modal) has more content than fits in max-height. Previously the whole
   dialog scrolled as one block, which could scroll the footer buttons
   out of view — exactly the "have to scroll to save" complaint this was
   meant to fix. */
/* Scoped to [open]: the UA stylesheet's `dialog:not([open]) { display:
   none }

/* Side-by-side field pairs (birth_date+gender, legal_entity+customer_name,
   child_age+child_name, etc.) — templates/leads/_lead_form_modal.html.
   Keeps the add/edit modal's total height short enough to avoid scrolling,
   matching the AlfaCRM reference layout. */
/* Список филиалов — в строку с переносом, а не столбиком: шесть галочек
   по вертикали занимали треть окна и отодвигали всё остальное за его
   нижний край. */
dialog.eddy-modal #id_branches {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 16px;
    margin: 0;
    padding: 0;
    list-style: none;
}

dialog.eddy-modal #id_branches > div,
dialog.eddy-modal #id_branches > li {
    list-style: none;
}

dialog.eddy-modal #id_branches label {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin: 0;
    font-weight: 400;
    white-space: nowrap;
}

.crm-field-row {
    display: flex;
    gap: 10px;
}

.crm-field-row .crm-field-col {
    flex: 1 1 0;
    min-width: 0;
}

.crm-field-row .crm-field-col-narrow {
    flex: 0 0 130px;
}

.crm-field-errors {
    color: var(--crm-danger);
    font-size: 12.5px;
    margin-top: 3px;
}

.finance-row-check {
    width: 16px;
    height: 16px;
}

.button-disabled {
    opacity: .5;
    pointer-events: none;
}

.crm-range-row {
    display: flex;
    gap: 8px;
}

.crm-range-row input {
    width: 100%;
}

.crm-checkbox-group {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 16px;
    margin-top: 4px;
}

.crm-checkbox-group-vertical {
    flex-direction: column;
    gap: 10px;
}

.crm-checkbox {
    display: flex !important;
    align-items: center;
    gap: 6px;
    font-size: 13.5px;
    font-weight: 400 !important;
    color: var(--crm-ink);
    margin: 0 !important;
}

.crm-checkbox input {
    width: auto !important;
}

.contact-rows {
    margin-top: 4px;
}

.contact-row {
    display: flex;
    gap: 6px;
    align-items: center;
    margin-bottom: 4px;
}

.contact-row select,
.contact-row input {
    width: auto !important;
    flex: 1 1 0;
}

.contact-row-remove {
    flex: 0 0 auto;
    background: none;
    border: none;
    font-size: 16px;
    line-height: 1;
    color: var(--crm-muted);
    cursor: pointer;
    padding: 2px 6px;
}

/* The formset's own DELETE checkbox is still in the DOM (lead_form.js
   reads it directly to mark a saved row for removal) but is redundant as
   a second, separate visible control next to .contact-row-remove's "×"
   button, which already drives the exact same checkbox — hidden, not
   removed from markup. */
.contact-delete-flag {
    display: none;
}

.contact-row-remove:hover {
    color: var(--crm-danger);
}

/* "+ещё" as a small text link (matching the AlfaCRM reference) rather than
   a full button — a real button here adds noticeable height to an already
   tall modal for very little benefit. */
.link-button {
    display: inline-block;
    background: none;
    border: none;
    color: var(--crm-accent);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    padding: 2px 0;
    margin-top: 2px;
}

.link-button:hover {
    text-decoration: underline;
}

/* Этапы воронки settings page (templates/leads/stage_settings.html,
   leads/static/leads/stage_reorder.js) — a genuinely different drag
   interaction from the kanban board's column-to-column move: reordering
   siblings within one vertical list needs actual insertion-position math,
   not just a target-column lookup. */
.stage-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    margin-bottom: 6px;
    background: #fff;
    border: 1px solid #e5e7eb;
    border-radius: 6px;
    cursor: grab;
}

.stage-row.dragging {
    opacity: 0.4;
}

.stage-row-handle {
    color: var(--crm-muted);
}

.stage-row-name {
    flex: 1 1 auto;
    font-weight: 600;
    color: var(--crm-ink);
}

.stage-row-flag {
    font-size: 11px;
    padding: 2px 8px;
    border-radius: 10px;
    background: #eef0f2;
    color: var(--crm-label);
}

.stage-row-won {
    background: #eaf6ea;
    color: #1e7d32;
}

.stage-row-lost {
    background: var(--crm-danger-bg);
    color: var(--crm-danger);
}

/* Student card "visit widget" (templates/students/_visit_widget.html) —
   compact day-cell strip per scheduling/services.py: visit_widget_cells. */
.visit-widget {
    margin-bottom: 16px;
}

.visit-toolbar {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 10px;
}

.visit-legend {
    list-style: none;
    margin: 8px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
    font-size: 12.5px;
    color: var(--crm-label);
}

/* Образец в легенде — не растягивается и не сжимается: он стоит в
   строке-флексе рядом с подписью, и без этого клетка сплющивалась до
   ширины значка. */
.visit-legend .eddy-visit {
    flex: none;
}

.visit-legend li {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* Тот же набор цветов, но плашкой в строке таблицы — история посещений
   в кабинете ученика и родителя. Цвет здесь несёт смысл: «отменён» и
   «пропуск без причины» иначе выглядят одинаковым серым текстом. */
.visit-tag {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 10px;
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
}

/* Полная подпись статуса рядом с короткой — только там, где для неё есть
   место. На телефоне в кабинете видна короткая («Был», «Пропуск»), на
   широком экране за ней идёт уточнение («списан урок»): нужное семье
   слово помещается в строку таблицы, а бухгалтерская точность не
   пропадает. */
.visit-tag-full {
    display: none;
}

@media (min-width: 600px) {
    .visit-tag-full {
        display: inline;
    }
}

.crm-credentials-box {
    background: #f3f4f6;
    border-radius: 6px;
    padding: 10px 12px;
    font-size: 14px;
    font-weight: 600;
    color: var(--crm-ink);
    margin: 0 0 10px;
}

/* Compact "under-the-name" summary for the student card
   (templates/students/student_card.html: {% block summary_panel %}

.crm-color-dot {
    display: inline-block;
    width: 14px;
    height: 14px;
    border-radius: 3px;
    vertical-align: middle;
}

.crm-contacts-row {
    margin-bottom: 14px;
    font-size: 13px;
}

/* Lead card's "Воронка / этап" switcher (templates/leads/lead_detail.html,
   leads/static/leads/lead_stage_switch.js) — posts to the same move_lead
   endpoint the kanban board's drag-and-drop already uses, so a stage
   change here goes through identical archive/unarchive logic. */
.crm-stage-track {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 6px;
}

.crm-stage-pill {
    display: inline-flex;
    align-items: center;
    padding: 6px 14px;
    border-radius: 20px;
    border: 1px solid var(--crm-line-strong);
    background: #fff;
    color: var(--crm-label);
    font: inherit;
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}

.crm-stage-pill:hover {
    border-color: var(--crm-accent);
    color: var(--crm-accent);
}

.crm-stage-pill.active {
    background: var(--crm-accent);
    border-color: var(--crm-accent);
    color: #fff;
    cursor: default;
}

.crm-stage-pill.active:hover {
    color: #fff;
}

/* Матрица посещений группы (templates/groups/group_card.html).

   Классы ячеек те же, что у полоски посещений ученика (.visit-cell +
   .visit-*): состояния одни и те же, и разводить им два набора цветов
   значило бы однажды покрасить «пропуск» по-разному на двух страницах.
   Отличается только раскладка — таблица вместо ленты. */
.visit-matrix-scroll {
    overflow-x: auto;
    max-width: 100%;
}

.eddy-table.visit-matrix th,
.eddy-table.visit-matrix td {
    padding: 4px 6px;
    text-align: center;
    white-space: nowrap;
}

/* Имя ученика прилипает к левому краю: в матрице на сорок занятий
   вперёд без этого не понять, чья строка уехала вбок. */
.eddy-table.visit-matrix .visit-matrix-name {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--crm-surface, #fff);
    text-align: left;
    min-width: 180px;
}

.eddy-table.visit-matrix .visit-matrix-cell {
    width: 34px;
}

.eddy-table.visit-matrix .eddy-visit {
    width: 26px;
    height: 26px;
    min-width: 26px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.eddy-table.visit-matrix .visit-today-col {
    background: #fff6e5;
}

/* Единая лента событий клиента (вкладка «Лента» на карточке ученика).

   Данные и раньше были все, но лежали по четырём вкладкам: чтобы увидеть
   «оплатили — и после этого перестал ходить», приходилось держать две
   вкладки в голове и сверять даты глазами. Отсюда одна колонка с датой
   слева и полоса-акцент слева от каждой записи — тип события читается
   цветом, а не подписью. */
.crm-timeline {
    list-style: none;
    margin: 0;
    padding: 0;
}

.crm-timeline-item {
    display: flex;
    gap: 14px;
    align-items: baseline;
    padding: 8px 12px;
    border-left: 3px solid var(--crm-line-strong);
    margin-bottom: 4px;
    background: #fbfcfd;
}

.crm-timeline-date {
    flex: 0 0 108px;
    font-size: 12px;
    color: var(--crm-muted);
    font-variant-numeric: tabular-nums;
}

.crm-timeline-time {
    margin-left: 6px;
}

.crm-timeline-body {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 10px;
    align-items: baseline;
}

.crm-timeline-detail {
    color: var(--crm-ink);
}

.crm-timeline-extra {
    font-size: 12px;
    color: var(--crm-muted);
}

.crm-timeline-lesson { border-left-color: #3E7CB1; }
.crm-timeline-payment { border-left-color: #2F6B4F; }
.crm-timeline-subscription { border-left-color: #C9A22E; }
.crm-timeline-communication { border-left-color: #7A5AA8; }

/* Признак неподтверждённой операции в списке Финансов. Точка, а не
   подпись: колонка узкая, а сам факт нужен именно как отметка — «эта
   строка ещё не деньги». */
.finance-number {
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.finance-unconfirmed {
    color: var(--crm-danger, #b00020);
    margin-left: 4px;
}


/* ---------------------------------------------------------------------
   Награды: витрина и путь
   ---------------------------------------------------------------------
   Прежний вид показывал недостачу: у реального ученика два значка из
   двадцати семи, и двадцать пять одинаковых серых кружков занимали
   почти весь экран. Здесь непройденное сжато до точки на дорожке, а
   место отдано тому, что получено и что ближе всего.

   Цвет ступени — одна переменная на всю плашку; символ поверх неё
   одноцветный (templates/students/_badge_symbol.html), поэтому одно и
   то же изображение работает и в награде, и в серой точке пути. */

.awards {
    margin: 28px 0 8px;
}

.awards-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 14px;
}

.awards-title {
    margin: 0;
    font-size: 18px;
    font-weight: 700;
    color: var(--crm-ink);
}

.awards-count {
    font-size: 13px;
    color: var(--crm-muted);
    font-variant-numeric: tabular-nums;
}

/* Плашка награды. Объём — из радиального блика и мягкой тени под ней, а
   не из градиента внутри SVG: одинаковые идентификаторы градиентов в
   повторяющемся включении разрешаются по первому найденному, и все
   значки на странице получили бы цвет первого. */
.badge-plate {
    --plate: #d8dce0;
    --plate-symbol: #ffffff;
    position: relative;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background:
        radial-gradient(circle at 34% 26%, rgba(255, 255, 255, .45), rgba(255, 255, 255, 0) 58%),
        var(--plate);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .35),
        inset 0 -2px 4px rgba(0, 0, 0, .12),
        0 6px 14px -8px rgba(17, 24, 39, .55);
}

.badge-plate-lg {
    width: 88px;
    height: 88px;
}

.badge-symbol {
    width: 60%;
    height: 60%;
    fill: var(--plate-symbol);
}

.badge-plate.tier-bronze { --plate: #b98c4e; --plate-symbol: #fff6ea; }
.badge-plate.tier-silver { --plate: #9fabb5; --plate-symbol: #ffffff; }
.badge-plate.tier-gold { --plate: #d9b23c; --plate-symbol: #fffaea; }
.badge-plate.tier-redgold { --plate: #b3272d; --plate-symbol: #ffd977; }
.badge-plate.tier-jade { --plate: #2f6f4e; --plate-symbol: #eaf6ee; }

/* Не полученная награда — не сломанная: светлая плашка без тени и без
   блика, символ виден, но приглушён. Серая заливка вместо этого читалась
   как «не загрузилось». */
.badge-plate.is-locked {
    --plate: #eef0f2;
    --plate-symbol: #b6bcc4;
    background: var(--plate);
    box-shadow: inset 0 0 0 1px #e0e4e8;
}

/* Ближайшая цель: та же плашка, но приглушённая до контура своего
   будущего цвета — обещание, а не полученная награда. */
.badge-plate.is-next {
    background: #fff;
    box-shadow: inset 0 0 0 2px var(--plate);
    --plate-symbol: var(--plate);
}

.awards-goal {
    display: flex;
    align-items: center;
    gap: 18px;
    padding: 16px 18px;
    border: 1px solid var(--crm-line-strong);
    border-radius: 12px;
    background: #fff;
    margin-bottom: 20px;
}

/* «Готов к бою!»: та же карточка, но по верхнему краю — внутри неё
   список из четырёх строк, и центрирование по вертикали уводило бы
   значок в середину списка. */
.awards-ready {
    display: flex;
    align-items: flex-start;
    gap: 18px;
    padding: 16px 18px;
    border: 1px solid var(--crm-line-strong);
    border-radius: 12px;
    background: #fff;
    margin-bottom: 20px;
}

.awards-ready .badge-plate {
    flex: 0 0 auto;
}

.awards-ready-body {
    flex: 1 1 auto;
    min-width: 0;
}

.ready-steps {
    list-style: none;
    margin: 12px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.ready-step {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 13px;
}

.ready-tick {
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 1.5px solid var(--crm-line-strong);
    display: flex;
    align-items: center;
    justify-content: center;
}

.ready-step.is-done .ready-tick {
    background: #2f6f4e;
    border-color: #2f6f4e;
}

.ready-tick svg {
    width: 13px;
    height: 13px;
    fill: #fff;
}

.ready-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.ready-text b {
    font-weight: 600;
    color: var(--crm-ink);
}

/* Сделанный шаг гасится, но не зачёркивается: зачёркнутое читается как
   «отменено», а не «готово». */
.ready-step.is-done .ready-text b {
    color: var(--crm-muted);
    font-weight: 400;
}

.ready-hint {
    font-size: 12px;
    color: var(--crm-muted);
}

.ready-step.is-done .ready-hint {
    display: none;
}

.ready-do {
    margin-left: auto;
    flex: 0 0 auto;
    font-size: 12px;
    font-weight: 700;
    color: var(--crm-accent);
    text-decoration: none;
    white-space: nowrap;
}

.ready-do:hover {
    text-decoration: underline;
}

@media (max-width: 640px) {
    /* На телефоне «Заполнить» не помещается в одну строку с подписью —
       без переноса кнопка наезжала прямо на текст шага. Перенос, а не
       уменьшение шрифта: подпись объясняет, зачем шаг нужен, и мельче
       она просто перестаёт читаться. */
    .ready-step {
        flex-wrap: wrap;
    }

    .ready-text {
        flex: 1 1 0;
    }

    .ready-do {
        flex: 0 0 100%;
        margin-left: 30px;
        margin-top: 2px;
    }

    /* Значок в узкой карточке съедал половину ширины, оставляя списку
       колонку в два слова. */
    .awards-ready .badge-plate-lg {
        width: 56px;
        height: 56px;
    }

    .awards-ready {
        gap: 12px;
        padding: 14px;
    }
}

/* Нажимаемая награда: курсор и лёгкий подъём — единственный признак,
   что за значком что-то есть. Без него окно с рассказом никто не
   откроет, потому что не догадается нажать. */
.js-award {
    cursor: pointer;
}

.js-award:focus-visible {
    outline: 2px solid var(--crm-accent);
    outline-offset: 2px;
}

.award.js-award:hover .badge-plate,
.hsk-seal.js-award:hover {
    transform: translateY(-2px);
    transition: transform .15s ease;
}

.award-modal {
    max-width: 380px;
}

.award-modal-body {
    text-align: center;
}

.award-modal-body .badge-plate {
    margin: 0 auto 14px;
}

.award-modal-kind {
    margin: 0;
    font-size: 12px;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--crm-muted);
}

.award-modal-about {
    margin: 10px 0 0;
    font-size: 14px;
    line-height: 1.5;
    color: var(--crm-ink);
}

.award-modal-status {
    margin: 12px 0 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--crm-muted);
}

/* Плашка не сжимается: во flex-ряду она получала ширину от остатка и
   превращалась в овал — круглая награда, ставшая эллипсом, читается как
   ошибка вёрстки, а не как награда. */
.awards-goal .badge-plate {
    flex: 0 0 auto;
}

.awards-goal-body {
    flex: 1 1 auto;
    min-width: 0;
}

.awards-goal-kind {
    margin: 0;
    font-size: 12px;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--crm-muted);
}

.awards-goal-name {
    margin: 2px 0 10px;
    font-size: 17px;
    font-weight: 700;
    color: var(--crm-ink);
}

.awards-bar {
    height: 8px;
    border-radius: 6px;
    background: #eef0f2;
    overflow: hidden;
}

.awards-bar-fill {
    --plate: #b98c4e;
    display: block;
    height: 100%;
    border-radius: 6px;
    background: var(--plate);
    transition: width .6s cubic-bezier(.22, .61, .36, 1);
}

.awards-bar-fill.tier-silver { --plate: #9fabb5; }
.awards-bar-fill.tier-gold { --plate: #d9b23c; }
.awards-bar-fill.tier-redgold { --plate: #b3272d; }
.awards-bar-fill.tier-jade { --plate: #2f6f4e; }

.awards-goal-left {
    margin: 8px 0 0;
    font-size: 13px;
    color: var(--crm-muted);
}

.awards-earned {
    display: flex;
    flex-wrap: wrap;
    gap: 18px 22px;
    margin-bottom: 28px;
}

.award {
    margin: 0;
    width: 84px;
    text-align: center;
}

/* Полученная награда — самый крупный элемент раздела после цели: ради
   неё раздел и открывают. */
.award .badge-plate {
    margin: 0 auto 8px;
    width: 72px;
    height: 72px;
}

.award-name {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--crm-ink);
    line-height: 1.25;
}

.award-kind {
    font-size: 11.5px;
    color: var(--crm-muted);
    line-height: 1.3;
}

.awards-empty {
    margin: 0 0 24px;
    font-size: 13.5px;
    color: var(--crm-muted);
}

.awards-tracks {
    display: grid;
    gap: 18px;
}

.track + .track,
.awards-tracks + .track {
    margin-top: 18px;
}

.track-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 8px;
}

.track-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--crm-label);
}

.track-now {
    font-size: 13px;
    color: var(--crm-muted);
    font-variant-numeric: tabular-nums;
}

/* Дорожка ступеней: линия между точками показывает, что это путь, а не
   набор. Пройденные точки залиты цветом своей ступени, ближайшая —
   обведена, остальные едва заметны. */
/* Дорожка не растягивается на всю ширину экрана: на десктопе пять точек
   по краям тысячи пикселей перестают читаться как путь и выглядят как
   разрозненные метки. */
.track-steps {
    display: flex;
    align-items: flex-start;
    max-width: 520px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.track-head {
    max-width: 520px;
}

.track-step {
    --plate: #b98c4e;
    position: relative;
    flex: 1 1 0;
    min-width: 0;
    text-align: center;
}

.track-step.tier-silver { --plate: #9fabb5; }
.track-step.tier-gold { --plate: #d9b23c; }
.track-step.tier-redgold { --plate: #b3272d; }
.track-step.tier-jade { --plate: #2f6f4e; }

.track-step::before {
    content: "";
    position: absolute;
    top: 8px;
    right: 50%;
    left: -50%;
    height: 2px;
    background: #e5e7eb;
}

.track-step:first-child::before {
    display: none;
}

.track-step.is-earned::before {
    background: var(--plate);
}

.track-dot {
    position: relative;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #fff;
    box-shadow: inset 0 0 0 2px #e0e4e8;
}

.track-dot svg {
    width: 12px;
    height: 12px;
    fill: #fff;
}

.track-step.is-earned .track-dot {
    background: var(--plate);
    box-shadow: inset 0 0 0 2px var(--plate), 0 2px 6px -2px var(--plate);
}

.track-step.is-next .track-dot {
    box-shadow: inset 0 0 0 2px var(--plate);
}

.track-step-label {
    display: block;
    margin-top: 6px;
    font-size: 11.5px;
    color: var(--crm-muted);
    font-variant-numeric: tabular-nums;
}

.track-step.is-earned .track-step-label {
    color: var(--crm-label);
    font-weight: 600;
}

.hsk-row {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.hsk-seal {
    width: 46px;
    height: 46px;
    border-radius: 10px;
}

.hsk-seal .badge-symbol {
    width: 66%;
    height: 66%;
}

@media (max-width: 640px) {
    .awards-goal {
        gap: 14px;
        padding: 14px;
    }

    .badge-plate-lg {
        width: 68px;
        height: 68px;
    }

    .awards-earned {
        gap: 14px 16px;
    }

    .award {
        width: 72px;
    }

    .track-step-label {
        font-size: 10.5px;
    }
}

/* Оценки уроков в карточке педагога */
.feedback-line {
    font-size: 13px;
    line-height: 1.5;
}

.feedback-comment {
    display: block;
    color: var(--crm-muted);
    font-size: 12px;
}

.crm-danger { color: var(--crm-danger, #a32b1c); }

/* ---------------------------------------------------------------------
   Кабинет ученика: зоны
   ------------------------------------------------------------------ */

/* Каждая зона — своя белая карточка на общем фоне. Раньше главный экран
   шёл сплошной лентой одинаковых блоков, и глазу не за что было
   зацепиться: чтобы найти время следующего занятия, страницу приходилось
   читать сверху донизу. */
.portal-zone {
    background: #fff;
    border: 1px solid var(--crm-line-strong);
    border-radius: 14px;
    padding: 18px 20px;
    margin-bottom: 16px;
}

/* Первая зона выделена: имя, ближайшее занятие и остаток — то, ради чего
   кабинет открывают, и они не должны выглядеть как «ещё один блок». */
.portal-zone-hero {
    background: linear-gradient(180deg, #f7fafb 0%, #fff 100%);
}

.zone-title {
    margin: 0 0 14px;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--crm-muted);
}

.portal-zone .awards-head {
    margin-top: 0;
}

.portal-identity {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
}

.portal-identity h1 {
    margin: 0;
}

/* Кнопка не тянется на всю колонку: во flex-столбце она растягивалась по
   ширине блока и читалась как полоса, а не как кнопка. */
.portal-identity .button {
    align-self: flex-start;
}

.portal-identity-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

/* Уровень, курс и остаток — плашками, а не строкой через точку: так
   видно, что это три разных факта, а не одна фраза. */
.portal-chip {
    padding: 2px 10px;
    border-radius: 12px;
    background: #eef6f8;
    color: var(--crm-accent);
    font-size: 12px;
    font-weight: 600;
}

.portal-chip-quiet {
    background: var(--crm-line);
    color: var(--crm-label);
}

/* ---------------------------------------------------------------------
   Мой педагог
   ------------------------------------------------------------------ */

.teacher-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 12px;
}

.teacher-card {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 12px 14px;
    border: 1px solid var(--crm-line-strong);
    border-radius: 12px;
    text-decoration: none;
    color: inherit;
    background: #fff;
    transition: border-color .15s ease, transform .15s ease;
}

.teacher-card:hover {
    border-color: var(--crm-accent);
    transform: translateY(-2px);
}

.teacher-card-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.teacher-card-label {
    font-size: 11px;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--crm-muted);
}

.teacher-card-name {
    font-size: 17px;
    color: var(--crm-ink);
}

.teacher-card-meta,
.teacher-card-rating {
    font-size: 12px;
    color: var(--crm-muted);
}

.teacher-card-nps {
    margin-left: 6px;
}

/* ── Профиль педагога: сначала показ, потом правка ─────────────── */

.teacher-profile-head {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    flex-wrap: wrap;
    padding: 18px 20px;
    background: linear-gradient(180deg, #f7fafb 0%, #fff 100%);
    border: 1px solid var(--crm-line-strong);
    border-radius: 14px;
    margin-bottom: 16px;
}

.teacher-profile-identity {
    flex: 1 1 260px;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.teacher-profile-identity h1 { margin: 0; }

.teacher-profile-bio {
    margin: 0;
    color: var(--crm-label);
    font-size: 14px;
    line-height: 1.5;
}

.teacher-profile-actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

/* Незаполненное — подсказкой, а не тревогой: это не ошибка, а
   приглашение дозаполнить. */
.teacher-profile-missing {
    padding: 10px 14px;
    border-radius: 10px;
    background: var(--crm-warning-bg);
    border: 1px solid var(--crm-warning-border);
    color: var(--crm-warning-text);
    font-size: 13px;
}

/* «Отмечать некого» вместо таблицы посещаемости. */
.attendance-empty {
    margin: 0 0 16px;
    padding: 14px 16px;
    border: 1px dashed var(--crm-line-strong);
    border-radius: 12px;
    color: var(--crm-muted);
    font-size: 13px;
}

/* Ссылка «← Все дети» над карточкой ребёнка в кабинете родителя. */
.portal-back {
    display: inline-block;
    margin-bottom: 12px;
    font-size: 13px;
    color: var(--crm-label);
    text-decoration: none;
}

.portal-back:hover { color: var(--crm-accent); }

/* Заголовок отдельной страницы кабинета (пополнение, счёт). Не
   `.zone-title`: тот подписывает зону внутри страницы — мелкими
   прописными, — и в роли главного заголовка читался как крик. */
.portal-page-title {
    margin: 0 0 14px;
    font-size: 20px;
    font-weight: 700;
    color: var(--crm-ink);
}

/* Способы оплаты и ожидание платежа по СБП. */
.topup-methods {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 4px;
}

.pay-app-link {
    display: flex;
    max-width: 260px;
    margin: 0 auto;
}

/* «Ждём оплату…» — мигающая строка вместо крутящегося колеса: анимация
   на CSS, без картинки и без библиотеки. */
.pay-spinner {
    margin: 10px 0 0;
    text-align: center;
    font-size: 13px;
    color: var(--crm-muted);
    animation: pay-pulse 1.6s ease-in-out infinite;
}

@keyframes pay-pulse {
    0%, 100% { opacity: .45; }
    50% { opacity: 1; }
}

/* Уважаем системную настройку «меньше движения»: мигающий текст — самое
   первое, что мешает при чувствительности к анимации. */
@media (prefers-reduced-motion: reduce) {
    .pay-spinner { animation: none; opacity: .8; }
}

/* ── Подключение эквайринга ────────────────────────────────────── */

/* Состояние сверху и цветом: «работает / заполнено, но выключено / не
   подключено» — первое, зачем открывают эту страницу. */
.acq-state {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 14px 16px;
    border-radius: 12px;
    margin-bottom: 16px;
    font-size: 13.5px;
}

.acq-state.is-live { background: #eaf7ef; color: #1e6b3a; }
.acq-state.is-ready { background: #fdf6e3; color: #8a6d1f; }
.acq-state.is-off { background: #f3f5f7; color: var(--crm-label); }
.acq-state span { color: inherit; opacity: .85; }

/* Чего не хватает для расчёта зарплаты. Список внутри плашки: обычный
   маркер съедается сбросом админки, поэтому задаём его на самой строке —
   унаследованное значение проигрывает прямому совпадению `ul li` из
   стоковой админки при любом весе. */
.missing-rates {
    margin: 6px 0 0;
    padding-left: 18px;
}

.missing-rates li {
    list-style-type: disc;
    margin: 2px 0;
}

/* Ряд действий на странице результата оплаты. Переносится: у отказа
   кнопок две, и на узком экране они не встают в строку. */
.portal-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 18px;
}

.acq-ok {
    margin: 0 0 14px;
    padding: 10px 14px;
    border-radius: 8px;
    background: #eaf7ef;
    color: #1e6b3a;
    font-size: 13px;
}

/* Две колонки: слева то, что делают в банке, справа то, что заполняют
   здесь. На узком экране — одна. */
.acq-columns {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 16px;
    align-items: start;
}

.acq-card {
    background: #fff;
    border: 1px solid var(--crm-line-strong);
    border-radius: 12px;
    padding: 16px 18px;
}

.acq-title {
    margin: 0 0 12px;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--crm-label);
}

.acq-steps {
    margin: 0 0 16px;
    padding-left: 18px;
    font-size: 13.5px;
    line-height: 1.55;
    color: var(--crm-ink);
}

.acq-steps li { margin-bottom: 8px; }

.acq-url {
    display: block;
    margin: 6px 0;
    padding: 8px 10px;
    background: #f3f5f7;
    border-radius: 6px;
    font-size: 12.5px;
    /* Адрес выделяют мышью и копируют — перенос по любому символу
       важнее красоты строки. */
    word-break: break-all;
}

.acq-group {
    padding: 12px 0;
    border-top: 1px solid var(--crm-line);
}

.acq-group:first-of-type { border-top: none; padding-top: 0; }

.acq-group h3 {
    margin: 0 0 10px;
    font-size: 13px;
    font-weight: 700;
    color: var(--crm-ink);
}

.acq-field {
    display: block;
    margin-bottom: 12px;
}

.acq-field > span {
    display: block;
    margin-bottom: 4px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--crm-label);
}

.acq-field input,
.acq-field select {
    width: 100%;
    max-width: 420px;
    height: var(--crm-control-h, 34px);
    padding: 0 10px;
    border: 1px solid #d1d5db;
    border-radius: 6px;
    font-size: 13.5px;
    font-family: inherit;
}

.acq-check {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 4px 8px;
    align-items: center;
    margin-bottom: 10px;
    font-size: 13.5px;
}

/* Флажок не растягивается: общее правило ширины полей иначе размазывает
   его на всю строку — ровно этим уже болели модальные окна. */
.acq-check input[type="checkbox"] { width: auto; justify-self: start; }
.acq-check em,
.acq-field em {
    grid-column: 2;
    font-style: normal;
    font-size: 12px;
    color: var(--crm-muted);
}

.acq-err { grid-column: 2; color: #a32b1c; font-size: 12px; }

.acq-hint {
    margin: 8px 0 0;
    font-size: 12px;
    color: var(--crm-muted);
}

/* ── Оболочка кабинета: то, чего нет в дизайн-системе ──────────────
   Логотип-ссылка в шапке и «Выйти» формой. Кнопка выхода — именно
   кнопка в <form method="post">: выход POST-запросом, ссылкой его
   делать нельзя (её открывает любой префетч). Выглядит она при этом как
   остальные пункты меню, потому что для человека это такой же пункт. */
.eddy-cabnav__brand {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
}

.eddy-cabnav__list form {
    flex: 0 0 auto;
    display: inline-flex;
}

.eddy-cabnav__list form button {
    padding: 8px 16px;
    border: none;
    border-radius: var(--r-pill, 999px);
    background: none;
    font: inherit;
    font-size: var(--type-app-body-size, 14px);
    font-weight: 500;
    color: var(--ink-600);
    cursor: pointer;
    white-space: nowrap;
}

.eddy-cabnav__list form button:hover {
    background: var(--paper-200);
    color: var(--text-body);
}

/* ── Кабинет: шапка экрана, строка ребёнка, заголовки ──────────────
   Дополнение к дизайн-системе там, где в ней нет готового компонента.
   Всё построено на её же токенах, чтобы значения не разъезжались. */

.cab-hero {
    display: flex;
    gap: 18px;
    align-items: flex-start;
    flex-wrap: wrap;
}

.cab-hero__body { flex: 1 1 240px; min-width: 0; }

.cab-hero__name {
    margin: 0;
    font-size: 26px;
    font-weight: 700;
    letter-spacing: -.01em;
    color: var(--text-body);
}

.cab-hero__badges,
.cab-hero__actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 10px;
}

.cab-hero__actions { gap: 10px; margin-top: 16px; }
.cab-hero__title { margin-top: 22px; }

/* Заголовок раздела внутри карточки: мелкие прописные, справа —
   пояснение или счётчик. Не h1 страницы: тот в кабинете один. */
.eddy-section-title {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin: 0 0 14px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-600);
}

.eddy-section-title__right {
    margin-left: auto;
    font-size: 12px;
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    color: var(--text-muted);
}

/* Форма в две колонки: поля парные по смыслу. На телефоне — одна. */
.eddy-form-2 {
    display: grid;
    /* Ровно две колонки, как в макете: поля здесь парные по смыслу
       (телефон и дата рождения — данные, цель и интересы — про
       обучение), и третья колонка эти пары разбивает. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
    margin-bottom: 14px;
}

@media (max-width: 599px) {
    .eddy-form-2 { grid-template-columns: 1fr; }
}

/* Строка ребёнка. Своя, потому что `eddy-childrow` в системе — сетка под
   другой макет (карточка с колонками), а здесь строка-гармошка. */
.child-list { display: grid; gap: 10px; }

.child-block {
    border-radius: var(--r-card);
    overflow: hidden;
}

.child-block[open] { border-color: var(--border-default); }

.child-summary {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px;
    min-height: 64px;
    cursor: pointer;
    list-style: none;
}

/* Треугольник у <details> рисует сам браузер; в WebKit — отдельным
   псевдоэлементом, поэтому убираются оба. */
.child-summary::-webkit-details-marker { display: none; }

.child-ident { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.child-name { font-size: 15px; font-weight: 700; color: var(--text-body); }
.child-meta { font-size: 13px; color: var(--text-muted); }

.child-chevron {
    margin-left: auto;
    display: inline-flex;
    color: var(--ink-400);
    transition: transform var(--dur, .15s) var(--ease, ease);
}

.child-chevron svg { width: 18px; height: 18px; }
.child-block[open] .child-chevron { transform: rotate(90deg); }

.child-body {
    border-top: 1px solid var(--border-subtle);
    background: var(--paper-100);
    padding: 14px;
    display: grid;
    gap: 14px;
}

.child-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.child-section { display: block; }

.child-login { margin: 0 0 10px; font-size: 13.5px; }

.child-hint {
    display: block;
    margin: 4px 0 0;
    font-size: 12px;
    color: var(--text-muted);
}

/* Уточнение статуса и оценка за ДЗ в таблице посещений. */
.visit-tag-full {
    display: none;
    margin-left: 6px;
    font-size: 11.5px;
    color: var(--text-muted);
}

@media (min-width: 600px) { .visit-tag-full { display: inline; } }

.visit-hw { font-size: 11px; color: var(--text-muted); }

.visit-note {
    padding-top: 0 !important;
    font-size: 12.5px;
    color: var(--ink-600);
    white-space: normal;
}

.visit-note .visit-comment { color: var(--text-muted); margin-left: 6px; }

.eddy-table .is-right { text-align: right; }
.eddy-table .is-nowrap { white-space: nowrap; }

/* ── Награды: цвета рангов и то, чего нет в системе ────────────────
   Диск, кольца, звёзды и сетка — компоненты дизайн-системы. Здесь
   только сопоставление НАШИХ пяти рангов с её цветами (у неё их восемь,
   до платины и алмаза) и блоки, которых в ней нет: «Готов к бою!» со
   списком дел и квадраты HSK.

   `tier-jade` показывается изумрудом: это тот же нефрит, только именем
   системы — своего «нефритового» ранга у неё нет. */
.tier-bronze-disc { background: var(--tier-bronze); color: var(--tier-bronze-ink); }
.tier-silver-disc { background: var(--tier-silver); color: var(--tier-silver-ink); }
.tier-gold-disc { background: var(--tier-gold); color: var(--tier-gold-ink); }
.tier-redgold-disc { background: var(--tier-redgold); color: var(--tier-redgold-ink); }
.tier-jade-disc,
.eddy-award--emerald-disc { background: var(--tier-emerald); color: var(--tier-emerald-ink); }

/* Знак внутри диска — наш SVG, а не глиф системы: он и задаёт смысл
   награды. Размер как у её глифа, цвет — от диска. */
.eddy-award__disc svg { color: #fff; }
.eddy-award--locked .eddy-award__disc svg { color: var(--text-subtle); }

.award-block-title { margin-top: 22px; }

/* «Готов к бою!»: диск слева, дела справа. */
.award-ready {
    display: flex;
    gap: 18px;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 22px;
    cursor: pointer;
}

.award-ready__badge { width: auto; }
.award-ready__body { flex: 1 1 240px; min-width: 0; }

.award-eyebrow {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-subtle);
}

.award-ready__name {
    margin: 2px 0 8px;
    font-size: 17px;
    font-weight: 700;
    color: var(--text-body);
}

.award-ready__steps {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 6px 16px;
    margin-top: 12px;
}

.award-step {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 13px;
    color: var(--text-muted);
}

/* Галочка в кружке: пройденный шаг — нефрит, непройденный — пустой
   контур. Цветом, а не только текстом: список читают взглядом. */
.award-step__tick {
    flex: none;
    width: 18px;
    height: 18px;
    border-radius: 999px;
    display: grid;
    place-items: center;
    border: 1.5px solid var(--border-strong);
    color: #fff;
}

.award-step.is-done .award-step__tick {
    background: var(--jade-500);
    border-color: var(--jade-500);
}

.award-step__tick svg { width: 11px; height: 11px; }
.award-step__text { flex: 1 1 auto; }

/* «Копится сейчас» и HSK — две колонки, на телефоне одна. */
.award-split { margin-top: 22px; }

.award-goal {
    display: flex;
    gap: 16px;
    align-items: center;
    cursor: pointer;
}

.award-goal__body { flex: 1 1 auto; min-width: 0; }
.award-goal__name { font-size: 15px; font-weight: 700; margin-bottom: 8px; }
.award-goal__left { margin-top: 6px; font-size: 12px; color: var(--text-muted); }

.award-hsk__about {
    margin: 0;
    font-size: 13px;
    line-height: 1.6;
    color: var(--ink-600);
}

.award-hsk__row { display: flex; gap: 8px; margin-top: 12px; }

/* Шесть уровней квадратами: сданный — нефритовый. Ряд из шести чисел
   отвечает на «где я» одним взглядом, чего список наград не делает. */
.award-hsk__cell {
    width: 40px;
    height: 40px;
    border-radius: 8px;
    display: grid;
    place-items: center;
    background: var(--paper-200);
    color: var(--text-subtle);
    font-weight: 700;
    font-size: 13px;
}

.award-hsk__cell.is-done { background: var(--jade-500); color: #fff; }
.award-hsk__note { margin-top: 8px; font-size: 12px; color: var(--text-muted); }

/* Весь путь ступенями. */
.award-tracks { display: grid; gap: 18px; margin-top: 22px; }
.award-track__head { display: flex; font-size: 13px; margin-bottom: 8px; }
.award-track__title { font-weight: 600; }
.award-track__now { margin-left: auto; color: var(--text-muted); }

.award-track__steps {
    display: flex;
    justify-content: space-between;
    margin-top: 4px;
    font-size: 11px;
    color: var(--text-subtle);
    font-variant-numeric: tabular-nums;
}

/* ── Кабинет семьи ────────────────────────────────────────────────
   Прежний набор правил для строки ребёнка (своя рамка, шеврон
   псевдоэлементом, свои заголовки) удалён: то же самое теперь делают
   компоненты дизайн-системы и блок «Кабинет: шапка экрана, строка
   ребёнка» выше. Два набора с одинаковым смыслом — гарантированный спор
   о том, какой из них главный. */

/* Домашняя работа и строка педагога — по макету CabChild. */
.hw-list { display: grid; gap: 10px; }

.hw-item {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

.hw-pct { flex: none; font-variant-numeric: tabular-nums; }
.hw-body { flex: 1 1 240px; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.hw-name { font-size: 14px; font-weight: 700; color: var(--text-body); }
.hw-meta { font-size: 13px; color: var(--text-muted); }
.hw-comment { font-size: 12.5px; color: var(--ink-600); }
.hw-act { margin-left: auto; }

.teacher-row {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-card);
    flex-wrap: wrap;
}

.teacher-row__body { flex: 1 1 200px; min-width: 0; }
.teacher-row__name { font-size: 17px; font-weight: 700; color: var(--text-body); }
.teacher-row__meta { font-size: 13px; color: var(--text-muted); }
.teacher-row__act { margin-left: auto; }

/* Оценка и слова педагога в истории посещений. */
.visit-grade { color: var(--crm-ink); }

.visit-hw {
    margin-left: 6px;
    font-size: 11.5px;
    color: var(--crm-muted);
}

.visit-note {
    padding-top: 0;
    font-size: 12.5px;
    color: var(--crm-label);
    /* Связный текст переносится по словам — в отличие от ячеек-данных,
       которым на телефоне задан nowrap ради прокрутки таблицы. */
    white-space: normal;
}

.visit-topic { font-weight: 600; margin-right: 6px; }
.visit-comment { color: var(--crm-muted); }

/* Пополнение и счёт. */
.topup-for {
    margin: 0;
    font-size: 14px;
}

.topup-note {
    margin: 14px 0 0;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--crm-muted);
}

.crm-error {
    margin: 0 0 14px;
    padding: 10px 14px;
    border-radius: 8px;
    background: #fdecea;
    color: #a32b1c;
    font-size: 13px;
}

.invoice-sum {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-bottom: 16px;
}

.invoice-sum-label {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--crm-muted);
}

.invoice-sum-value {
    font-size: 28px;
    font-weight: 700;
    color: var(--crm-ink);
    font-variant-numeric: tabular-nums;
}

.invoice-sum-for { font-size: 13px; color: var(--crm-muted); }

/* QR печатается и сканируется — ему нужен белый фон и запас по краям
   независимо от фона страницы. */
.invoice-qr {
    max-width: 260px;
    margin: 0 auto 6px;
    padding: 10px;
    background: #fff;
    border: 1px solid var(--crm-line-strong);
    border-radius: 12px;
}

.invoice-qr svg { width: 100%; height: auto; display: block; }

.invoice-details th {
    width: 40%;
    color: var(--crm-label);
    font-weight: 600;
}

.credentials-line {
    margin: 0 0 10px;
    padding: 10px 12px;
    border-radius: 8px;
    background: #f3f7f8;
    font-size: 13.5px;
    line-height: 1.5;
    word-break: break-word;
}

/* ── Лента ближайших занятий ───────────────────────────────────── */

/* Горизонтальная лента, а не таблица: «что дальше» спрашивают каждый
   день, «что через две недели» — раз в месяц, и таблица отвечала на оба
   одинаково скучно. Прокрутка вбок вместо переноса — так видно, что
   список продолжается. */
/* Подпись над лентой отвечает на «а это что»: без неё ряд карточек под
   именем читается как второй список ближайших занятий. */
.lesson-strip-head {
    margin: 14px 0 6px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--crm-muted);
}

.lesson-strip {
    display: flex;
    gap: 10px;
    overflow-x: auto;
    padding: 4px 2px 10px;
    scroll-snap-type: x proximity;
    /* Полоса прокрутки тонкая: на широком экране она иначе спорит с
       самими карточками. */
    scrollbar-width: thin;
}

.lesson-chip {
    flex: 0 0 auto;
    scroll-snap-align: start;
    min-width: 168px;
    /* Верхняя граница ширины: в карточке родителя в строке участников
       стоят имена всех детей, ходящих на это занятие, и без предела
       первая же карточка растягивалась шире экрана телефона — то есть
       ближайшее занятие, ради которого ленту и открывают, читалось
       только прокруткой вбок. */
    max-width: min(260px, 74vw);
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 10px 14px;
    border: 1px solid var(--crm-line-strong);
    border-radius: 12px;
    background: #fff;
}

/* Ближайшее занятие выделено: ради него ленту и открывают. */
.lesson-chip.is-next {
    border-color: var(--crm-accent);
    background: linear-gradient(180deg, #f2f8fa 0%, #fff 100%);
    box-shadow: 0 6px 16px -12px rgba(16, 24, 40, .5);
}

/* День недели — над датой и подписью: расписание у ученика недельное
   («по вторникам и четвергам»), и без него дата заставляет сверяться с
   календарём. */
.chip-day {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--crm-accent);
}

.chip-when {
    font-weight: 700;
    font-size: 15px;
    color: var(--crm-ink);
    font-variant-numeric: tabular-nums;
}

.chip-group {
    font-size: 13px;
    color: var(--crm-label);
    /* Переносится по словам, а не обрезается: список имён — это ответ на
       «кого везти», и обрезанный он на этот вопрос не отвечает. */
    overflow-wrap: anywhere;
}

.chip-teacher,
.chip-room {
    font-size: 12px;
    color: var(--crm-muted);
}

/* Карточка ближайшего занятия в шапке — второй смысловой центр экрана,
   поэтому у неё свой фон и подпись мельче, чем у имени. */
.portal-next-lesson {
    background: linear-gradient(180deg, #f7fbfc 0%, #fff 100%);
    box-shadow: 0 8px 20px -16px rgba(16, 24, 40, .55);
}


.lesson-strip-empty {
    margin: 0;
    padding: 14px 16px;
    border: 1px dashed var(--crm-line-strong);
    border-radius: 12px;
    color: var(--crm-muted);
    font-size: 13px;
}


/* ── Витрина наград: полученное и HSK рядом ─────────────────────── */

/* Две колонки, а не один поток: HSK — награда извне, с сертификатом и
   датой, и в общем ряду она читалась бы как очередная ступень. */
.awards-row {
    display: grid;
    grid-template-columns: 2fr 1fr;
    gap: 16px;
    margin-bottom: 18px;
}

@media (max-width: 720px) {
    .awards-row { grid-template-columns: 1fr; }
}

.awards-block-title {
    margin: 0 0 10px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--crm-muted);
}

.awards-hsk-box {
    padding-left: 16px;
    border-left: 1px solid var(--crm-line-strong);
}

@media (max-width: 720px) {
    .awards-hsk-box {
        padding-left: 0;
        border-left: none;
        border-top: 1px solid var(--crm-line-strong);
        padding-top: 14px;
    }
}

.awards-all {
    margin-top: 16px;
}

[data-awards-toggle] {
    margin-top: 4px;
}


/* Долг — красной плашкой, а не тем же серым: это единственное число в
   кабинете родителя, требующее действия. */
.portal-chip-debt {
    background: var(--crm-danger-bg);
    color: var(--crm-danger);
}

/* Подпись вида операции рядом с суммой — только у необычных: возврат,
   корректировка. У обычной оплаты её нет вовсе. */
.payment-kind {
    margin-left: 8px;
    padding: 1px 8px;
    border-radius: 10px;
    background: var(--crm-line);
    color: var(--crm-muted);
    font-size: 11px;
    font-weight: 600;
}

/* ── Главная педагога (макет CabTeacher) ─────────────────────────────
   Три числа стоят голыми, без плашек: их читают одним взглядом, а рамка
   вокруг каждого превращает сводку в отчёт. На телефоне они переносятся
   и держат ширину в треть строки — иначе «минут в этом месяце» ломается
   на три строки и ряд разъезжается. */
.cab-greet { margin-bottom: 4px; }
.cab-greet__title {
    font-size: 26px;
    font-weight: 700;
    line-height: 1.2;
    margin: 0;
}
.cab-bignums {
    display: flex;
    flex-wrap: wrap;
    gap: 40px;
    margin-top: 18px;
}
.cab-bignum__value {
    font-size: 34px;
    font-weight: 700;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    color: var(--text-body);
}
.cab-bignum__label {
    margin-top: 6px;
    font-size: 13px;
    color: var(--ink-600);
}
.eddy-table td.cell-act {
    text-align: right;
    white-space: nowrap;
}
/* Долг по отметкам на главной педагога: «сколько дней назад» стоит
   рядом с датой, а не отдельной колонкой — колонка ради одного числа
   на телефоне съедает место у имени группы. */
.backlog-inline-time { display: none; }
.backlog-ago {
    display: block;
    font-size: 12px;
    color: var(--text-muted);
}
@media (max-width: 599px) {
    /* На телефоне строка этого блока становится карточкой: дата со
       временем, ниже кто, ниже кнопка во всю ширину. Таблицей ряд не
       помещался — 379 точек против 293 доступных, — и кнопка
       «Отметить» уезжала за горизонтальную прокрутку, то есть блок
       прятал единственное, ради чего он заведён. Колонка статуса не
       показывается: в этом блоке он у всех строк один и назван
       заголовком карточки. */
    /* Вес селектора обязан быть не ниже, чем у правила `display: block`
       ниже: `body.portal-body .backlog-table td` весит 0,3,1 и обычное
       `.backlog-table .backlog-time` (0,2,0) молча проигрывает — время
       и статус оставались на экране по два раза. */
    .backlog-table thead { display: none; }
    body.portal-body .backlog-table td.backlog-status,
    body.portal-body .backlog-table td.backlog-time,
    body.portal-body .backlog-table th.backlog-status,
    body.portal-body .backlog-table th.backlog-time { display: none; }
    .backlog-table .backlog-inline-time { display: inline; }
    .backlog-table,
    .backlog-table tbody,
    .backlog-table tr,
    body.portal-body .backlog-table td { display: block; width: auto; }
    .backlog-table tr {
        padding: 10px 0;
        border-bottom: 1px solid var(--border-subtle);
    }
    .backlog-table tr:last-child { border-bottom: 0; }
    body.portal-body .backlog-table td {
        padding: 0;
        border: 0;
        white-space: normal;
    }
    .backlog-table td.eddy-table__primary { margin-top: 2px; }
    .backlog-table td.cell-act { margin-top: 8px; text-align: left; }
    .backlog-table td.cell-act .eddy-btn { width: 100%; }
}
.award-note {
    margin: 14px 0 0;
    font-size: 12px;
    color: var(--text-muted);
}
@media (max-width: 599px) {
    .cab-greet__title { font-size: 22px; }
    .cab-bignums { gap: 16px 24px; }
    .cab-bignum { flex: 1 1 88px; }
    .cab-bignum__value { font-size: 28px; }
}

/* ── Профиль педагога (макет TeacherProfile) ──────────────────────────
   Имя и кнопки в одной строке: кнопка «Изменить» относится к профилю
   целиком, и внизу карточки её пришлось бы искать. На узком экране ряд
   переносится — иначе «Привязать бота» уезжает за край. */
.cab-hero__top {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    flex-wrap: wrap;
}
.cab-hero__top .cab-hero__actions { margin-top: 0; margin-left: auto; }
.cab-hero__bio {
    margin: 14px 0 0;
    font-size: 15px;
    line-height: 1.7;
    color: var(--ink-700);
}

/* Ряд «поле + кнопка»: загрузка файла и добавление сертификата. Поле
   растягивается, кнопка держит свою ширину. */
.cab-form-row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 18px;
}
.cab-form-row .eddy-input { flex: 1 1 200px; min-width: 0; }
.cab-form-row .eddy-btn { flex: none; }
.cab-form-foot { margin-top: 18px; }

.cab-filelist { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.cab-filelist__item {
    display: flex;
    align-items: center;
    gap: 12px;
    justify-content: space-between;
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: 10px;
    font-size: 14px;
}

/* Четыре плашки статистики на телефоне становятся двумя в ряд: в один
   столбец они занимают экран целиком, а четыре в ряд не читаются. */
@media (max-width: 767px) {
    .eddy-statrow { grid-template-columns: 1fr 1fr; }
}

/* ── Отметка урока (макет TeacherAttendance) ──────────────────────────
   Чипы посещения — настоящие радиокнопки, скрытые с сохранением
   доступности: `display:none` выключил бы их для клавиатуры и для
   чтения с экрана. Отсюда сдвиг за пределы поля вместо скрытия. */
.chip-set { display: inline-flex; gap: 6px; flex-wrap: wrap; }
.chip-radio {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}
.chip-radio + .eddy-chip { margin: 0; }
.chip-radio:checked + .eddy-chip {
    background: var(--accent-soft);
    border-color: var(--red-200);
    color: var(--accent-ink);
    font-weight: 600;
}
.chip-radio:focus-visible + .eddy-chip {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.eddy-input--narrow { width: 78px; }
.eddy-table td.cell-mid { text-align: center; }

.cab-back {
    display: inline-block;
    margin-bottom: 8px;
    font-size: 13px;
    color: var(--text-muted);
}
.cab-page-title {
    margin: 0;
    font-size: 22px;
    font-weight: 700;
    color: var(--text-body);
}

@media (max-width: 767px) {
    /* Заголовок и кнопка в столбик: рядом кнопка «Сохранить и провести»
       сжимала название урока до двух букв. */
    .eddy-page-head { flex-direction: column; align-items: stretch; }
}

/* ── Финансы педагога (макет TeacherFinance) ──────────────────────── */
.cab-inline-facts {
    display: flex;
    gap: 20px;
    flex-wrap: wrap;
    align-items: baseline;
    font-size: 14px;
}
.cab-muted { color: var(--text-muted); }
.cab-danger { color: var(--status-danger); }

/* Список «вопрос — ответ»: на телефоне в один столбец, иначе колонка
   ответа сжимается до двух слов в строке. */
@media (max-width: 599px) {
    .eddy-dl { grid-template-columns: 1fr; gap: 2px 0; }
    .eddy-dl dt { margin-top: 10px; }
}

/* Плашка без примечания: у системы в сетке три ряда (подпись, число,
   примечание), и пустой третий оставлял под числом полтора пустых ряда.
   Примечания у статистики педагога нет — рядов два. */
.eddy-stat--plain {
    grid-template-rows: auto auto;
    align-content: space-between;
}

/* Кнопка в узкой колонке карточки. «Сбросить пароль и отправить» в
   двести пикселей одной строкой не помещается и вылезает за панель —
   вместе с рамкой и фоном. В ряду тулбара перенос запрещён правильно
   (там строка рядом с соседями), а здесь кнопки стоят стопкой, и
   перенос — единственный способ остаться внутри колонки. */
.crm-card-side .button {
    height: auto;
    min-height: var(--control-h-sm, 32px);
    padding: var(--sp-3, 6px) var(--sp-5, 10px);
    font-size: var(--fs-xs, 13px);
    white-space: normal;
    text-align: center;
    max-width: 100%;
}

/* ===== Дополнения к компонентам кита, которых у него нет =====
   Всё остальное на карточках теперь рисует сама дизайн-система
   (`eddy-stat`, `eddy-pill`, `eddy-dl`, `eddy-card`, `eddy-tabs`), и
   271 строка собственных правил под `crm-stat`/`crm-row`/`crm-tabs`/
   быстрые списки удалена: мёртвый селектор выглядит рабочим и уводит
   поиск не туда. */

/* Отрицательный баланс. У кита плашка одноцветная — «должен» и «на
   счету» там не различаются, а на карточке ученика это первое, что
   ищут глазами. */
.eddy-stat__value--neg { color: var(--status-danger, #C0392B); }

/* Присутствовал, но урок не списан. У кита такого состояния нет — цвет
   тот же, что у присутствия, а разница помечена штриховой рамкой:
   отдельный цвет читался бы как ещё один вид посещения, а это то же
   посещение с открытым вопросом по деньгам. */
.eddy-visit.is-unpaid {
    box-shadow: inset 0 0 0 1px var(--visit-attended-fg, #1e7d32);
    background-image: repeating-linear-gradient(45deg, transparent 0 4px, rgba(255, 255, 255, .45) 4px 8px);
}

/* Признак «не задано» — плашка по содержимому, а не во всю колонку:
   ячейка списка определений это элемент сетки и растягивается сама. */
.eddy-dl dd.empty { justify-self: start; }

/* «Посмотреть кабинет» — кнопка формы (режим обозревателя заводит
   состояние сессии, ссылкой он включался бы от предзагрузки страницы).
   У кита `eddy-pill__add` рассчитан на ссылку, поэтому кнопке снимаем
   рамку и фон — иначе внутри пилюли она выглядит второй кнопкой. */
button.eddy-pill__add {
    border: 0;
    padding: 0;
    background: none;
    font: inherit;
    font-weight: var(--fw-bold, 700);
    cursor: pointer;
}

/* Хвост быстрого списка («ещё 3 →») — подпись, а не строка данных. */
.eddy-card__more { color: var(--text-muted); font-size: var(--fs-2xs); }

/* Быстрые списки: карточка кита рассчитана на крупный блок, здесь их
   четыре в ряд и внутри по две строки — поля вдвое меньше, иначе
   четверть экрана уходит на воздух. */
.eddy-money4 .eddy-card__head { padding: 10px 12px; }
.eddy-money4 .eddy-card__body {
    list-style: none;
    margin: 0;
    padding: 8px 12px 12px;
    font-size: 12px;
    line-height: 1.35;
}
/* Маркер снимается на самой строке, а не наследованием от списка: у
   стоковой админки правило стоит прямо на `ul li`, а унаследованное
   значение проигрывает прямому совпадению при любом весе селектора. */
.eddy-money4 .eddy-card__body li { padding: 2px 0; list-style: none; }

/* Ссылка «+ добавить» в шапке узкой карточки: при общем размере она
   переносится на вторую строку и шапка вырастает вдвое. */
.eddy-money4 .eddy-card__head { gap: 8px; }
.eddy-money4 .eddy-card__head .eddy-pill__add { font-size: 11.5px; white-space: nowrap; }

/* Ряд плашек с действием: кнопка стоит в подписи, а не под значением —
   так плашка остаётся той же высоты, что и денежные над ней, и ряды не
   разъезжаются. */
.crm-statrow-actions .eddy-stat__label {
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.eddy-stat__add {
    font-size: 12px;
    font-weight: 400;
    color: var(--accent-ink, #B3222B);
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    white-space: nowrap;
}

.eddy-stat__add:hover {
    text-decoration: underline;
}

.crm-statrow-actions .eddy-stat__value {
    font-size: var(--fs-sm, 13px);
    font-weight: 500;
}


/* ── Ровные ряды карточки ────────────────────────────────────────────
   Три ряда плашек читаются как один блок, только когда у них общая
   высота: денежные карточки кита выше плашек-статов, и ряд «группы —
   уроки — педагог — доступ» проваливался под соседний.

   Ряд 1 остаётся крупным: там числа, ради которых карточку открывают.
   Ряды 2 и 3 — одной высоты между собой. Ряд 4 — пилюли, они мелкие по
   своей природе. */
/* Ряд 1 — крупный: там числа, ради которых карточку открывают. */
.eddy-statrow:not(.crm-statrow-actions) .eddy-stat {
    min-height: 148px;
}

/* Ряды 2 и 3 — ровно одной высоты, и это `height`, а не `min-height`:
   содержимое у них разное (список платежей против одной строки с
   группой), и по минимуму они всё равно расходились на полтора десятка
   точек — ряд выглядел съехавшим. Что не поместилось, прокручивается
   внутри карточки. */
.crm-statrow-actions .eddy-stat,
.eddy-money4 .eddy-card {
    height: 124px;
}

.eddy-money4 .eddy-card__body,
.crm-statrow-actions .eddy-stat__value {
    overflow-y: auto;
    min-height: 0;
}

/* У денежной карточки высоту держит список, поэтому он тянется, а не
   обрезается: две строки плюс «ещё N» помещаются ровно. */
.eddy-money4 .eddy-card {
    display: flex;
    flex-direction: column;
}

.eddy-money4 .eddy-card__body {
    flex: 1 1 auto;
}

/* Плашка-действие: значение может занять две строки (две группы, два
   регулярных урока), поэтому сетка строк у неё своя. */
.crm-statrow-actions .eddy-stat {
    grid-template-rows: auto 1fr;
}

/* --- Выдача слов с урока ---------------------------------------------- */
.word-issue { margin-top: var(--sp-6); }
.word-issue__row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--sp-4); margin-top: var(--sp-5); }
.word-issue__field { flex: 0 1 180px; }
.word-issue__btn { flex: 0 0 auto; }

/* Строка поиска над длинным списком в окне (см. select_search.js). */
.eddy-select-search {
    box-sizing: border-box;
    width: 100%;
    height: var(--crm-control-h, 34px);
    margin-bottom: 6px;
    padding: 0 10px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-control);
    background: var(--surface-card);
    font: inherit;
    font-size: var(--fs-sm);
    color: var(--text-body);
}
.eddy-select-search:focus {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: var(--ring-focus);
}

/* --- Карточка группы: панель расписания и лента комментариев ---------- */
/* Числовые плашки и панель расписания стоят в одном ряду, но плашкой
   `eddy-stat` расписание быть не может: у неё сетка из трёх строк
   фиксированной высоты, а строк расписания бывает от одной до четырёх.
   Смешивать же `eddy-stat` с `eddy-stat--text` в одном ряду нельзя —
   у них разная сетка, и значения встают на разной высоте (замерено на
   живой странице: 252 против 285). */
.group-summary { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 2fr); }
.group-schedule {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
    padding: var(--sp-6) var(--sp-7);
    background: var(--surface-card);
    border: var(--border-w) solid var(--border-subtle);
    border-radius: var(--r-card);
}
.group-schedule__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 2px;
}
.group-schedule__row {
    display: flex;
    align-items: baseline;
    gap: 8px;
    min-width: 0;
    font-size: var(--fs-sm);
}
.group-schedule__when { font-weight: var(--fw-semibold); white-space: nowrap; }
.group-schedule__where {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text-muted);
    font-size: var(--fs-2xs);
}
.group-schedule__edit { flex: none; font-size: var(--fs-2xs); }
.group-schedule__row.is-muted { opacity: .55; }
.group-schedule__empty { margin: 0; color: var(--red-600); font-size: var(--fs-2xs); }
.group-schedule__more { font-size: var(--fs-2xs); }

@media (max-width: 1023px) {
    .group-summary { grid-template-columns: 1fr; }
}

/* Общий рендерер полей (`_modelform_fields.html`) рассчитан на окно: там
   подпись и поле выстраивает сама модалка. Вне окна их надо выстроить
   самим, иначе подпись встаёт слева от поля, а `textarea` держит свою
   ширину из атрибута cols — на живой странице вышло 250 точек в блоке
   на 1130. */
.group-comment-form { margin-bottom: var(--sp-6); max-width: 640px; }
.group-comment-form label {
    display: block;
    margin-bottom: 4px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--ink-700);
}
.group-comment-form textarea {
    box-sizing: border-box;
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-control);
    background: var(--surface-card);
    font: inherit;
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: var(--text-body);
    resize: vertical;
}
.group-comment-form textarea:focus {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: var(--ring-focus);
}
.group-comment-form__actions { margin-top: 8px; }
.group-comments { list-style: none; margin: 0; padding: 0; }
.group-comment {
    padding: var(--sp-5) 0;
    border-top: var(--border-w) solid var(--border-subtle);
}
.group-comment__meta {
    display: flex;
    gap: 10px;
    align-items: baseline;
    font-size: var(--fs-2xs);
    color: var(--text-muted);
    margin-bottom: 3px;
}
.group-comment__text { font-size: var(--fs-sm); line-height: var(--lh-normal); }

/* Ссылка в пилюле без жирности: `eddy-pill__add` набран полужирным,
   потому что это «+ добавить» — призыв. «Копировать группу» рядом с
   признаками группы призывом не является и весом спорит с ними. */
.eddy-pill__link { color: var(--text-link); cursor: pointer; }
/* Разрушающее действие в окне — отдельной строкой под подвалом, а не
   рядом с «Сохранить». */
.eddy-modal__aside {
    padding: 0 var(--sp-7) var(--sp-6);
    text-align: right;
}

/* Просроченное пробное участие. Модификатор объявлен здесь, а не в
   компоненте кита: это состояние строки участия, а не общий вид пилюли —
   в других разделах такой пометки нет. */
.eddy-pill--danger {
    border-color: var(--red-200, #f3c4c4);
    background: var(--red-50, #fdf0f0);
    color: var(--red-600, #a01c1c);
}

/* Выдача сквозного поиска. Раздел без находок не рисуется вовсе, поэтому
   пустых таблиц здесь не бывает и стилей под них нет. */
.crm-search-section { margin: var(--sp-7) 0 var(--sp-3); font-size: 15px; font-weight: 600; }
.crm-search-hint { margin: var(--sp-6) 0; color: var(--text-muted); max-width: 70ch; }
.crm-search-cell { padding: var(--sp-3) var(--sp-4); }
.crm-search-cell--sub { color: var(--text-muted); }
