/* src/static/css/app.css — единственная таблица стилей; без фреймворков и сборки.
   Канон — `doc/01-design.md` §6.1, «Пересмотр 2026-09-24» (M3-16): каркас и плотность как в
   We7 v1, доступность — там, где она не меняет облик. Базовый текст 16 px, цели нажатия от
   44 px, текст ≥ 4.5:1 (AA; до 2026-09-24 здесь было 7:1), крупный текст и иконки ≥ 3:1,
   главная кнопка против фона и кольцо фокуса ≥ 3:1 — всё это `tests/test_contrast.py`.
   Обводки у кнопок нет (тональные пилюли v1), поэтому и требования 3:1 к ней больше нет.
   Ничего только цветом, видимый фокус, зум 200 % без горизонтальной прокрутки.

   Внешний вид (M3-11, 2026-09-23) — из We7 v1: четыре шкалы цвета скопированы из
   `web/tailwind.config.ts` v1 точно; семантические роли ниже — navy для ссылок, главной
   кнопки и знака; тёплая — «предложено», «сегодня», задания; зелёная — подтверждено и
   перенесено-взято; красная — ошибки, отмена, отклонено. Золото `--warm-400` и красный
   `--accent-500` в тексте не живут (1,7:1 и 3,9:1 на белом): только в знаке, полосах и
   заливках. Каркас (M3-16) — `sidebar.tsx`, `mobile-nav.tsx`, `(app)/layout.tsx` v1.

   Шрифт. Канон обещает DM Sans с кириллицей; 2026-09-23 проверено: сборка Google Fonts
   v4.004 (486 глифов) и upstream-репозиторий googlefonts/dm-fonts кириллицы не содержат —
   запрос подмножества `text=Привет` вернул 0 кириллических глифов. Подключать латиницу
   без кириллицы — значит набирать «16:00 Пианино» двумя шрифтами. Остаётся `system-ui`. */

:root {
  color-scheme: light dark;

  /* Шкалы v1 (значения точные). */
  --brand-50: #f0f3f6;
  --brand-100: #dae1e8;
  --brand-200: #b5c3d1;
  --brand-300: #8da3b6;
  --brand-400: #627d93;
  --brand-500: #3d5a73;
  --brand-600: #2C3E50;
  --brand-700: #243444;
  --brand-800: #1c2936;
  --brand-900: #151f29;
  --brand-950: #0d141c;

  --accent-50: #fef2f1;
  --accent-100: #fee2df;
  --accent-200: #fec9c5;
  --accent-300: #fca39c;
  --accent-400: #f87168;
  --accent-500: #E74C3C;
  --accent-600: #d43425;
  --accent-700: #b22a1d;
  --accent-800: #93261c;
  --accent-900: #7a261e;
  --accent-950: #430f0a;

  --warm-50: #fffdf0;
  --warm-100: #fef9d7;
  --warm-200: #fef1a8;
  --warm-300: #fde56e;
  --warm-400: #F1C40F;
  --warm-500: #d9a907;
  --warm-600: #b07f04;
  --warm-700: #865f08;
  --warm-800: #6f4e0e;
  --warm-900: #5c4112;
  --warm-950: #362308;

  --surface-0: #ffffff;
  --surface-50: #f8f9fb;
  --surface-100: #f1f3f5;
  --surface-200: #e9ecef;
  --surface-300: #dee2e6;
  --surface-400: #ced4da;
  --surface-500: #adb5bd;
  --surface-600: #868e96;
  --surface-700: #495057;
  --surface-800: #343a40;
  --surface-900: #212529;

  /* Зелёная шкала: в `tailwind.config.ts` v1 своей нет — v1 брал `green-*` из умолчаний
     Tailwind 3 (`bg-green-50 text-green-700`); это они. */
  --green-50: #f0fdf4;
  --green-100: #dcfce7;
  --green-200: #bbf7d0;
  --green-300: #86efac;
  --green-400: #4ade80;
  --green-500: #22c55e;
  --green-600: #16a34a;
  --green-700: #15803d;
  --green-800: #166534;
  --green-900: #14532d;
  --green-950: #052e16;

  /* Роли — светлая схема. Текст берёт тёмные ступени: `-900` у тёплой и зелёной, `-900`/`-950`
     у красной (`accent-800` на `accent-200` даёт 5,7:1 — не проходит), `-800` у navy. */
  --bg: var(--surface-50);
  --card: var(--surface-0);
  --card-border: var(--surface-200);
  --shadow: 0 1px 3px rgb(0 0 0 / 0.04), 0 1px 2px -1px rgb(0 0 0 / 0.04);
  --text: var(--surface-900);
  --muted: var(--surface-700);
  /* Подписи второго плана: логин, капсовые подзаголовки секций. В v1 — `surface-500`, а это
     2,1:1 на белом; `#646c74` — самый светлый серый той же гаммы, что держит 4.5:1 и на
     `surface-100` наложений (4,8:1). Своей ступени в шкале у него нет, поэтому роль. */
  --subtle: #646c74;
  --link: var(--brand-600);
  --focus: var(--brand-600);
  --border: var(--brand-400);
  /* Рамка поля — тонкая скруглённая, как `.input` v1, но `surface-600`, а не `surface-300`:
     та давала 1,3:1 к белой карточке, ниже WCAG 1.4.11 для границы поля (2026-09-27,
     замечание оркестратора M3-16); `surface-600` — 3,3:1. Тон при фокусе — `brand-500`. */
  --field-border: var(--surface-600);
  --field-focus: var(--brand-500);
  --field-ring: rgb(61 90 115 / 0.25);
  --header-bg: var(--surface-0);
  --header-glass: rgb(255 255 255 / 0.85);
  --header-text: var(--brand-600);
  --tab-bg: var(--surface-0);
  --tab-text: var(--brand-600);
  --tab-active-text: var(--brand-800);
  --tab-active-bar: var(--brand-600);
  /* Боковое меню (≥ 64 rem): белое, пункт — navy, активный — заливка `brand-50` и `brand-800`,
     наведение — `surface-50` (`sidebar.tsx` v1). */
  --nav-bg: var(--surface-0);
  --nav-text: var(--brand-600);
  --nav-hover: var(--surface-50);
  --nav-active-bg: var(--brand-50);
  --nav-active-text: var(--brand-800);
  --avatar-bg: var(--brand-100);
  --avatar-text: var(--brand-700);
  /* Второстепенная кнопка — тональная пилюля без обводки, как «Привязать» в v1. */
  --button-bg: var(--brand-50);
  --button-text: var(--brand-800);
  --button-hover: var(--brand-100);
  --button-active: var(--brand-200);
  --primary-bg: var(--brand-600);
  --primary-text: var(--surface-0);
  --primary-hover: var(--brand-700);
  --primary-active: var(--brand-800);
  --yes-bg: var(--green-50);
  --yes-hover: var(--green-100);
  --yes-active: var(--green-200);
  --yes-text: var(--green-900);
  --no-bg: var(--accent-50);
  --no-hover: var(--accent-100);
  --no-active: var(--accent-200);
  --no-text: var(--accent-950);
  --warm-bg: var(--warm-50);
  --warm-hover: var(--warm-100);
  --warm-active: var(--warm-200);
  --warm-text: var(--warm-900);
  --proposed-bg: var(--warm-100);
  --proposed-text: var(--warm-900);
  --proposed-border: var(--warm-700);
  --today-bg: var(--warm-100);
  --today-text: var(--warm-900);
  --moved-bg: var(--green-100);
  --moved-text: var(--green-900);
  --moved-border: var(--green-700);
  --error-bg: var(--accent-50);
  --error-text: var(--accent-900);
  --error-border: var(--accent-600);
  --bring-bg: var(--warm-100);
  --bring-text: var(--warm-900);
  --bring-border: var(--warm-700);
  --overlay-bg: var(--surface-100);
  --overlay-text: var(--surface-900);
  --overlay-border: var(--surface-300);
  --previous-bg: var(--surface-100);
  --previous-text: var(--surface-700);
  --replacement-bg: var(--brand-100);
  --replacement-text: var(--brand-800);
  --cancelled-bg: var(--surface-100);
  --cancelled-text: var(--surface-700);
  --note-bg: var(--brand-50);
  --note-text: var(--brand-800);
  --note-border: var(--brand-200);
  --kind-school-bg: var(--brand-50);
  --kind-school-icon: var(--brand-700);
  --kind-section-bg: var(--warm-50);
  --kind-section-icon: var(--warm-800);
  --kind-work-bg: var(--surface-100);
  --kind-work-icon: var(--surface-700);
  --kind-health-bg: var(--accent-50);
  --kind-health-icon: var(--accent-800);
  --kind-other-bg: var(--surface-100);
  --kind-other-icon: var(--surface-700);
  --kind-homework-bg: var(--warm-50);
  --kind-homework-icon: var(--warm-800);
  --kind-source-bg: var(--brand-50);
  --kind-source-icon: var(--brand-700);
  --kind-person-bg: var(--brand-50);
  --kind-person-icon: var(--brand-700);
}

/* Тёмная схема — те же роли на тёмных ступенях (в v1 её не было). Тональные кнопки: фон
   `-950`, наведение `-900`, нажатие `-800`, текст белый; navy-пилюля — `brand-800` с
   `brand-100`. */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: var(--brand-950);
    --card: var(--surface-900);
    --card-border: var(--surface-700);
    --shadow: 0 1px 3px rgb(0 0 0 / 0.4), 0 1px 2px -1px rgb(0 0 0 / 0.4);
    --text: var(--surface-100);
    --muted: var(--surface-400);
    --subtle: var(--surface-500);
    --link: var(--brand-100); /* `brand-200` на `--overlay-bg` давала 6,4:1 (ревью 2026-09-23) */
    --focus: var(--warm-400);
    --border: var(--brand-300);
    --field-border: var(--surface-600);
    --field-focus: var(--brand-300);
    --field-ring: rgb(141 163 182 / 0.35);
    --header-bg: var(--surface-900);
    --header-glass: rgb(33 37 41 / 0.92);
    --header-text: var(--brand-100);
    --tab-bg: var(--surface-900);
    --tab-text: var(--brand-100);
    --tab-active-text: var(--brand-50);
    --tab-active-bar: var(--warm-400);
    --nav-bg: var(--surface-900);
    --nav-text: var(--brand-100);
    --nav-hover: var(--surface-800);
    --nav-active-bg: var(--brand-800);
    --nav-active-text: var(--brand-50);
    --avatar-bg: var(--brand-800);
    --avatar-text: var(--brand-100);
    --button-bg: var(--brand-800);
    --button-text: var(--brand-100);
    --button-hover: var(--brand-700);
    --button-active: var(--brand-600);
    --primary-bg: var(--brand-200);
    --primary-text: var(--brand-900);
    --primary-hover: var(--brand-100);
    --primary-active: var(--brand-50);
    --yes-bg: var(--green-950);
    --yes-hover: var(--green-900);
    --yes-active: var(--green-800);
    --yes-text: var(--surface-0);
    --no-bg: var(--accent-950);
    --no-hover: var(--accent-900);
    --no-active: var(--accent-800);
    --no-text: var(--surface-0);
    --warm-bg: var(--warm-950);
    --warm-hover: var(--warm-900);
    --warm-active: var(--warm-800);
    --warm-text: var(--surface-0);
    --proposed-bg: var(--warm-950);
    --proposed-text: var(--warm-200);
    --proposed-border: var(--warm-400);
    --today-bg: var(--warm-950);
    --today-text: var(--warm-200);
    --moved-bg: var(--green-950);
    --moved-text: var(--green-200);
    --moved-border: var(--green-400);
    --error-bg: var(--accent-950);
    --error-text: var(--accent-200);
    --error-border: var(--accent-400);
    --bring-bg: var(--warm-950);
    --bring-text: var(--warm-200);
    --bring-border: var(--warm-400);
    --overlay-bg: var(--surface-800);
    --overlay-text: var(--surface-100);
    --overlay-border: var(--surface-600);
    --previous-bg: var(--surface-800);
    --previous-text: var(--surface-300);
    --replacement-bg: var(--brand-800);
    --replacement-text: var(--brand-100);
    --cancelled-bg: var(--surface-800);
    --cancelled-text: var(--surface-300);
    --note-bg: var(--brand-900);
    --note-text: var(--brand-100);
    --note-border: var(--brand-700);
    --kind-school-bg: var(--brand-800);
    --kind-school-icon: var(--brand-100);
    --kind-section-bg: var(--warm-950);
    --kind-section-icon: var(--warm-200);
    --kind-work-bg: var(--surface-800);
    --kind-work-icon: var(--surface-300);
    --kind-health-bg: var(--accent-950);
    --kind-health-icon: var(--accent-200);
    --kind-other-bg: var(--surface-800);
    --kind-other-icon: var(--surface-300);
    --kind-homework-bg: var(--warm-950);
    --kind-homework-icon: var(--warm-200);
    --kind-source-bg: var(--brand-800);
    --kind-source-icon: var(--brand-100);
    --kind-person-bg: var(--brand-800);
    --kind-person-icon: var(--brand-100);
  }
}

* {
  box-sizing: border-box;
}

html {
  /* 16 px, как в v1 (M3-16). До 2026-09-24 здесь стояло 112,5 % — 18 px ради старших;
     владелец сравнил скриншоты и выбрал плотность v1, крупность держит зум браузера. */
  font-size: 100%;
  -webkit-text-size-adjust: 100%;
  /* Шапка и панель липкие и перекрывают контент. 2026-09-23 (ревью доступности): без
     этого ссылка у нижнего края получала фокус под панелью, а якорь `#found-lessons`
     уходил под шапку. Снизу с запасом: панель при зуме выше 4,5 rem. */
  scroll-padding-top: 4rem;
  /* 10 rem, не 8 (2026-09-27, проверка доступности M3-16): пять вкладок при зуме 200 %
     переносятся в три строки, и 8 rem оставляли «Выйти» под панелью. */
  scroll-padding-bottom: calc(10rem + env(safe-area-inset-bottom));
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  line-height: 1.5;
  overflow-wrap: anywhere;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

a {
  color: var(--link);
  text-underline-offset: 0.15em;
  transition: color 150ms;
}

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
}

h1 {
  font-size: 1.5rem;
  line-height: 1.25;
  font-weight: 500;
  margin: 0 0 1.25rem;
}

/* Заголовок раздела с иконкой слева — как «Сегодня» в v1: иконка 24, navy, зазор 0,75 rem. */
.page-title {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.page-title > .icon {
  color: var(--header-text);
}

/* Подзаголовок секции — капс, 0,875 rem, 600, разрядка; `--subtle` держит 4.5:1. */
.section-heading {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.875rem;
  line-height: 1.3;
  font-weight: 600;
  color: var(--subtle);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0 0 0.75rem;
}

h2 {
  font-size: 1.25rem;
  line-height: 1.3;
  font-weight: 500;
  margin: 0 0 0.5rem;
}

p {
  margin: 0 0 0.75rem;
}

/* Появление страницы — 0,3 s; переходы цвета 150 ms. `prefers-reduced-motion` гасит всё
   (в v1 этого не было). */
@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }
}

/* Ссылка «к содержимому»: невидима, пока не получит фокус с клавиатуры. */
.skip-link {
  position: absolute;
  left: 1rem;
  top: -100vh;
  padding: 0.75rem 1rem;
  background: var(--card);
  color: var(--link);
  border: 2px solid var(--link);
  border-radius: 0.75rem;
  z-index: 30;
}

.skip-link:focus {
  top: 0.5rem;
}

/* Скрыто глазами, видно чтецу: разделители, подписи без места на экране. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Спрайт иконок: один `<svg>` в `base.html`, скрыт; `<use>` берёт символы по id.
   `display: none` безопасен, пока в спрайте только `<symbol>` с путями: градиенты, маски,
   `clipPath` и фильтры из скрытого svg в WebKit не применяются (инвариант в `test_look.py`). */
.sprite {
  display: none;
}

/* Иконки — lucide, 24-сетка, декоративные (`aria-hidden`), смысл несёт слово рядом.
   Три размера: 24 в заголовке, 20 в навигации и квадрате вида, 16 в пилюлях. */
.icon {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -0.2em;
}

.icon--20 {
  width: 20px;
  height: 20px;
}

.icon--16 {
  width: 16px;
  height: 16px;
  vertical-align: -0.12em;
}

/* Знак: три круга по восходящей дуге; охранное поле — диаметр малого круга. */
.mark {
  flex-shrink: 0;
  display: inline-block;
  vertical-align: middle;
}

/* Вордмарк «w7»: «w» обычным, «7» жирным и золотым — единственный типографический жест. */
.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin: 1rem 0 0.5rem;
  font-size: 1.75rem;
  line-height: 1;
  color: var(--header-text);
}

.wordmark b {
  font-weight: 800;
  color: var(--warm-400);
}

/* Каркас (M3-16, `doc/01-design.md` §6.1, «Пересмотр 2026-09-24»). Уже 64 rem — шапка со
   знаком и названием семьи, липкая сверху, и нижняя панель из пяти вкладок; шире — боковое
   меню 16 rem слева, шапки нет (в v1 там колокольчик, у нас уведомлений нет). Навигация —
   одна `<nav class="app-nav">` на обе раскладки; пункты `--wide` и `--narrow` скрываются
   `display: none`, и чтец видит ровно одну раскладку.

   Уже 64 rem шапка и панель лежат в обёртке `.site-top` с `display: contents` и становятся
   двумя элементами flex-колонки `body` с разными липкими краями: панель в потоке, `sticky;
   bottom: 0`, визуально последняя через `order`; DOM и порядок Tab не меняются (2026-09-23,
   замена `fixed` с константным отступом, который панель при зуме перерастала). */
.site-top {
  display: contents;
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--header-glass);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--card-border);
  min-height: 56px;
  display: flex;
  align-items: center;
  padding: 0.5rem 1rem;
}

/* Низкое окно — телефон при зуме 200 % (195×422 css px) или в альбомной ориентации: шапка
   уходит в поток. Липкие шапка и панель вместе съедали больше половины окна, и содержимое
   читалось через щель (проверка доступности 2026-09-27). Панель остаётся липкой: без неё
   разделы не найти, а шапка лишь повторяет название семьи. */
@media (max-height: 30rem) {
  .site-header {
    position: static;
  }
}

/* Название семьи — одной строкой с многоточием: страницу называет `<h1>`, не шапка; при
   зуме 200 % длинное название иначе съедало окно (шапка ≤ 64 px при любом названии). */
.site-title {
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  max-width: 100%;
  font-weight: 600;
  font-size: 1.125rem;
  color: var(--header-text);
}

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

.site-title b,
.app-nav__brand b {
  font-weight: 800;
  color: var(--warm-400);
}

/* Нижняя панель: пять вкладок с иконкой 20 и подписью (`mobile-nav.tsx` v1: h-16, подпись
   под иконкой). Подпись 0,8125 rem — 13 px при 16: пять вкладок на 320 px — это 64 px на
   вкладку, и «Задания» крупнее в неё одной строкой не входит; иконка и `<h1>` страницы
   дублируют слово. Вкладки переносятся: на 160 css px (зум 200 %) — строки по три.
   Активная — цвет, вес, полоса сверху и `aria-current`. */
.app-nav {
  position: sticky;
  bottom: 0;
  order: 99;
  z-index: 20;
  background: var(--tab-bg);
  border-top: 1px solid var(--card-border);
  padding-bottom: env(safe-area-inset-bottom);
}

.app-nav__brand,
.app-nav .user-card,
.app-nav__item--wide {
  display: none;
}

.app-nav ul {
  list-style: none;
  margin: 0 auto;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  max-width: 32rem;
}

.app-nav li {
  flex: 1 1 3.25rem;
  min-width: 0;
}

.app-nav li a {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.15rem;
  min-height: 56px;
  min-width: 0;
  padding: 0.35rem 0.125rem;
  color: var(--tab-text);
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1.2;
  text-decoration: none;
  border-top: 3px solid transparent;
  transition: color 150ms, background-color 150ms;
}

.app-nav li a span {
  white-space: nowrap;
}

/* Уже 11 rem (зум 200 % на 320 px даёт 160 css px) три вкладки в строке — это 53 px, а
   «Найдено» одной строкой шире; здесь по две в строке, и вкладка ниже, чтобы три строки
   панели не съедали окно (замер 2026-09-27: 3 × 48 = 144 px из 568). Запрос контейнера, а
   не `@media`: CSS-`zoom` страницы (им зум проверяют `test_week_zoom`, `test_qr_zoom`)
   сужает раскладку, не трогая ширину окна, и медиазапрос его не видит. */
.app-nav {
  container-type: inline-size;
}

@container (max-width: 11rem) {
  .app-nav li {
    flex-basis: 4.5rem;
  }

  .app-nav li a {
    min-height: 48px;
  }
}

.app-nav li a[aria-current="page"] {
  color: var(--tab-active-text);
  font-weight: 600;
  border-top-color: var(--tab-active-bar);
}

/* Подпись активной вкладки подчёркнута всегда: цвет, вес и тонкая полоса сверху при зуме и
   в ярком свете читались одинаково у всех пяти (проверка доступности 2026-09-27). */
.app-nav li a[aria-current="page"] span {
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.2em;
}

/* Кольцо фокуса глубже полосы активной вкладки — иначе они совпадали. */
.app-nav li a:focus-visible {
  outline-offset: -6px;
}

/* В низком окне (зум 200 % на телефоне: 160×284 css px) вкладки плотнее — 44 px, ровно
   цель нажатия: три строки по 52 px занимали 157 px из 284, больше половины окна
   (проверка доступности 2026-09-27). Стоит после запроса контейнера и перебивает его 48 px. */
@media (max-height: 30rem) {
  .app-nav li a {
    min-height: 44px;
    padding-block: 0.125rem;
    gap: 0;
  }
}

/* Без `backdrop-filter` стекло было бы просто прозрачным — тогда шапка непрозрачная. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header {
    background: var(--header-bg);
  }
}

/* Контент: до 56 rem, поля 1 rem по бокам и 1,5 rem сверху (`max-w-4xl px-4 py-6` v1). */
.site-main {
  width: 100%;
  max-width: 56rem;
  margin: 0 auto;
  padding: 1.5rem 1rem 2rem;
  flex: 1;
  animation: fade-in 0.3s ease-out;
}

.site-main:focus {
  outline: none;
}

/* Кружок с первой буквой имени — `bg-brand-100`, `text-brand-700`, 600 (v1). */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 999px;
  background: var(--avatar-bg);
  color: var(--avatar-text);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1;
}

/* Карточка пользователя внизу бокового меню: кружок, имя, логин; ведёт в «Настройки». */
.user-card {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 44px;
  padding: 0.375rem 0.5rem;
  margin: 0 -0.5rem;
  border-radius: 0.75rem;
  color: var(--text);
  text-decoration: none;
  transition: background-color 150ms;
}

.user-card:hover {
  background: var(--nav-hover);
}

.user-card__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.user-card__name,
.user-card__login {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.user-card__name {
  font-size: 0.875rem;
  font-weight: 500;
}

.user-card__login {
  font-size: 0.8125rem;
  color: var(--subtle);
}

/* Шире 64 rem: боковое меню 16 rem слева, белое, с правой волосяной рамкой; вверху
   вордмарк (h-16, px-6), ниже разделы (px-3 py-4, пункт px-3 py-2.5, rounded, 500), внизу —
   карточка пользователя за волосяной рамкой (p-4). Меню липкое на всю высоту окна и
   прокручивается само, если окно ниже списка. Контент — справа, `px-6 py-6`. */
@media (min-width: 64rem) {
  html {
    scroll-padding-top: 1rem;
    scroll-padding-bottom: 1rem;
  }

  /* Колонка меню нарисована и фоном `body`: меню липкое высотой в окно, а на длинной
     странице под ним иначе обрывались бы белый фон и правая рамка — так на снимке всей
     страницы и при печати (2026-09-27, скриншот недели 1280). */
  body.shell--nav {
    display: grid;
    grid-template-columns: 16rem minmax(0, 1fr);
    align-items: start;
    background: linear-gradient(
      to right,
      var(--nav-bg) 0 calc(16rem - 1px),
      var(--card-border) calc(16rem - 1px) 16rem,
      var(--bg) 16rem
    );
  }

  .shell--nav .site-header {
    display: none;
  }

  .shell--nav .app-nav {
    grid-column: 1;
    grid-row: 1;
    order: 0;
    position: sticky;
    top: 0;
    bottom: auto;
    height: 100vh;
    height: 100dvh;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    background: var(--nav-bg);
    border-top: 0;
    border-right: 1px solid var(--card-border);
    padding: 0;
  }

  .shell--nav .site-main {
    grid-column: 2;
    grid-row: 1;
    padding: 1.5rem;
  }

  .app-nav__brand {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 4rem;
    margin: 0;
    padding: 0 1.5rem;
    border-bottom: 1px solid var(--surface-100);
    font-size: 1.5rem;
    line-height: 1;
    color: var(--header-text);
  }

  .app-nav ul {
    flex: 1;
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 0.25rem;
    max-width: none;
    margin: 0;
    padding: 1rem 0.75rem;
  }

  .app-nav li {
    flex: 0 0 auto;
  }

  .app-nav__item--wide {
    display: block;
  }

  .app-nav__item--narrow {
    display: none;
  }

  .app-nav li a {
    flex-direction: row;
    justify-content: flex-start;
    gap: 0.75rem;
    min-height: 44px;
    padding: 0.625rem 0.75rem;
    border-top: 0;
    border-radius: 0.75rem;
    color: var(--nav-text);
    font-size: 0.9375rem;
  }

  .app-nav li a:hover {
    background: var(--nav-hover);
  }

  /* Активный пункт — заливка и полоса слева 3 px: одна заливка `brand-50` против белого —
     1,1:1, то есть признак почти только цветом (проверка доступности 2026-09-27). */
  .app-nav li a[aria-current="page"] {
    background: var(--nav-active-bg);
    color: var(--nav-active-text);
    box-shadow: inset 3px 0 0 var(--tab-active-bar);
  }

  .app-nav li a[aria-current="page"] span {
    text-decoration: none;
  }

  .app-nav li a:focus-visible {
    outline-offset: 2px;
  }

  .app-nav .user-card {
    display: flex;
    margin: 0;
    border-top: 1px solid var(--surface-100);
    border-radius: 0;
    padding: 1rem;
  }

  /* Кольцо фокуса внутрь: карточка во всю ширину меню, а меню с `overflow-y: auto`
     обрезает всё за своим краем — наружное кольцо было видно только сверху (проверка
     доступности 2026-09-27). */
  .app-nav .user-card:focus-visible {
    outline-offset: -5px;
  }
}

/* Вход (M3-16, `login/page.tsx` v1): без шапки и навигации, фон `surface-50`, колонка
   24 rem по центру окна; вордмарк крупно — знак 44 px и «w7» 2,25 rem. */
.shell--login .site-main {
  max-width: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 2rem 1rem;
}

.login {
  width: 100%;
  max-width: 24rem;
}

.wordmark--large {
  display: flex;
  justify-content: center;
  gap: 0.75rem;
  margin: 0 0 0.75rem;
  font-size: 2.25rem;
}

.login__subtitle {
  font-size: 0.9375rem;
  font-weight: 400;
  color: var(--subtle);
  text-align: center;
  margin: 0 0 2rem;
}

.login-form {
  padding: 1.25rem;
}

.login-form .field {
  margin-bottom: 1.25rem;
}

.login-form .field label {
  font-weight: 400;
  color: var(--muted);
}

.login-form .button--primary {
  margin-top: 0.25rem;
  font-weight: 400;
}

.login__language {
  margin: 1.5rem 0 0;
}

.login__language .choice {
  justify-content: center;
  gap: 0.25rem;
}

.login__language .button {
  padding-inline: 0.75rem;
  font-size: 0.875rem;
  font-weight: 400;
}

.login__language .button[aria-current="true"] {
  background: var(--button-bg);
  color: var(--button-text);
}

/* Карточка — атом интерфейса: белый прямоугольник, радиус 1,25 rem, мягкая тень, волосяная
   рамка `surface-200`. Волосяная рамка допустима только у карточек: контролы держат ≥ 3:1. */
.card {
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: 1.25rem;
  box-shadow: var(--shadow);
  padding: 1rem;
}

/* Кнопки и ссылки-кнопки (M3-16): тональная пилюля v1 — фон `brand-50`, текст `brand-800`,
   наведение `-100`, нажатие `-200`, без обводки; радиус 0,75 rem, 500, цель не меньше 44 px.
   Главная — navy залитая; `--yes`/`--no`/`--warm` — тональные зелёная, красная, тёплая.
   Рамка 2 px прозрачная, а не `0`: в контрастной теме Windows прозрачная рамка рисуется
   системным цветом, и кнопка остаётся очерченной. Действия никогда не прячутся до наведения. */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 44px;
  min-width: 44px;
  padding: 0.5rem 1.125rem;
  font: inherit;
  font-weight: 500;
  line-height: 1.3;
  color: var(--button-text);
  background: var(--button-bg);
  border: 2px solid transparent;
  border-radius: 0.75rem;
  text-decoration: none;
  cursor: pointer;
  hyphens: auto;
  transition: background-color 150ms, color 150ms;
}

@media (max-width: 20rem) {
  .button {
    padding-inline: 0.5rem;
    gap: 0.25rem;
  }
}

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

.button:active {
  background: var(--button-active);
}

/* Текущий выбор — у кнопок языка и семьи, у фильтра по ученику: заливка плюс подчёркивание. */
.button[aria-current="true"],
.student-filter a[aria-current="page"] {
  background: var(--primary-bg);
  color: var(--primary-text);
  text-decoration: underline;
  text-decoration-thickness: 2px;
}

.button--primary {
  background: var(--primary-bg);
  color: var(--primary-text);
  width: 100%;
}

.button--primary:hover {
  background: var(--primary-hover);
}

.button--primary:active {
  background: var(--primary-active);
}

/* У залитых кнопок внешнее кольцо того же цвета, что кнопка, — добавляется внутреннее. */
.button--primary:focus-visible,
.button[aria-current="true"]:focus-visible,
.student-filter a[aria-current="page"]:focus-visible {
  box-shadow: inset 0 0 0 3px var(--primary-text);
}

.button--yes {
  background: var(--yes-bg);
  color: var(--yes-text);
}

.button--yes:hover {
  background: var(--yes-hover);
}

.button--yes:active {
  background: var(--yes-active);
}

.button--no {
  background: var(--no-bg);
  color: var(--no-text);
}

.button--no:hover {
  background: var(--no-hover);
}

.button--no:active {
  background: var(--no-active);
}

.button--warm {
  background: var(--warm-bg);
  color: var(--warm-text);
}

.button--warm:hover {
  background: var(--warm-hover);
}

.button--warm:active {
  background: var(--warm-active);
}

/* На всю ширину — «Выйти» в «Настройках», как в v1. */
.button--wide {
  width: 100%;
}

/* Тихая кнопка — выбор языка под формой входа: без заливки, пока не выбрана. */
.button--quiet {
  background: transparent;
}

.choice {
  border: 0;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.choice legend {
  float: left; /* легенда в одну строку с кнопками, при этом остаётся легендой */
  padding: 0;
  margin-right: 0.5rem;
  font-weight: 600;
}

/* Формы — карточки. Форма смены пароля — той же ширины, что формы правки. */
.password-form {
  max-width: 32rem;
}

/* Подсказка Django к новому паролю — список правил; маркеры оставлены, отступ меньше. */
.field-hint ul {
  margin: 0;
  padding-left: 1.25rem;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin: 0 0 1rem;
}

.field label {
  font-weight: 400;
  color: var(--muted);
}

/* Радио и чекбоксы исключены: 48px минимальной высоты растягивали кружок радио до
   27×48 и строку выбора до 57px (замер accessibility-lead, 2026-09-19). */
/* Поле — как `.input` v1: радиус 0,75 rem, рамка `surface-300`, при фокусе рамка
   `brand-500` и кольцо; высота 44 px — цель нажатия. */
.field input:not([type="radio"]):not([type="checkbox"]),
.field select,
.field textarea {
  font: inherit;
  min-height: 44px;
  padding: 0.5rem 1rem;
  border: 1px solid var(--field-border);
  border-radius: 0.75rem;
  background: var(--card);
  color: var(--text);
  width: 100%;
  transition: border-color 150ms, box-shadow 150ms;
}

.field input:not([type="radio"]):not([type="checkbox"]):focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--field-focus);
  box-shadow: 0 0 0 2px var(--field-focus);
  outline: none;
}

.field input::placeholder,
.field textarea::placeholder {
  color: var(--subtle);
  opacity: 1;
}

/* Ошибка — слово, иконка и красная рамка; цвет третьим. */
.field-error,
.form-error {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  color: var(--error-text);
  background: var(--error-bg);
  border: 2px solid var(--error-border);
  border-radius: 0.75rem;
  padding: 0.5rem 0.75rem;
}

.field-error .icon,
.form-error .icon {
  margin-top: 0.2em;
}

.form-error {
  margin: 0 0 1rem;
}

.form-error p {
  margin: 0;
}

/* Сводка ошибок формы (M3-12): заголовок со счётом, ниже ссылки на поля. Фокус на неё
   ставит `app.js` ради диктора и клавиатуры, и кольцо фокуса видно — в отличие от
   `.site-main:focus`: старшим и тем, кто идёт с клавиатуры, после перезагрузки нужен
   ориентир, где сейчас фокус, иначе первый Tab уводит неизвестно откуда (проверка
   доступности M3-12, 2026-09-23). */
.form-error:focus {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
}

.form-error-title {
  font-size: 1.1rem;
  font-weight: 600;
  line-height: 1.35;
  margin: 0 0 0.25rem;
}

/* Ссылки без маркеров списка и во всю ширину колонки: при зуме 200 % на 390 px маркер и
   отступ съедали колонку до 61 px, и слова рвались посреди (замер 2026-09-23). `role="list"`
   в шаблоне — потому что Safari с `list-style: none` перестаёт объявлять список. */
.form-error ul {
  margin: 0.5rem 0 0;
  padding-left: 0;
  list-style: none;
}

.form-error li + li {
  margin-top: 0.5rem;
}

.form-error a {
  color: inherit;
  display: block;
  min-height: 44px;
  padding: 0.35rem 0;
  text-decoration-thickness: 2px;
}

/* Уже 30 rem иконка не держит свою колонку во flex, а обтекается текстом: иначе иконка с
   зазором отнимали у текста ещё 30 px из тех же 61 (2026-09-23). */
@media (max-width: 30rem) {
  .form-error {
    display: block;
  }

  .form-error > .icon {
    float: left;
    margin: 0.2em 0.5rem 0 0;
  }
}

/* Переход по ссылке сводки ставит поле под шапку с учётом `scroll-padding-top` у `html`, но
   метка и `legend` стоят над инпутом и уходили под шапку: замер 2026-09-23 — метка на
   30–66 px под шапкой 56 px, человек видел поле без подписи. Запас сверху — на метку. */
.field :is(input, select, textarea) {
  scroll-margin-top: 3.5rem;
}

/* Неделя */
.week-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin: 0 0 1rem;
}

/* «Прошлая / Сегодня / Следующая» — пилюли-фильтры v1 в строку, по ширине слова. */
.week-nav .button {
  flex: 0 1 auto;
  border-radius: 999px;
  padding-inline: 0.5rem;
  gap: 0.375rem;
  font-size: 0.875rem;
}

/* Подсказка — не карточка: скруглённая рамка на светлом у пользователей v1 читалась как
   поле ввода, в него пытались печатать. Здесь `brand-50`, рамка `brand-200`, иконка «i». */
.note {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  background: var(--note-bg);
  color: var(--note-text);
  border: 1px solid var(--note-border);
  border-left-width: 4px;
  border-radius: 0.5rem;
  padding: 0.75rem 1rem;
  margin: 0 0 1rem;
}

.note > .icon {
  margin-top: 0.2em;
}

.note p {
  margin: 0 0 0.5rem;
}

.note p:last-child,
.note .button {
  margin-bottom: 0;
}

.note a {
  color: inherit;
}

/* Пустое состояние — осмысленная фраза с иконкой и следующим шагом, не пустая страница. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 2rem 1rem;
  margin: 0 0 1.25rem;
}

.empty-state__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border-radius: 1rem;
  background: var(--note-bg);
  color: var(--note-text);
  margin: 0 0 1rem;
}

.empty-state__title {
  font-size: 1.25rem;
  font-weight: 500;
  margin: 0 0 0.35rem;
}

.empty-state__text {
  color: var(--muted);
  max-width: 28rem;
  margin: 0 0 1rem;
}

.empty-state .button {
  width: auto;
}

/* Даты остаются в хронологическом DOM-порядке. Ширина самой колонки, а не окна,
   определяет число колонок: зум и боковое меню не оставляют узкие карточки рядом. */
.week-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
  gap: 1.5rem;
  align-items: start;
}

.week-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1.5rem;
}

.week-toolbar .week-nav,
.week-toolbar .actions {
  margin: 0;
}

.day {
  min-width: 0;
  margin: 0;
}

.day > h2 {
  font-size: 1rem;
  padding-bottom: 0.75rem;
  margin-bottom: 0.75rem;
  border-bottom: 1px solid var(--card-border);
}

.week-grid .entry,
.week-grid .homework {
  padding: 0.875rem;
  gap: 0.5rem;
}

.week-grid .entry-meta,
.week-grid .people {
  font-size: 0.875rem;
}

.week-grid .entry-actions .button {
  font-size: 0.875rem;
}

.week-grid .empty {
  padding: 0.25rem 0 0.75rem;
  font-size: 0.875rem;
}

.day--today h2 {
  text-decoration: underline;
  text-decoration-thickness: 3px;
  text-underline-offset: 0.2em;
}

.today-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 1rem;
  font-weight: 500;
  padding: 0.1rem 0.7rem;
  border-radius: 999px;
  background: var(--today-bg);
  color: var(--today-text);
}

.empty {
  color: var(--muted);
  margin: 0 0 0.5rem;
}

.entries {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* Занятие, задание, источник — карточка с квадратом вида слева: 44×44, пастельная заливка
   и иконка по виду сущности. Иерархия внутри: мета (время, вид, флаги) → заголовок →
   подробности → люди → действия. */
.entry,
.homework,
.source {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: flex-start;
}

.kind {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 0.75rem;
  background: var(--kind-other-bg);
  color: var(--kind-other-icon);
}

.kind--school {
  background: var(--kind-school-bg);
  color: var(--kind-school-icon);
}

.kind--section {
  background: var(--kind-section-bg);
  color: var(--kind-section-icon);
}

.kind--work {
  background: var(--kind-work-bg);
  color: var(--kind-work-icon);
}

.kind--health {
  background: var(--kind-health-bg);
  color: var(--kind-health-icon);
}

.kind--homework {
  background: var(--kind-homework-bg);
  color: var(--kind-homework-icon);
}

.kind--source {
  background: var(--kind-source-bg);
  color: var(--kind-source-icon);
}

.kind--person {
  background: var(--kind-person-bg);
  color: var(--kind-person-icon);
}

/* Тело карточки — рядом с квадратом, пока есть 8 rem; уже (зум 200% на 320 px даёт окно в
   160 px, а карточка внутри наложения — 57 px) — переносится под квадрат: сетка 44px + 1fr
   тут давала бы отрицательную колонку и горизонтальную прокрутку. */
.entry-body,
.homework-body,
.source-body {
  flex: 1 1 8rem;
  min-width: 0;
}

.entry-body > :last-child,
.homework-body > :last-child,
.source-body > :last-child {
  margin-bottom: 0;
}

.entry-title {
  font-weight: 500;
  font-size: 1rem;
  margin: 0 0 0.25rem;
}

.entry-meta {
  margin: 0 0 0.25rem;
  color: var(--muted);
}

.entry-meta time {
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  color: var(--text);
}

.entry-flags,
.homework-flags {
  list-style: none;
  padding: 0;
  margin: 0.25rem 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Метки: слово плюс иконка плюс цвет. Пилюля: фон `-100`, текст `-800/-900`. */
.flag {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.15rem 0.7rem;
  border-radius: 999px;
  font-weight: 500;
  font-size: 0.8125rem;
  background: var(--previous-bg);
  color: var(--previous-text);
}

.entry--proposed {
  border-style: dashed;
  border-color: var(--proposed-border);
}

.flag--proposed {
  color: var(--proposed-text);
  background: var(--proposed-bg);
}

.entry--moved {
  border-color: var(--moved-border);
}

.flag--moved,
.flag--confirmed,
.flag--done {
  color: var(--moved-text);
  background: var(--moved-bg);
}

.flag--replacement {
  color: var(--replacement-text);
  background: var(--replacement-bg);
}

.flag--cancelled,
.flag--previous {
  color: var(--cancelled-text);
  background: var(--cancelled-bg);
}

/* «с вашим участием» / «о вас» — метка словами и нейтральной пилюлей: это не состояние,
   и зелёный рядом с «предложено» читался бы как «уже принято» (доступность M3-5). */
.flag--mine {
  color: var(--text);
  background: var(--previous-bg);
}

.flag--bring {
  color: var(--bring-text);
  background: var(--bring-bg);
}

.people {
  list-style: none;
  margin: 0.25rem 0 0;
  padding: 0;
}

/* Наложение: карточка-контейнер версий на `surface-100`, внутри — обычные карточки. */
.overlay,
.homework-versions {
  display: block;
  background: var(--overlay-bg);
  border: 1px solid var(--overlay-border);
  border-radius: 1.25rem;
  box-shadow: var(--shadow);
  color: var(--overlay-text);
  padding: 0.75rem;
}

.overlay .entry,
.homework-versions .homework {
  color: var(--text);
}

.overlay-note {
  font-weight: 600;
  margin: 0 0 0.75rem;
}

.entry--replacement,
.homework--latest {
  border-color: var(--border);
}

/* Задания (M3-2): под занятиями дня и в своём разделе. Та же карточка, что у занятия;
   «принести» — пунктирная рамка и слово, «сделано» — зачёркнуто и слово. */
.homework-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.homework-head {
  margin: 0 0 0.25rem;
  font-size: 1.15rem;
}

.homework-subject {
  font-weight: 600;
}

.homework-task {
  white-space: pre-line; /* задание переносится так, как его написали */
}

s.homework-task {
  text-decoration-thickness: 2px;
}

.homework--bring {
  border-style: dashed;
  border-color: var(--bring-border);
}

.homework--done .homework-head {
  color: var(--muted);
}

.button--small {
  margin-top: 0.5rem;
  padding: 0.25rem 0.75rem;
  font-size: 1rem;
}

/* Фильтр учеников — пилюли: активная navy с белым, остальные белые с рамкой. */
.student-filter ul {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.student-filter .button {
  border-radius: 999px;
}

/* Форма задания; форма договорённости, дня и назначения (M3-4) — той же ширины. */
.homework-form,
.commitment-form,
.feedback-form {
  max-width: 32rem;
}

/* «В этот день…» у строки недели: отдельной строкой под людьми, чтобы цель не липла к
   ссылке названия. */
.entry-actions {
  margin: 0;
}

.entry-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  width: 100%;
}

.entry-footer .people {
  flex: 1 1 10rem;
  margin: 0;
  min-width: 0;
}

.entry-footer .entry-actions {
  flex: 0 0 auto;
  max-width: 100%;
}

.field textarea {
  min-height: 6rem;
}

.field-hint {
  color: var(--muted);
}

/* Радиокнопки: цель — вся строка с подписью, не меньше 44px; кружок крупный. */
.field--choice {
  border: 0;
  padding: 0;
  margin: 0 0 1rem;
}

.field--choice legend {
  font-weight: 600;
  padding: 0;
  margin: 0 0 0.35rem;
}

/* Части формы занятия — «Если по дням недели», «Если разово» (M3-15, 2026-09-23). Были
   `<h2>` перед полями, и диктор по Tab объявлял поле без части: «Время начала» звучало
   обязательным всегда (находка forms-specialist к M3-14). Теперь часть — `fieldset`, её
   подпись — `legend`: её диктор объявляет при входе в группу.
   Слева линия, а не рамка: без неё не видно, где часть кончается, и «Кто идёт» под «Когда»
   читался как поле разового (проверка форм M3-15). Рамка вокруг не годится — форма уже
   карточка, рамка внутри — карточка в карточке, а скруглённая рамка на светлом у
   пользователей v1 читалась как поле ввода (§6.1, «Подсказка»). `min-width: 0` — у
   `fieldset` по умолчанию `min-inline-size: min-content`, и самая широкая строка внутри
   распирала бы его шире экрана при зуме 200 %. `legend` без `float`: плавающая подпись
   перестаёт быть над полями и на узком экране уезжает в строку с ними. */
.form-part {
  border: 0;
  border-inline-start: 3px solid var(--border);
  padding: 0 0 0 0.75rem;
  margin: 0 0 1.25rem;
  min-width: 0;
}

.form-part > legend {
  padding: 0;
  margin: 0 0 0.5rem;
}

/* Внутри `legend` — `<h2>`, размер и вес от глобального `h2`. Одной `legend` мало: ротор
   «Заголовки» частей не видел, и до «Если разово» было ~12 свайпов (проверка форм
   2026-09-23). Свой отступ у заголовка снят — его держит `legend`. */
.form-part__heading {
  margin: 0;
}

.field--choice label {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-height: 44px;
  padding: 0.25rem 0;
  font-weight: 400;
  cursor: pointer;
}

.field--choice input[type="radio"],
.field--choice input[type="checkbox"] {
  width: 1.5rem;
  height: 1.5rem;
  margin: 0;
  flex-shrink: 0;
  accent-color: var(--primary-bg);
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin: 1rem 0 0;
}

.form-actions .button--primary {
  width: auto;
  flex: 1 1 12rem;
}

/* Сообщение после действия (M3-3): первым в <main>, `role="status"`. Рамка и слово, не
   только цвет: «Подтверждено: …» читается и в ч/б. */
.notice {
  border-left-width: 6px;
  border-left-color: var(--moved-border);
  padding: 0.75rem 1rem;
  margin: 1rem 0;
  font-weight: 600;
}

/* Фокус ставит `app.js`, и кольцо видно — как у сводки ошибок: после перехода человеку
   нужен ориентир, где сейчас фокус (M3-16). */
.notice:focus {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
}

.notice p {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin: 0;
}

.notice .icon {
  margin-top: 0.2em;
}

/* Ошибка — слово «Не получилось:», иконка и своя рамка слева; цвет третьим. */
.notice:has(.notice-error) {
  border-left-color: var(--error-border);
}

.notice-error {
  color: var(--error-text);
}

/* Название занятия в неделе — ссылка на карточку, цель не ниже 44px. */
.entry-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--link);
  text-decoration: none;
}

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

/* Карточка договорённости. */
.back {
  margin: 0.75rem 0 0;
}

.facts {
  margin: 0 0 1rem;
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.35rem 1rem;
}

.facts dt {
  font-weight: 600;
  color: var(--muted);
}

.facts dd {
  margin: 0;
}

/* Между «Да, верно» и соседней кнопкой — 1rem, не 9px: промах пальцем здесь дорог. */
.actions,
.row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin: 0 0 1rem;
}

.row-actions {
  margin: 0.5rem 0 0;
}

/* Две формы у одной строки участия («Не пойдёт» и «Снять назначение»): промежуток 1rem,
   чтобы кнопки разных действий не читались одной группой (доступность M3-4). */
.row-actions + .row-actions {
  margin-top: 1rem;
}

/* Подзаголовки секций: 1 rem, 600, капс с разрядкой; `<h2>` дней остаются крупными. */
.part-heading,
.found-subheading,
.homework-heading {
  font-size: 1rem;
  line-height: 1.3;
  font-weight: 600;
  color: var(--subtle);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0.75rem 0 0.5rem;
}

.people-rows,
.sources {
  list-style: none;
  margin: 0 0 0.75rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.person-answer {
  display: block;
  font-weight: 600;
}

.source-head {
  margin: 0 0 0.35rem;
  color: var(--muted);
}

.source-author {
  font-weight: 600;
  color: var(--text);
}

.source-text {
  margin: 0 0 0.5rem;
  padding: 0 0 0 0.75rem;
  border-left: 3px solid var(--border);
  white-space: pre-line;
}

.source-hidden {
  margin: 0 0 0.5rem;
  color: var(--muted);
  font-style: normal;
}

.feedback-link {
  margin: 1.5rem 0 0;
}

@media (max-width: 30rem) {
  .facts {
    grid-template-columns: 1fr;
    gap: 0.1rem 0;
  }

  .facts dd {
    margin: 0 0 0.5rem;
  }
}

/* «Найдено» (M3-5): разделы одним списком; строка — та же карточка недели. */
.found-section {
  margin: 0 0 1.5rem;
}

.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
}

.page-head h1 {
  margin-right: auto;
}

.entry .sources {
  margin-top: 0.5rem;
}

/* Свёртка источника: summary — блок ≥ 44 px, не flex (WebKit теряет маркер); маркер-треугольник
   нативный и не гасится — единственная видимая подсказка, что нажимается. */
.source-fold > summary {
  display: block;
  min-height: 44px;
  padding: 0.5rem 0;
  cursor: pointer;
}

.source-fold[open] > summary {
  margin-bottom: 0.35rem;
}

/* Страница-вопроса: две равные кнопки, ни одна не главная — залитое «Да» подталкивает
   к необратимому. */
.actions--question .button {
  width: auto;
  flex: 1 1 12rem;
}

.found-nav ul {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

/* «Источники» (M3-6): QR на странице. Контраст-ревью 2026-09-22, каждое число — зачем:
   400px — целиться телефоном проще, чем в 320; 85vh — при зуме 200% окно 640×360, и
   квадрат обязан помещаться в высоту целиком, иначе телефон видит половину кода;
   aspect-ratio — старый WebKit без width/height рисует svg 150px независимо от viewBox;
   белый фон — зона тишины обязана быть светлой и в тёмной теме; forced-color-adjust —
   цвета QR это содержимое, и контрастная тема Windows не должна их перекрашивать.
   Статус входа — крупно. */
.qr {
  margin: 0 0 1rem;
  display: inline-block;
  max-width: 100%;
  padding: 0; /* карточка без полей: на 320 px поле в 1rem съедает код до 246 px при минимуме 280 (`tests/sources/test_qr_zoom.py`) */
  overflow: hidden;
}

@media (min-width: 30rem) {
  .qr {
    padding: 1rem;
  }
}

.qr svg {
  display: block;
  width: min(100%, 400px, 85vh);
  height: auto;
  aspect-ratio: 1 / 1;
  background: #ffffff;
  forced-color-adjust: none;
}

/* Свёртка «Подробности» у ошибки аккаунта — та же, что у источника: summary ≥ 44 px. */

.steps {
  padding-left: 1.5rem;
  margin: 0 0 1rem;
}

.steps li {
  margin: 0 0 0.5rem;
}

.login-status {
  font-size: 1.15rem;
  font-weight: 600;
  margin: 0 0 1rem;
}

/* Шире 48 rem карточки просторнее — `p-5` v1. */
@media (min-width: 48rem) {
  .card {
    padding: 1.25rem;
  }

  .overlay,
  .homework-versions {
    padding: 1rem;
  }
}

/* Личный кабинет (M3-16): «Настройки», «Семья», «Ещё» — секции-карточки с капсовым
   подзаголовком над карточкой (`settings/page.tsx`, `family/page.tsx` v1: mb-8, mb-3). */
.settings-section {
  margin: 0 0 2rem;
}

.card-footer {
  margin: 1rem 0 0;
  padding: 1rem 0 0;
  border-top: 1px solid var(--card-border);
}

.card-footer p {
  margin: 0 0 0.75rem;
}

.card-footer > :last-child,
.card-footer p:last-child {
  margin-bottom: 0;
}

/* Профиль: кружок 3,5 rem со скруглением 1 rem (v1 `w-14 h-14 rounded-2xl`), имя, факты. */
.profile {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
}

.avatar--large {
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 1rem;
  font-size: 1.25rem;
}

.profile__text {
  flex: 1 1 10rem;
  min-width: 0;
}

.profile__name {
  font-weight: 500;
  margin: 0 0 0.25rem;
}

.profile__facts {
  margin: 0;
}

.profile__facts div {
  display: flex;
  flex-wrap: wrap;
  gap: 0 0.5rem;
}

.profile__facts dt {
  color: var(--subtle);
}

.profile__facts dt::after {
  content: ":";
}

.profile__facts dd {
  margin: 0;
}

/* Мессенджеры: строка — квадрат вида, название, пилюля статуса словом и иконкой. */
.messengers {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.messenger {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}

.messenger__body {
  flex: 1 1 8rem;
  min-width: 0;
}

.messenger__name {
  font-weight: 500;
  margin: 0 0 0.25rem;
}

.messenger__text {
  margin: 0;
  color: var(--muted);
}

.messenger .flag {
  margin: 0;
}

.flag--error {
  color: var(--error-text);
  background: var(--error-bg);
}

/* Семья: карточка с названием, участники — `member-card.tsx` v1: карточка px-5 py-3,
   кружок 2,5 rem, имя 500, пилюля «вы» `brand-50`/`brand-800`, роль словом ниже. */
.family-card {
  margin: 0 0 1.5rem;
}

.family-card__title {
  font-size: 1.125rem;
  font-weight: 500;
  margin: 0 0 0.25rem;
}

.family-card__count {
  margin: 0;
  color: var(--subtle);
}

.members {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.member {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  padding: 0.75rem 1.25rem;
}

.avatar--member {
  width: 2.5rem;
  height: 2.5rem;
  font-size: 1rem;
}

.member__body {
  flex: 1 1 8rem;
  min-width: 0;
}

.member__name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.5rem;
  font-weight: 500;
  margin: 0;
}

.member__me {
  font-size: 0.8125rem;
  font-weight: 500;
  padding: 0.05rem 0.5rem;
  border-radius: 999px;
  background: var(--note-bg);
  color: var(--note-text);
}

.member__role {
  margin: 0;
  font-size: 0.875rem;
  color: var(--subtle);
}

/* «Ещё»: две карточки-ссылки во всю ширину, цель — вся карточка. */
.link-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.link-card {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: 44px;
  color: var(--text);
  text-decoration: none;
  transition: box-shadow 150ms;
}

.link-card:hover {
  box-shadow: 0 2px 8px 0 rgb(0 0 0 / 0.06), 0 1px 2px -1px rgb(0 0 0 / 0.04);
}

.link-card__body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.link-card__title {
  font-weight: 600;
  color: var(--link);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.15em;
}

.link-card__text {
  color: var(--muted);
}

.link-card__arrow {
  color: var(--link);
}

/* Контрастная тема Windows: цвета системные, активная вкладка — Highlight плюс
   подчёркивание, метки и квадраты вида получают рамку, чтобы не слиться с фоном. */
@media (forced-colors: active) {
  .field input:not([type="radio"]):not([type="checkbox"]):focus,
  .field select:focus,
  .field textarea:focus {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }

  .app-nav li a {
    border-top-color: Canvas;
  }

  .app-nav li a[aria-current="page"] {
    border-top-color: Highlight;
    text-decoration: underline;
  }

  a.button {
    color: ButtonText;
    background: ButtonFace;
    border-color: ButtonBorder;
  }

  .flag,
  .today-badge,
  .kind {
    border: 1px solid CanvasText;
  }
}

/* Заливка активного пункта бокового меню в контрастной теме исчезает — остаётся рамка.
   Только на широкой раскладке: у вкладок нижней панели свой признак — полоса сверху. */
@media (forced-colors: active) and (min-width: 64rem) {
  .shell--nav .app-nav li a[aria-current="page"] {
    border: 2px solid Highlight;
  }
}

/* «Больше контраста»: волосяные рамки карточек и подсказок становятся рамками ≥ 3:1,
   стекло — непрозрачным, тень не нужна. */
@media (prefers-contrast: more) {
  :root {
    --card-border: var(--brand-400);
    --note-border: var(--brand-400);
    --header-glass: var(--header-bg);
    --shadow: none;
  }
}

@media (prefers-contrast: more) and (prefers-color-scheme: dark) {
  :root {
    --card-border: var(--surface-600);
    --note-border: var(--surface-600);
  }
}
