/* ─────────────────────────────────────────────────────────────────────────────
   Korepeta — общий визуальный фундамент. Обычный CSS, а не SCSS, и лежит в
   `public/` намеренно: его подключают ДВЕ разные вещи.

   1. Приложение — строкой `<link rel="stylesheet" href="/kit/base.css">` в
      `src/index.html`, ровно так же, как `/fonts/fonts.css`. Идёт до
      собранного `styles.css`, поэтому правила оболочки перебивают его при
      равной специфичности, а не наоборот.
   2. Страница-визитка — `public/site/*.html`. Она статическая: ни Angular, ни
      сборки, ни JS-фреймворка. Собирается генератором `scripts/site/`.

   ── Зачем разделять ──

   Визитка обязана выглядеть как продолжение админки, а не «похоже на неё».
   Единственный способ добиться этого навсегда — общий файл: не «те же цвета,
   выписанные во второй раз», а буквально одни и те же правила. Копия палитры
   разъехалась бы на первой же правке — этот проект уже проходил такое с
   адресами роутов (`core/routes.ts`) и с переводами (`i18n-keys.spec.ts`), и
   оба раза лечилось одним источником, а не аккуратностью.

   ── Что сюда входит, а что нет ──

   Сюда — то, что осмысленно на любой странице продукта: токены, сброс,
   типографика, марка, кнопки, карточки, чипы, поля ввода, полоса, аватар,
   сетки, утилиты, правила «палец вместо мыши» и «меньше движения».

   Не сюда — оболочка админки: сетка `.app`, боковое меню, таблицы, полосы
   действий, экраны входа. Визитке они не нужны, а от переезда сюда пользы
   ноль. Они остались в `src/styles.scss`.

   ── Точки перелома продублированы ──

   Ниже 640px и 1023.98px написаны числами: обычный CSS не знает про
   `$bp-mobile-max` из `src/styles.scss`. Это третье место после
   `layout/breakpoint.ts`, и оно подписано с обеих сторон, а
   `src/styles.spec.ts` сверяет, что все три не разъехались.
   ───────────────────────────────────────────────────────────────────────── */

:root {
  /* ── Бумага и поверхности (PLAN.md §4.1) ── */
  --bg:        #FBF9F4;
  --surface:   #FFFFFF;
  --surface2:  #F5F1E6;
  --surface3:  #EDE8DA;

  --line:      #E2DCCB;
  --line2:     #C8C0AB;
  --line-soft: #EFEBDF;

  /* ── Текст ── */
  --ink:       #1A1610;
  --ink2:      #4A4335;
  --ink3:      #6E6656;
  --ink4:      #A79E8A;

  /* ── Акценты ── */
  --accent:       #B85C16;
  --accent-soft:  #FAEBD2;
  --accent-ink:   #8E4710;

  --info:      #284B70;
  --info-soft: #E5ECF4;

  /* ── Статусы. `--warn-ink` — не украшение: текст цветом `--warn` на
        подложке `--warn-soft` не дотягивает до контраста, и по проекту в
        шести местах был вбит один и тот же `#7A5913`. Теперь он токен. ── */
  --pos:       #1F7A3E;
  --pos-soft:  #DFEBD8;
  --neg:       #A8341E;
  --neg-soft:  #F6DED6;
  --warn:      #A47214;
  --warn-soft: #F7E9C6;
  --warn-ink:  #7A5913;

  /* Затемнение под окнами и ящиком — один цвет, а не четыре разных rgba(). */
  --scrim: rgba(26, 22, 16, 0.44);

  /* ── Скругления (PLAN.md §4.1: 4 / 8 / 14 / 20) ── */
  --radius-sm: 4px;
  --radius:    8px;
  --radius-lg: 14px;
  --radius-xl: 20px;
  --radius-pill: 999px;

  /* ── Тени ── */
  --shadow-sm: 0 1px 2px rgba(26, 22, 16, 0.05);
  --shadow:    0 1px 3px rgba(26, 22, 16, 0.06), 0 4px 12px rgba(26, 22, 16, 0.04);
  --shadow-lg: 0 4px 16px rgba(26, 22, 16, 0.09), 0 14px 36px rgba(26, 22, 16, 0.07);

  /* ── Движение (PLAN.md §4.1: 140 / 260 / 520ms, без прыжков) ── */
  --t-fast: 140ms;
  --t-mid:  260ms;
  --t-slow: 520ms;
  --ease:   cubic-bezier(0.2, 0.8, 0.2, 1);

  /* ── Шрифты ── */
  --ff-sans: 'Inter', ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --ff-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
  --ff-serif: 'Fraunces', 'Source Serif 4', Georgia, serif;
  --ff-hand:  'Caveat', 'Comic Sans MS', cursive;

  /* ── Шкала расстояний ──
     Ступени постоянные: отступ между двумя строчками текста не обязан расти
     вместе с монитором. Растут только поля страницы и карточек — там разница
     между телефоном и монитором действительно есть. */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-7: 32px;
  --sp-8: 40px;
  --sp-9: 56px;

  /* Поля страницы: 16px на телефоне → 40px на широком экране. */
  --gutter:   clamp(16px, 8px + 2.2vw, 40px);
  /* Поля карточки: 14 → 22. Карточка на телефоне не имеет права тратить
     четверть ширины на воздух — именно так текст и оказывался «зажат». */
  --card-pad: clamp(14px, 10px + 1.2vw, 22px);

  /* ── Кегли ──
     Тело чуть крупнее на широком экране, заголовки — заметно. */
  --fs-3xs: 10.5px;
  --fs-2xs: 11px;
  --fs-xs:  11.5px;
  --fs-sm:  13px;
  --fs-md:  14px;
  --fs-lg:  16px;
  --fs-body: clamp(14.5px, 14.1px + 0.12vw, 15.5px);
  --fs-h1: clamp(21px, 17.4px + 1.1vw, 30px);
  --fs-h2: clamp(17px, 15.1px + 0.6vw, 21px);
  --fs-h3: clamp(15px, 14.2px + 0.25vw, 17px);
  /* Крупное число в плитке статистики. Раньше было жёстких 26px, и на
     телефоне «100%» в колонке шириной 150px переносился на две строки. */
  --fs-num: clamp(21px, 17px + 1.1vw, 28px);

  /* ── Оболочка ── */
  --sidebar-w: 244px;
  --appbar-h:  56px;
  --page-max:  1180px;

  /* ── Безопасные зоны iOS (чёлка и полоса «домой») ── */
  --safe-top:    env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left:   env(safe-area-inset-left, 0px);
  --safe-right:  env(safe-area-inset-right, 0px);

  /* Куда прижимаются «плавающие» полосы действий (`.action-bar`). Одно
     значение на все три такие полосы в проекте: до этого каждая считала
     отступ сама и все три считали его через высоту нижней панели вкладок —
     панели больше нет. */
  --dock-bottom: calc(var(--safe-bottom) + var(--sp-5));
}

/* ── Сброс ─────────────────────────────────────────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }

html {
  background: var(--bg);
  /* iOS в альбомной ориентации самовольно раздувает текст — из-за этого
     подписи вылезали из кнопок ровно после поворота телефона. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

html, body { margin: 0; padding: 0; }

body {
  font-family: var(--ff-sans);
  font-size: var(--fs-body);
  line-height: 1.55;
  color: var(--ink);
  background-color: var(--bg);
  /* Зернистость бумаги (PLAN.md §4.1) — плитка 140×140 в data-URI, то есть
     без единого запроса наружу и в рамках CSP (`img-src … data:`). */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.035'/%3E%3C/svg%3E");
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Длинное слово без пробелов (ссылка родителя, путь к файлу, формула) не
     должно распирать колонку изнутри — на телефоне это первая причина, по
     которой «текст выходит за границы». */
  overflow-wrap: break-word;
}

/* Картинка никогда не шире своего места. Правило общее, потому что забыть
   его на конкретном `<img>` можно ровно один раз. */
img, svg, video, canvas { max-width: 100%; }
img, video { height: auto; }

/* Видимая обводка фокуса на всём, что можно нажать. До этого она была ровно
   у одного компонента (`ko-toggle`) — с клавиатуры продукт был непроходим. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Серый прямоугольник на месте нажатия в мобильном Chrome/Safari — вместо
   него у нас свои состояния. */
button, a, [role='button'], label, input, select, textarea {
  -webkit-tap-highlight-color: transparent;
}


/* ── Типографика ───────────────────────────────────────────────────────── */

h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -0.015em; color: var(--ink); }
h1 { font-size: var(--fs-h1); line-height: 1.15; letter-spacing: -0.025em; }
h2 { font-size: var(--fs-h2); line-height: 1.25; }
h3 { font-size: var(--fs-h3); line-height: 1.3; }
h4 {
  font-size: var(--fs-2xs); line-height: 1.3; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink3); font-weight: 600;
}
p { margin: 0; }

.mono { font-family: var(--ff-mono); }
.num  { font-family: var(--ff-mono); font-variant-numeric: tabular-nums; }

.muted  { color: var(--ink3); }
.subtle { color: var(--ink2); }

/* Надзаголовок: «БАЛЛЫ», «СРОК СДАЧИ». Разметка
   `text-xs muted + font-weight + letter-spacing + text-transform` была
   скопирована по экранам два десятка раз — теперь это один класс. */
.overline {
  font-family: var(--ff-mono);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink3);
}

.truncate {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Только для скринридера: подпись, которая не должна занимать место. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

a { color: var(--accent-ink); text-decoration: none; }
@media (hover: hover) and (pointer: fine) {
  a:hover { text-decoration: underline; text-decoration-style: dotted; }
}

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


/* ── Марка ─────────────────────────────────────────────────────────────── */

.brand {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
  color: inherit;
}
.brand-mark {
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: var(--radius);
  background: var(--accent);
  color: #FFF;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--ff-serif);
  font-weight: 700;
  font-size: 17px;
  letter-spacing: -0.02em;
}
.brand-name {
  font-family: var(--ff-serif);
  font-weight: 700;
  font-size: 18px;
  letter-spacing: -0.015em;
  color: var(--ink);
}


/* ── Сегментированный переключатель ────────────────────────────────────────
   Группа кнопок, из которых включена одна. Назывался `.role-switch`, пока
   таким переключателем выбирали роль; роль выбирать больше нельзя (Э11,
   PLAN.md §11), а стиль остался и живёт у переключателя языка. */
.segmented {
  display: inline-flex;
  background: var(--surface3);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 3px;
  align-self: stretch;
}
/* `a` рядом с `button`: в приложении сегментами переключают состояние и это
   кнопки, на визитке — язык, и это настоящие ссылки на другой адрес (иначе
   поисковик не увидит связи между версиями, а человек не сможет открыть
   версию в новой вкладке). Вид у них обязан быть один. */
.segmented button, .segmented a {
  /* `flex: 1` — это `flex-basis: 0`, то есть кнопки делят ширину поровну.
     Ровно поэтому раньше стоял `min-width: 0`, и ровно поэтому надпись
     ломалась пополам: базовый размер нулевой, а `body` наследует
     `overflow-wrap: break-word` — при нехватке места «EN» уезжало в две
     строки буквами друг под другом (экран входа, 2026-08-22).

     `max-content` возвращает нижнюю границу — саму надпись: пока места
     хватает, кнопки по-прежнему равны, а когда не хватает, сжимается что
     угодно, но не текст. `nowrap` закрывает то же самое со второй стороны:
     подпись сегмента не переносится никогда. */
  flex: 1;
  min-width: max-content;
  white-space: nowrap;
  padding: 6px 12px;
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink3);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.segmented .on {
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-sm);
}


/* ── Кнопки ────────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 38px;
  padding: 9px var(--sp-4);
  border-radius: var(--radius);
  border: 1px solid var(--line2);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 500;
  font-family: inherit;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease),
              color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
  /* Подпись кнопки не переносится, но и не распирает строку: слишком
     длинный перевод (немецкий) обрежется многоточием, а не уедет за край. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  /* Кнопкой бывает и ссылка (`<a class="btn">` на визитке). Без этого она
     получала бы подчёркивание от общего правила для `a`. */
  text-decoration: none;
}
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn:disabled:active { transform: none; }

.btn-primary { background: var(--accent); border-color: var(--accent); color: #FFF; }
.btn-ghost   { background: transparent; border-color: transparent; color: var(--ink2); }
/* Разрушающее действие. Раньше писалось инлайном (`style="color: var(--neg)"`)
   в четырёх местах и в одном — целым набором из трёх свойств. */
.btn-danger  { color: var(--neg); border-color: var(--line2); background: var(--surface); }
.btn-danger-solid { background: var(--neg); border-color: var(--neg); color: #FFF; }

@media (hover: hover) and (pointer: fine) {
  a.btn:hover                            { text-decoration: none; }
  .btn:not(:disabled):hover              { background: var(--surface2); border-color: var(--ink4); }
  .btn-primary:not(:disabled):hover      { background: var(--accent-ink); border-color: var(--accent-ink); }
  .btn-ghost:not(:disabled):hover        { background: var(--surface3); color: var(--ink); border-color: transparent; }
  .btn-danger:not(:disabled):hover       { background: var(--neg-soft); border-color: var(--neg); }
  .btn-danger-solid:not(:disabled):hover { background: #8E2B18; border-color: #8E2B18; }
}

.btn-sm { min-height: 32px; padding: 5px 10px; font-size: var(--fs-xs); border-radius: var(--radius-sm); }
.btn-lg { min-height: 46px; padding: 12px 22px; font-size: var(--fs-lg); }
/* Кнопка-иконка: квадрат, а не приплюснутый прямоугольник. */
.btn-icon { padding: 0; width: 38px; min-height: 38px; flex: none; }
.btn-icon.btn-sm { width: 32px; min-height: 32px; }


/* ── Карточки ──────────────────────────────────────────────────────────── */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--card-pad);
}
.card-tight { padding: calc(var(--card-pad) * 0.72); }
.card-pad   { padding: calc(var(--card-pad) * 1.18); }
/* Акцентная кромка сверху вместо рамки целиком (PLAN.md §4.1) — для
   карточек, которые должны выделяться из ряда соседних. */
.card-accent { border-top: 2px solid var(--accent); }

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.card-title {
  font-size: var(--fs-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  min-width: 0;
}
.card-subtitle { font-size: var(--fs-xs); color: var(--ink3); margin-top: 2px; }


/* ── Метки-чипы ────────────────────────────────────────────────────────── */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  max-width: 100%;
  padding: 3px 9px;
  font-size: var(--fs-xs);
  font-weight: 500;
  border-radius: var(--radius-sm);
  background: var(--surface2);
  color: var(--ink2);
  border: 1px solid var(--line);
  font-family: var(--ff-sans);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chip-pos   { background: var(--pos-soft);    color: var(--pos);        border-color: transparent; }
.chip-neg   { background: var(--neg-soft);    color: var(--neg);        border-color: transparent; }
.chip-warn  { background: var(--warn-soft);   color: var(--warn-ink);   border-color: transparent; }
.chip-info  { background: var(--info-soft);   color: var(--info);       border-color: transparent; }
.chip-amber { background: var(--accent-soft); color: var(--accent-ink); border-color: transparent; }
.chip-mono  { font-family: var(--ff-mono); font-size: var(--fs-2xs); letter-spacing: 0.02em; }


/* ── Поля ввода ────────────────────────────────────────────────────────── */

.input, .select, .textarea {
  width: 100%;
  min-height: 38px;
  padding: 8px 12px;
  border: 1px solid var(--line2);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-family: inherit;
  font-size: var(--fs-sm);
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.input:disabled, .select:disabled, .textarea:disabled {
  background: var(--surface2);
  color: var(--ink3);
  cursor: not-allowed;
}
/* `resize: none` — высоту держит `koAutosize` (ui/autosize), а не ручка в
   углу: поле само растёт под содержимое. */
.textarea { resize: none; min-height: 80px; font-family: var(--ff-sans); }

/* Родная стрелка `<select>` в Safari на телефоне рисуется поверх текста;
   рисуем свою и оставляем ей место. */
.select {
  appearance: none;
  padding-right: var(--sp-7);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none' stroke='%236E6656' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
}


/* ── Полоса заполнения ─────────────────────────────────────────────────── */

.bar {
  height: 6px;
  background: var(--surface3);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.bar-fill {
  height: 100%;
  background: var(--accent);
  border-radius: inherit;
  transition: width var(--t-slow) var(--ease);
}

/* ── Аватар ────────────────────────────────────────────────────────────── */

.avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: var(--fs-sm);
  color: #FFF;
  flex-shrink: 0;
  letter-spacing: -0.01em;
  overflow: hidden;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.avatar-sm { width: 28px; height: 28px; font-size: var(--fs-2xs); }
.avatar-lg { width: 52px; height: 52px; font-size: 18px; }

/* ── Разделитель ───────────────────────────────────────────────────────── */

.hr { height: 1px; background: var(--line); border: none; margin: var(--sp-4) 0; }


/* ── Сетки раскладки ───────────────────────────────────────────────────────
   Все три — «сами по себе отзывчивые»: колонок ровно столько, сколько
   влезает, без единого media-запроса. `min(100%, …)` в `minmax` обязателен —
   без него колонка шириной 220px не помещается в экран шириной 200px и
   выталкивает страницу вбок. Это ровно тот класс поломки, на который
   жаловался владелец. */

/* Сетка формы. Ширину назначает колонка, а не поле: до этого её задавал
   инлайновый `max-width` у самого поля (320 у одного, 260 у другого, 120 у
   третьего), и форма читалась как набор случайно брошенных прямоугольников. */
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
  gap: var(--sp-4) var(--sp-5);
  align-items: start;
}
.form-grid > .span-2 { grid-column: 1 / -1; }

/* Плитки чисел. `--col` можно переопределить на месте: `style="--col: 150px"`. */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--col, 170px)), 1fr));
  gap: var(--sp-3);
}

/* Свободная сетка карточек. */
.auto-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--col, 260px)), 1fr));
  gap: var(--sp-4);
}

/* Две колонки «основное + боковое», сворачивающиеся в одну.
   `minmax(0, 1fr)`, а не `1fr`: у второго минимальная ширина — `auto`, и
   длинное неразрывное содержимое (ссылка родителя моноширинным шрифтом)
   распирает колонку изнутри. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, var(--aside, 320px));
  gap: var(--sp-5);
  align-items: start;
}
.split-even { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* Колонка ограниченной ширины: длинная строка перестаёт читаться примерно
   после 80 знаков, а форма во всю ширину монитора выглядит анкетой.
   Раньше писалось `style="max-width: 720px"` на каждом таком экране. */
.measure    { width: 100%; max-width: 720px; }
.measure-sm { width: 100%; max-width: 640px; margin-inline: auto; }

/* Вертикальная стопка карточек — самый частый скелет экрана. */
.stack { display: flex; flex-direction: column; gap: var(--sp-4); }

/* Горизонтальная прокрутка для того, что физически не сжимается (широкая
   таблица, сетка календаря). `overscroll-behavior-inline: contain` не даёт
   жесту прокрутки внутри увести всю страницу вбок. */
.scroll-x {
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  -webkit-overflow-scrolling: touch;
}

/* ── Мелкие утилиты ────────────────────────────────────────────────────── */

/* `.row` переносится по умолчанию. Это не косметика: не переносящаяся
   строка — самая частая причина, по которой содержимое уезжало за правый
   край. Кому перенос вреден (полоса из иконки и одного слова) — `.row-tight`. */
.row  { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-3); }
.row-tight { flex-wrap: nowrap; }
.col  { display: flex; flex-direction: column; gap: var(--sp-2); }
.grow { flex: 1; min-width: 0; }
/* Разрешение сжиматься внутри flex/grid. Нужно там, где `.truncate` был бы
   ошибкой: он вдобавок ставит `nowrap` и `overflow: hidden`, и колонка с
   несколькими строками (имя, подпись, полоса реакций) обрезалась бы целиком —
   ровно это и случилось с шапкой экрана проверки работы. */
.min-w-0 { min-width: 0; }
.gap-1 { gap: var(--sp-1); } .gap-2 { gap: var(--sp-2); } .gap-3 { gap: var(--sp-3); }
.gap-4 { gap: var(--sp-4); } .gap-5 { gap: var(--sp-5); } .gap-6 { gap: var(--sp-6); }
.mt-1 { margin-top: var(--sp-1); } .mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); } .mt-4 { margin-top: var(--sp-4); }
.mt-6 { margin-top: var(--sp-6); }
.mb-0 { margin-bottom: 0; }
.mb-2 { margin-bottom: var(--sp-2); } .mb-3 { margin-bottom: var(--sp-3); }
.mb-4 { margin-bottom: var(--sp-4); } .mb-6 { margin-bottom: var(--sp-6); }
.text-xs { font-size: var(--fs-xs); }
.text-sm { font-size: var(--fs-sm); }
.text-lg { font-size: var(--fs-lg); }
.strong  { font-weight: 600; }
/* Текст с сохранёнными переводами строк: отчёт о занятии, комментарий. */
.pre     { white-space: pre-wrap; }
.hidden  { display: none; }
.justify-end     { justify-content: flex-end; }
.justify-center  { justify-content: center; }
.justify-between { justify-content: space-between; }
.items-start     { align-items: flex-start; }
.items-end       { align-items: flex-end; }
.items-baseline  { align-items: baseline; }
/* Зеркальная иконка: «назад» — это «вперёд» наоборот, отдельного значка под
   это в наборе нет. Было расписано `style="transform: scaleX(-1)"` в шести
   местах. */
.flip { transform: scaleX(-1); }
.ink  { color: var(--ink); }
.pos  { color: var(--pos); }
.neg  { color: var(--neg); }
.warn { color: var(--warn-ink); }

/* Полоса-объяснение: «сохранено», «не вышло», «превышен бюджет». Была
   расписана инлайном `padding + border-radius + background + color +
   font-size` в двенадцати местах, каждый раз чуть иначе. */
.notice {
  padding: 10px 14px;
  border-radius: var(--radius);
  font-size: var(--fs-sm);
  line-height: 1.5;
  background: var(--surface2);
  color: var(--ink2);
}
.notice-ok   { background: var(--pos-soft);    color: var(--pos); }
.notice-bad  { background: var(--neg-soft);    color: var(--neg); }
.notice-warn { background: var(--warn-soft);   color: var(--warn-ink); }
.notice-info { background: var(--accent-soft); color: var(--accent-ink); }

/* ── Прокрутка ─────────────────────────────────────────────────────────── */

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line2); border-radius: var(--radius-sm); }
@media (hover: hover) and (pointer: fine) {
  ::-webkit-scrollbar-thumb:hover { background: var(--ink4); }
}


/* ── Палец, а не ширина экрана ─────────────────────────────────────────────
   Признак — `pointer: coarse`, то есть «управляют пальцем». Ноутбук с
   сенсорным экраном получает крупные цели тоже, а узкое окно браузера на
   десктопе — нет; при `max-width` было ровно наоборот. Второе условие
   оставлено для эмуляции телефона без сенсорного ввода. */
@media (pointer: coarse), (max-width: 640px) {
  /* 44px — минимальная цель по HIG и Material. */
  .btn      { min-height: 44px; padding: 10px var(--sp-4); }
  .btn-sm   { min-height: 38px; padding: 8px 12px; }
  .btn-lg   { min-height: 52px; }
  .btn-icon { width: 44px; }
  .btn-icon.btn-sm { width: 38px; }

  /* 16px — порог, ниже которого iOS зумит страницу при фокусе на поле.
     Именно это и «ломало вёрстку» после первого же тапа по инпуту. */
  .input, .select, .textarea { min-height: 44px; font-size: 16px; padding: 11px 12px; }
  .textarea { min-height: 96px; }
  .select { padding-right: var(--sp-8); }

  /* Флажок и радиокнопка родные — на телефоне они по 13px. */
  input[type='checkbox'], input[type='radio'] { width: 20px; height: 20px; accent-color: var(--accent); }
}


/* ── Телефон ───────────────────────────────────────────────────────────── */

@media (max-width: 640px) {
  .card { border-radius: var(--radius); }

  ::-webkit-scrollbar { width: 4px; height: 4px; }
}


/* ── Уважение к настройке «меньше движения» ────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}