/* Luina — дизайн-система admin-UI amo-lead-router.
 *
 * Фундамент: design-токены, self-hosted Geist + Geist Mono и базовый reset.
 * Базовые значения перенесены из офлайн-прототипа handoff-пакета
 * (docs/prototypes/luina-offline.html) и README дизайн-хендоффа
 * (docs/DESIGN_HANDOFF.md). Компонентные стили экранов добавляются
 * по мере переноса разделов на новую оболочку.
 *
 * Тема только светлая — прототип и брифы тёмную тему не предусматривают.
 */

/* ── Self-hosted Geist (variable, вес 100–900). Лицензия OFL 1.1 —
   app/static/fonts/OFL.txt. Отдаётся через /static (см. app/main.py). ── */
@font-face {
  font-family: "Geist";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/Geist-Variable.woff2") format("woff2");
}
@font-face {
  font-family: "Geist Mono";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/GeistMono-Variable.woff2") format("woff2");
}

:root {
  /* Отступы — база 4px */
  /* Шкала отступов — по 4px, и в ней остаётся то, что применяется. Верхние ступени
     (32, 48, 64) встречались только рядом с внешкальными значениями вроде 34 и 60,
     где половина объявления в токенах читается хуже литерала; их убрали до тех пор,
     пока сами значения не приведут к шкале (#194). */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  /* Верхние ступени вернулись, когда на них встали живые значения: поля страницы
     «Журнала» — 32px по бокам и 64px снизу (#255). Ступени 28, 36, 48 не
     объявлены намеренно: шкала описывает нарисованное (#194). */
  --space-7: 32px;
  --space-8: 64px;

  /* Радиусы — pill для кнопок/чипов, md для контролов, lg/xl для панелей */
  /* Шкала радиусов — по тому, что реально нарисовано в макете, а не по красивому
     ряду. До #194 половина шкалы не использовалась ни разу, а самый частый радиус
     проекта (10px, 18 мест) в ней отсутствовал, и его писали числом. */
  --radius-xs: 4px;
  --radius-badge: 5px;
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-card: 10px;
  --radius-pill: 999px;

  /* Поверхности и фон */
  --bg-subtle: oklch(0.985 0 0);
  --surface: #ffffff;
  --surface-2: oklch(0.97 0 0);

  /* Текст (ink-лестница) */
  --ink: #131315;
  --ink-2: oklch(0.32 0 0);
  --ink-3: oklch(0.50 0 0);
  --ink-4: oklch(0.708 0 0);

  /* Границы */
  --line: oklch(0.922 0 0);
  --line-strong: oklch(0.85 0 0);

  /* Статусные пастели (фон + текст) */
  --err-bg: #fef2f2;
  --err-fg: #b91c1c;
  --warn-bg: #fef3c7;
  --warn-fg: #92400e;
  --info-bg: #eff6ff;
  --info-fg: #1d4ed8;

  /* Загрузка смены — цвет только как сигнал, по бизнес-порогам:
     0–70% нейтральный, 71–100% зелёный, >100% красный.
     Выполнение плана использует отдельную семантику: <100% нейтрально,
     >=100% зелёным; перевыполнение не является ошибкой. */
  --progress: #787878;
  --progress-good: #15803d;
  --progress-over: #b91c1c;
  --plan-progress-met: #15803d;

  /* Тени */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
  --shadow-pop: 0 8px 24px rgba(0, 0, 0, 0.08);

  /* Шкала размеров текста (#255). Ступени: 12 · 14 · 16 · 20 · 24 · 32 при
     базовом 1rem = 16px; ниже 12px не опускаемся — для плотных таблиц это порог
     читаемости. В шкале объявлено только то, что реально нарисовано: ступень 16
     появится, когда на неё что-то встанет.

     Единица — rem, а не px, и это не стилистика: пользовательский размер шрифта и
     текстовый зум 200% (WCAG 1.4.4) px не масштабируют, страничный зум масштабирует
     оба. Значения подобраны так, что при браузерном умолчании 16px нарисованное
     совпадает с прежним до пикселя.

     Имена по t-shirt, а не по номеру: вставка ступени между двумя существующими
     ломает нумерацию, но не ряд xs · sm · md · lg. */
  --text-xs: 0.75rem;    /* 12px */
  --text-sm: 0.875rem;   /* 14px */
  --text-lg: 1.25rem;    /* 20px */
  --text-xl: 1.5rem;     /* 24px */
  --text-2xl: 2rem;      /* 32px */

  /* Шрифты */
  --font-sans: "Geist", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* Раскладка */
  --rail-width: 84px;

  /* Z-лестница */
  /* Слои внутри таблицы: ячейка липкой колонки и её же шапка, которая обязана
     перекрывать соседние ячейки при прокрутке. Писались числами 2 и 4 мимо
     лестницы (#194); порядок между ними — единственное, что здесь важно. */
  --z-sticky-cell: 2;
  --z-sticky-head: 4;
  --z-sticky: 20;
  --z-popover: 60;
  --z-toast: 70;

  /* Кольцо фокуса для контролов, у которых собственный outline снят ради
     скруглений (#186). Шесть правил ссылались на этот токен, а объявлен он не был
     нигде: браузер молча выкидывал всё объявление, и фокус исчезал на select, поле
     сделки, «Найти», summary фильтра и периода, поле даты «Смены», пагинации и
     кнопке обновления. Lighthouse при этом показывал accessibility 100.

     Геометрия повторяет глобальное `outline: 2px solid var(--ink)` с
     `outline-offset: 2px` из shell.css: сначала 2px подложки, затем 2px чернил —
     иначе кольцо на разных экранах выглядело бы по-разному. */
  --focus-ring: 0 0 0 2px var(--surface), 0 0 0 4px var(--ink);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg-subtle);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size:var(--text-sm);
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}

/* Маркер команды — буква в ink-квадрате. Общий для «Смены», «Графика» и «Плана»:
   раньше это были четыре правила в трёх файлах с разными размерами (#123). */
/* Грейд-бейдж и заголовок команды: «График», «План» и «Журнал» рисуют их
   одинаково, а правила лежали копиями в трёх экранных файлах (#178). Селекторы
   намеренно без родителя — привязка к контейнеру в этом проекте ломалась трижды.
   Внешнего отступа компонент не задаёт: в «Журнале» бейдж лежит во flex с gap, и
   собственный margin давал там двойной зазор. Отступ ставит место применения. */
.grade {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 20px;
  height: 20px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-badge);
  color: var(--ink-2);
  font: 600 var(--text-xs) var(--font-mono);
}
.manager-name {
  display: inline-block;
  max-width: 178px;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: middle;
}
.team-title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.team-title h2 {
  margin: 0;
  font-size: var(--text-sm);
}
.team-badge {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-sm);
  background: var(--ink);
  color: #fff;
  font: 600 var(--text-xs) var(--font-mono);
}

/* Общий контрол периода «Графика» и «Плана» (#121). Внутри только ссылки:
   сегменты, стрелки и «Сегодня». Поле даты убрано в #177 — период здесь неделя
   или месяц, выбирать один день нечем. */
.period-picker {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.segments {
  display: flex;
  min-width: 0;
  padding: var(--space-1);
  border-radius: var(--radius-md);
  background: var(--surface-2);
}
.segments a {
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--ink-3);
  text-decoration: none;
}
.segments a[aria-current] {
  background: var(--ink);
  color: #fff;
  box-shadow: var(--shadow-sm);
  font-weight: 600;
}
.period-arrow {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  color: var(--ink);
  font-size:var(--text-lg);
  text-decoration: none;
}
.period-arrow.disabled {
  background: var(--surface-2);
  color: var(--ink-4);
  cursor: not-allowed;
}
.period {
  min-width: 170px;
  padding: var(--space-2) var(--space-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  text-align: center;
}
.period-today {
  padding: 0 var(--space-1);
  color: var(--ink-2);
  font-size: var(--text-xs);
  text-decoration: underline;
}
.period-picker a:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

@media (max-width: 700px) {
  /* На узком экране переключатель растягивается на доступную ширину. */
  .segments a {
    width: 100%;
    padding-inline: var(--space-1);
    text-align: center;
  }
  .period {
    min-width: 0;
    overflow: hidden;
    padding-inline: var(--space-1);
    font-size: var(--text-xs);
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .period-pop-body {
    right: auto;
    left: 0;
    width: min(360px, calc(100vw - 48px));
  }
}

/* Период, свёрнутый в кнопку (#136). Сама обёртка .period-button общая для
   «Журнала» и «Смены», а вот раскрывашка .period-pop ниже — только «Журнала»:
   у «Смены» с #137 стоит .shift-date с нативным полем (shift_now.html:16–24),
   промежуточный поповер стоил там лишнего клика. Поповер — нативный <details>,
   поэтому без JS. */
.period-button {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.shift-date input[type="date"] {
  box-sizing: border-box;
  height: 34px;
  padding: 0 var(--space-2) 0 var(--space-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  font: 600 var(--text-xs) var(--font-mono);
  cursor: pointer;
}
.shift-date input[type="date"]:hover {
  border-color: var(--ink-3);
}
/* Иконка календаря приглушена, чтобы не спорить с самой датой. Правило переехало
   сюда из контрола «Графика» в #177: там оно ушло вместе с полем, а тест «Смены»
   всё это время засчитывал его себе — искал подстроку по всему файлу, тогда как
   селектор был привязан к .period-picker и к .shift-date не применялся. */
.shift-date input[type="date"]::-webkit-calendar-picker-indicator {
  width: 15px;
  height: 15px;
  margin-left: var(--space-1);
  padding: 0;
  opacity: 0.55;
  cursor: pointer;
}
.shift-date input[type="date"]::-webkit-calendar-picker-indicator:hover {
  opacity: 1;
}
/* Кольцо вешается на :focus-within самого поля, а не на его :focus-visible.
   Нативное поле даты в Chromium — составной контрол: Tab проходит сегменты дня,
   месяца и года, а последней остановкой становится иконка календаря внутри
   shadow-дерева. На ней хост не совпадает уже ни с :focus, ни с :focus-visible, и
   кольцо пропадало ровно на один Tab. Нашлось настоящим Tab-проходом — проверка
   по селекторам такого не видит, а Lighthouse на этом экране даёт 100.

   :focus-within именно у поля, а не у формы: форма без JavaScript содержит ещё и
   кнопку «Открыть», и при фокусе на ней кольцо загоралось у обоих — у кнопки от
   своего правила и у ненажатого поля от формы. */
.shift-date input[type="date"]:focus-within,
.shift-date noscript button:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
.period-pop {
  position: relative;
}
.period-pop > summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  box-sizing: border-box;
  height: 34px;
  padding: 0 var(--space-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--text-xs);
  list-style: none;
  cursor: pointer;
}
.period-pop > summary::-webkit-details-marker {
  display: none;
}
.period-pop > summary:hover {
  border-color: var(--ink-3);
}
.period-pop[open] > summary {
  border-color: var(--ink);
  background: var(--surface-2);
  font-weight: 600;
}
.period-pop > summary svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  opacity: 0.6;
}
.period-pop > summary i {
  color: var(--ink-3);
  font-style: normal;
  font-size: var(--text-xs);
}
.period-pop-body {
  position: absolute;
  z-index: var(--z-popover);
  top: 40px;
  right: 0;
  display: grid;
  gap: var(--space-3);
  width: 360px;
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--shadow-pop);
}
.period-range {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
}
.period-range label {
  display: grid;
  gap: var(--space-1);
}
.period-range label span {
  color: var(--ink-3);
  font: 600 var(--text-xs) var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.period-range input[type="date"] {
  box-sizing: border-box;
  width: 100%;
  height: 36px;
  padding: 0 var(--space-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  font: 600 var(--text-xs) var(--font-mono);
}
.period-range button {
  grid-column: 1 / -1;
  height: 36px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--ink);
  color: #fff;
  font-weight: 600;
  cursor: pointer;
}
.period-pop-body .segments {
  flex-wrap: wrap;
}
.period-pop-body .period-today {
  justify-self: start;
}
.period-pop > summary:focus-visible,
.period-pop-body input:focus-visible,
.period-pop-body button:focus-visible,
.period-pop-body a:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* Текст только для скринридера. Утилита системы: нужна и на «Смене», и в журнале. */
.visually-hidden {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  margin: -1px !important;
  padding: 0 !important;
  overflow: hidden !important;
  border: 0 !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
}

/* Числа, время, ALL-CAPS ярлыки — Geist Mono, tabular numbers */
.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Прокручиваемые области таблиц достижимы с клавиатуры (#186).

   Chrome, в отличие от Firefox, не делает `overflow:auto` фокусируемым сам. Все
   четыре контейнера — «График», «План», «Журнал», «Смена» — были без `tabindex`,
   и месяц нельзя было доскроллить без мыши: 715px содержимого просто недостижимы.

   Правило общее и держится на атрибутах, а не на классах: иначе в каждом экранном
   файле завелась бы своя копия — ровно то, чем расходились маркер команды (#123) и
   бейдж грейда (#178). В `feed.css` такая копия уже лежала, написанная под
   `tabindex`, который так и не добавили. */
[role="region"][tabindex="0"]:focus {
  outline: none;
}
[role="region"][tabindex="0"]:focus-visible {
  box-shadow: var(--focus-ring);
}

/* Строка-заголовок команды внутри таблицы. Общая для «Графика» и «Плана» (#185):
   до этого «План» рендерил по таблице на команду и заголовок стоял снаружи, а
   после сведения к одной таблице разметка у экранов совпала. Копировать правило
   во второй экранный файл нельзя — ровно так расходились маркер команды (#123) и
   бейдж грейда (#178).

   Селектор намеренно `tr.team-row`, а не `.team-row`. Экранные файлы задают
   `.schedule-table th,.schedule-table td` и `.plan-scroll th,.plan-scroll td` со
   специфичностью (0,1,1) — ровно такой же, как у `.team-row th`. Пока правило
   лежало в schedule.css ниже них, оно выигрывало порядком; в общем файле, который
   подключается раньше экранных, оно бы порядок проиграло, и высота строки молча
   уехала бы с 40px на 43px. `tr.team-row th` даёт (0,1,2) и выигрывает по
   специфичности, не привязываясь при этом к родительскому контейнеру. */
tr.team-row th {
  height: 40px;
  padding: var(--space-2) var(--space-3);
  border-top: 1px solid var(--line-strong);
  background: var(--surface-2);
  text-align: left;
  font-size: var(--text-xs);
}
.team-row .team-badge {
  margin-right: var(--space-2);
}

/* Название команды не уезжает при горизонтальной прокрутке (#185).

   Липкой делается подпись внутри ячейки, а не сама ячейка: заголовок растянут на
   всю таблицу через `colspan`, его box и так стоит на месте — уплывает текст,
   лежащий у левого края этого box. Липкая подпись остаётся у левого края области
   прокрутки, как липкая колонка менеджера в соседних строках.

   Замерено на месяце: бейдж команды стоял на 129px и уезжал на −571px после
   полной прокрутки — то есть название команды пропадало ровно тогда, когда по
   нему нужно понять, чью строку смотришь. Стало 129px на любой прокрутке.

   `left` повторяет левый padding ячейки: при нулевой прокрутке подпись обязана
   стоять ровно там, где стояла бы без липкости. Это арифметика, а не совпадение,
   и она закреплена тестом. */
.team-row-label {
  position: sticky;
  left: var(--space-3);
  display: inline-block;
}

/* Зазор между командами в мобильном списке — тоже общий для двух экранов (#185). */
.mobile-team + .mobile-team {
  margin-top: var(--space-6);
}

/* Плашка-уведомление и пустое состояние — по одному компоненту на всю админку (#191).

   До этого их было десять: пять плашек и четыре пустых состояния, перечисленных в
   задаче, плюс шестая плашка `.shift-stale`, которую не заметили при заведении. Все
   отличались друг от друга padding'ом, рамкой и размером шрифта, а на «Графике» ещё
   и хардкодами цветов мимо токенов.

   Механизм копирования зафиксирован документально: `data_refresh_result.html`
   объяснял появление пятой плашки тем, что `.alert.info` определён только в
   `plan.css`, и на «Графике» баннер остался бы неоформленным. То есть проблема была
   осознана, а решением стал ещё один класс. Пока компонент живёт в экранном файле,
   следующий экран его копирует — это уже закрывали для маркера команды (#123) и
   бейджа грейда (#178).

   Рамка — `color-mix` от статусного токена, как у `.cell-missing`. Процент подобран
   замером под прежние хардкоды `#e3cd9a`, `#c2d6f2`, `#e7b9b9`: при 25% разница с
   ними 8, 21 и 11 единиц суммарно по трём каналам, то есть глазу неразличима. Смысл
   не в оттенке, а в том, что рамка теперь следует за токеном: сменится статусный
   цвет — сменится и она. */
.notice {
  margin: var(--space-3) 0;
  padding: var(--space-3) var(--space-3);
  border: 1px solid;
  border-radius: var(--radius-md);
  font-size: var(--text-xs);
}
.notice.info {
  border-color: color-mix(in oklch, var(--info-fg) 25%, transparent);
  background: var(--info-bg);
  color: var(--info-fg);
}
.notice.warn {
  border-color: color-mix(in oklch, var(--warn-fg) 25%, transparent);
  background: var(--warn-bg);
  color: var(--warn-fg);
}
.notice.error {
  border-color: color-mix(in oklch, var(--err-fg) 25%, transparent);
  background: var(--err-bg);
  color: var(--err-fg);
}
/* Плашка устаревших данных прячется атрибутом, а `display:grid` ниже перебил бы
   браузерное `[hidden]{display:none}` по специфичности (#187). */
.notice[hidden] {
  display: none;
}

/* Пустое состояние и ошибка — один компонент с модификатором. Раньше у двух экранов
   из четырёх модификатора `.error` не было вовсе, и ошибка выглядела как пустота:
   «данных нет» вместо «мы их не смогли получить». Это разные сообщения. */
.state {
  display: grid;
  gap: var(--space-1);
  place-content: center;
  min-height: 180px;
  padding: var(--space-7);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-card);
  background: var(--surface-2);
  color: var(--ink-2);
  text-align: center;
}
.state span,
.state small {
  color: var(--ink-3);
}
.state a {
  color: var(--ink);
  font-weight: 600;
  text-decoration: underline;
}
.state.error {
  border-color: color-mix(in oklch, var(--err-fg) 25%, transparent);
  background: var(--err-bg);
  color: var(--err-fg);
}
.state.error span {
  color: inherit;
}

/* Карточка менеджера с мини-сеткой дней. Компонент общий: «План» рисует ею месяц на
   мобильном с #185, «График» — с #199, где месячная раскладка карточками по дню давала
   11 341px прокрутки против 2 329px у недели.

   Внешнего отступа у карточки намеренно нет: расстояние между карточками принадлежит
   месту применения, а не компоненту, — иначе он складывается с `gap` контейнера и
   разъезжается на соседнем экране, как это уже было с бейджем грейда (#178). */
.manager-card {
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  background: var(--surface);
}
.manager-card header,
.manager-card footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
.manager-card header {
  font-weight: 600;
}
.manager-card footer {
  margin-top: var(--space-2);
  color: var(--ink-3);
  font-size: var(--text-xs);
}
.manager-card footer b {
  color: var(--ink);
  font-family: var(--font-mono);
}
.manager-card .grade {
  margin-right: var(--space-2);
}
.mini-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(38px, 1fr));
  gap: var(--space-1);
  margin-top: var(--space-2);
}
.mini-day {
  min-width: 0;
  text-align: center;
}
.mini-day > span {
  display: block;
  margin-bottom: var(--space-1);
  color: var(--ink-3);
  font-size: var(--text-xs);
}
.mini-day > b {
  display: grid;
  place-items: center;
  min-height: 34px;
  border-radius: var(--radius-badge);
  font-family: var(--font-mono);
}

/* Страница входа (#102). Живёт в дизайн-системе, а не в пятом экранном файле: у неё
   нет своего экрана, и заводить его ради одной формы значило бы повторить ровно ту
   ошибку, которую разбирал #191. */
.login-page {
  max-width: 340px;
  margin: 12vh auto;
  text-align: center;
}
.login-page h1 {
  margin: 0 0 var(--space-2);
  font-size: var(--text-lg);
}
.login-page p {
  margin: 0 0 var(--space-6);
  color: var(--ink-3);
  font-size: var(--text-sm);
}
.login-form {
  display: grid;
  gap: var(--space-2);
  text-align: left;
}
.login-form label {
  color: var(--ink-3);
  font-size: var(--text-xs);
}
.login-form input {
  box-sizing: border-box;
  height: 40px;
  padding: 0 var(--space-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  font: 500 var(--text-sm) var(--font-sans);
}
.login-form button {
  height: 40px;
  margin-top: var(--space-2);
  border: 0;
  border-radius: var(--radius-md);
  background: var(--ink);
  color: #fff;
  font: 600 var(--text-sm) var(--font-sans);
  cursor: pointer;
}
