/* ФинВизор — оформление веб-интерфейса.
   Своя палитра вместо готового фреймворка: страниц немного, а тащить
   ради них мегабайт CSS с чужого CDN на российском хостинге — лишний
   риск и лишняя зависимость.

   Фирменные цвета (брендбук): зелёный козырёк бухгалтера, латунь заклёпок,
   красные чернила. Отсюда важное правило — у цвета две разные работы, и
   путать их нельзя:

     --accent             интерфейс: ссылки, кнопки, фокус, активный пункт;
     --income / --expense смысл денег: приход и расход;
     --brass              величина без знака: доли, прибыль, группа B.

   До брендбука акцент был синим и спокойно работал третьим цветом на
   графиках. Теперь он зелёный, то есть неотличим от дохода, — поэтому
   везде, где цвет обозначал данные, а не действие, стоит латунь. */

:root {
  color-scheme: light dark;

  /* Нейтральные тона тёплые, а не серо-синие: страница должна читаться
     как бумага под зелёным козырьком, а не как консоль. Смысловые цвета
     (accent/income/expense/brass) при этом не меняются — у них своя
     работа, описанная выше. */
  --bg: #f2f1ea;
  --paper: #faf9f4;
  --surface: #ffffff;
  --surface-2: #f1f3f6;
  --border: #e2dfd2;
  --border-soft: #eae7da;
  --text: #191a16;
  --text-dim: #6b6a5c;
  /* Зелёный целлулоид козырька — фирменный акцент */
  --accent: #2e6b4c;
  --accent-2: #3f8a63;
  --accent-soft: #e4efe8;
  --income: #1b6e46;
  --income-soft: #dcf2e4;
  /* Красные чернила бухгалтера */
  --expense: #b23b2a;
  --expense-soft: #f9e8e5;
  /* Латунь заклёпок — второй фирменный цвет, только для данных */
  --brass: #8f6a1f;
  --brass-2: #c9992f;
  /* Текст поверх насыщенной заливки: кнопки, значки ABC */
  --btn-ink: #ffffff;
  --warn-bg: #fff8e5;
  --warn-border: #f0d391;

  /* Шкала радиусов и теней вместо одного значения на всё. Раньше каждая
     карточка лежала на странице с одинаковым весом; теперь глубина
     работает как иерархия: показатели ближе к читателю, панели глубже.
     `--radius` и `--shadow` оставлены псевдонимами — на них ссылается
     остальной файл, и менять их по всему коду ради переименования
     незачем. */
  /* Шкала кегля. До неё размеры подбирались по месту — 11, 11.5, 12,
     12.5, 13, 13.5 — и разница между соседними ступенями была меньше,
     чем ошибка глаза. Шесть ступеней с внятным шагом читаются как
     порядок; полтора десятка близких значений — как небрежность.
     Крупные ступени растут быстрее мелких: подписи должны различаться
     мало, суммы — сильно. */
  --t-2xs: 11px;
  --t-xs: 12px;
  --t-sm: 13px;
  --t-base: 15px;
  --t-md: 17px;
  --t-lg: 20px;
  --t-xl: 24px;
  --t-2xl: 34px;

  /* Ритм отступов — шаг 4px. Причина та же: 18px рядом с 20px не
     воспринимаются как разные величины, они воспринимаются как одна,
     поставленная неаккуратно. */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-8: 32px;

  /* Скругления. Путь был 22/16/10 → 14/10/7 → 10/8/6: каждый шаг в одну
     сторону. Крупный радиус читается как «мягко и дружелюбно» — это
     верно для приложения о привычках и неверно для приложения о
     деньгах. Швейцарская школа, на которой построен этот вид, вообще
     обходится прямым углом; шесть пикселей — уступка тому, что экран
     не бумага, и касаться прямых углов пальцем неприятно.

     Отдельно ушла «пилюля» (999px) у кнопок. Овальная кнопка — примета
     потребительских приложений: она приглашает нажать. Здесь кнопки не
     приглашают, а называют действие. */
  --radius-lg: 10px;
  --radius-md: 8px;
  --radius-sm: 6px;
  /* Радиус элемента управления: кнопка, поле, вкладка. Отдельно от
     карточек — они крупнее и радиус у них свой. */
  --radius-control: 7px;
  /* Отступ денег в клетке графика: суммы смен и итог за сутки стоят в
     одну колонку под именами. Общее значение на двоих — чтобы они не
     разъехались, как разъехались до 12.09.2026. */
  --cal-money-indent: 16px;
  /* Высота маленькой кнопки. Названа отдельно, потому что её должны
     разделять двое: кнопка с подписью и кнопка из одного значка. Пока
     высота была следствием отступов у одной и заданным числом у другой,
     они расходились на пару пикселей — и «Переименовать» с крестиком
     рядом стояли на разной высоте. */
  --control-small: 32px;
  --radius: var(--radius-lg);
  --shadow-sm: 0 1px 2px rgba(30, 26, 10, .06);
  --shadow-md: 0 10px 30px -14px rgba(30, 26, 10, .28);
  --shadow-lg: 0 24px 60px -20px rgba(30, 26, 10, .32);
  /* Волосяная обводка тенью, а не рамкой: рамка на скруглении даёт
     ступеньку, тень — нет */
  --ring: 0 0 0 1px rgba(30, 26, 10, .05);
  --shadow: var(--shadow-sm);
}

:root[data-theme="dark"] {
  --bg: #0d100d;
  --paper: #12160f;
  --surface: #161b15;
  --surface-2: #1b211a;
  --border: #262c22;
  --border-soft: #20261d;
  --text: #eaeee4;
  --text-dim: #9aa192;
  --accent: #4c9c74;
  --accent-2: #6bb98f;
  --accent-soft: #16261f;
  --income: #4fae7d;
  --income-soft: #102a1d;
  --expense: #e0715f;
  --expense-soft: #2b1512;
  --brass: #c9a05a;
  --brass-2: #e0b96a;
  /* На светлой зелени и латуни белый текст даёт 3,3:1 — ниже нормы.
     Поэтому в тёмной теме поверх заливки идут не белые буквы, а
     чернила цвета фона страницы: 5,7–7,8:1. */
  --btn-ink: #0d100d;
  --warn-bg: #2a2312;
  --warn-border: #5c4a1c;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5);
  --shadow-md: 0 14px 34px -16px rgba(0, 0, 0, .7);
  --shadow-lg: 0 28px 70px -22px rgba(0, 0, 0, .8);
  --ring: 0 0 0 1px rgba(255, 255, 255, .04);
  --shadow: var(--shadow-sm);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0d100d;
    --paper: #12160f;
    --surface: #161b15;
    --surface-2: #1b211a;
    --border: #262c22;
    --border-soft: #20261d;
    --text: #eaeee4;
    --text-dim: #9aa192;
    --accent: #4c9c74;
    --accent-2: #6bb98f;
    --accent-soft: #16261f;
    --income: #4fae7d;
    --income-soft: #102a1d;
    --expense: #e0715f;
    --expense-soft: #2b1512;
    --brass: #c9a05a;
    --brass-2: #e0b96a;
    --btn-ink: #0d100d;
    --warn-bg: #2a2312;
    --warn-border: #5c4a1c;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5);
    --shadow-md: 0 14px 34px -16px rgba(0, 0, 0, .7);
    --shadow-lg: 0 28px 70px -22px rgba(0, 0, 0, .8);
    --ring: 0 0 0 1px rgba(255, 255, 255, .04);
    --shadow: var(--shadow-sm);
  }
}

* { box-sizing: border-box; }

/* Ховеры без рывка. Анимации выключаются, если система просит покоя:
   для части людей это не украшение, а повод закрыть страницу. */
@media (prefers-reduced-motion: no-preference) {
  .nav-link, .btn, .tabs a, .switcher-item, tbody tr, .theme-toggle {
    transition: background-color .12s ease, color .12s ease, border-color .12s ease;
  }

  /* Правило движения: двигается то, что изменилось. Всё остальное
     стоит.

     Отсюда убрано всплытие карточек по очереди. Оно проигрывалось на
     каждой загрузке любой страницы, то есть сообщало «что-то
     произошло» ровно тогда, когда не происходило ничего: те же цифры,
     тот же экран, просто человек вернулся. Полсекунды ожидания за
     сообщение, которого нет.

     Что двигается вместо этого:
       переход между страницами — ниже, через View Transitions;
       баннер подтверждения      — .banner.ok, что-то и правда случилось;
       столбики графика          — данные появляются впервые;
       отклик на касание         — кнопки и строки под курсором. */

  /* Баннер приезжает сверху: он появляется в ответ на действие, и это
     единственное на странице, что изменилось с прошлого показа. */
  @keyframes settle {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0); }
  }
  .banner.ok { animation: settle .28s cubic-bezier(.2, .7, .3, 1) backwards; }

  /* Нажатие должно чувствоваться пальцем, а не только читаться глазом */
  .btn:active { transform: translateY(1px); }
}

/* Переход между страницами.

   Сайт серверный: каждый щелчок — новый документ, и до сих пор это
   выглядело как вспышка белым (или чёрным) между страницами. View
   Transitions сшивает два документа силами самого браузера — без
   javascript, без библиотеки и без переписывания на одностраничное
   приложение. Браузер, который её не знает, просто переключит страницу
   как раньше: терять нечего.

   Боковая панель получает своё имя и потому не участвует в переходе:
   она одна и та же на всех страницах, и растворять её вместе с
   содержимым значило бы моргать тем, что не менялось. */
@view-transition { navigation: auto; }

@media (prefers-reduced-motion: no-preference) {
  .sidebar { view-transition-name: panel; }
  .main { view-transition-name: main; }

  @keyframes vt-out { to { opacity: 0; transform: translateY(-4px); } }
  @keyframes vt-in { from { opacity: 0; transform: translateY(6px); } }
  ::view-transition-old(main) { animation: vt-out .16s ease-in both; }
  ::view-transition-new(main) { animation: vt-in .24s cubic-bezier(.2, .7, .3, 1) both; }
}

/* Просьбу о покое уважает и переход: анимации групп надо гасить
   отдельно — они живут в псевдоэлементах, а не в дереве документа. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* Шрифты брендбука. Системный стек оставлен фолбэком в каждой цепочке:
   файлы локальные, но если они не доедут, страница обязана остаться
   читаемой, а не ждать шрифт. */
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.55 "PT Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* Заголовки и логотип — Golos Text, плотный и «вывесочный» */
h1, h2, .brand { font-family: "Golos Text", "PT Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }

/* Деньги — моноширинным с одинаковой шириной цифр: колонка сумм должна
   читаться столбиком, а не «прыгать» на единицах и семёрках */
.num, .amount-income, .amount-expense, .income-text, .expense-text,
.filter-summary b, .split-line {
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Видимый фокус на всём, куда можно попасть с клавиатуры. Раньше кольцо
   было только у полей ввода, и человек, идущий по странице табом, терял
   курсор на ссылках и кнопках. */
/* Обводка фокуса — одна на весь интерфейс. Пункт чек-листа
   ui-ux-pro-max: «Focus states visible for keyboard nav», и он же в
   списке критичных — снятая обводка фокуса стоит там в антипаттернах. */
:where(a, button, summary, [tabindex], input, select, textarea):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 8px;
}

/* Первая цель таба — пропустить меню и попасть сразу в содержимое */
.skip-link {
  position: absolute; left: 8px; top: -60px; z-index: 100;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 14px; font-weight: 550; box-shadow: var(--shadow);
}
.skip-link:focus { top: 8px; text-decoration: none; }

.layout { display: flex; min-height: 100vh; }

/* ------------------------------------------------------------ навигация */

.sidebar {
  width: 232px;
  flex: 0 0 232px;
  /* Панель прилипает и прокручивается сама. Без этого она тянулась во
     всю высоту страницы, и на длинной сводке подвал с «Выйти» и
     переключателем темы уезжал на 1200-й пиксель — чтобы сменить тему,
     надо было доскроллить до конца отчёта. */
  position: sticky;
  top: 0;
  align-self: flex-start;
  height: 100vh;
  overflow-y: auto;
  /* Панель на тон отличается и от фона страницы, и от карточек: карточки
     должны читаться как лежащие на странице, а панель — как её край */
  background: var(--paper);
  border-right: 1px solid var(--border-soft);
  padding: 22px 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 17px;
  font-weight: 650;
  letter-spacing: -.01em;
  margin-bottom: 20px;
  padding: 0 6px;
}
/* Знак бренда — тот же поднимающийся штрих, что в favicon: во вкладке
   браузера и в углу страницы человек должен видеть одно и то же.
   Градиент и внутренняя подсветка сверху дают объём плитки, не добавляя
   ни одного изображения. */
.brand-mark {
  width: 34px; height: 34px; border-radius: 10px; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(160deg, var(--accent-2), var(--accent) 60%, #1d4e36);
  box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255, 255, 255, .25);
}
/* Знак занимает плитку целиком: поля заложены в самой графике
   (штрихи идут от 5 до 19 из 24), как на образце от владельца. Прежние
   18px внутри 34px давали двойной отступ — свой и нарисованный. */
.brand-mark svg { width: 100%; height: 100%; }
.brand-text { display: flex; flex-direction: column; min-width: 0; }
.brand-name { font-size: var(--t-base); font-weight: 800; letter-spacing: -.01em; }
.brand-sub { font-size: 11px; font-weight: 500; color: var(--text-dim); margin-top: -2px; }
/* Виден только на узком экране, где сайдбар свёрнут */
.brand-biz { display: none; }

.nav-link {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px;
  border-radius: 10px;
  color: var(--text);
  font-weight: 500;
}
.nav-link:hover { background: var(--surface-2); text-decoration: none; }
.nav-link.active { background: var(--accent-soft); color: var(--accent); }
/* Значок — часть текста: обводка берёт `currentColor`, поэтому в
   активном пункте он перекрашивается вместе с подписью. С эмодзи так
   было нельзя, и активный пункт выглядел наполовину покрашенным. */
.nav-link .ico { width: 17px; height: 17px; flex: 0 0 auto; }

/* Заголовок раздела меню. Мелкий и разрежённый: это указатель, а не
   пункт, и кликать по нему не нужно. */
.nav-label {
  font-size: 10px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--text-dim); font-weight: 700; padding: 12px 10px 4px;
}
.nav-label:first-child { padding-top: 2px; }

/* Всё под шапкой сайдбара — одним блоком: на телефоне он сворачивается
   целиком, и не нужно прятать переключатель, меню и подвал по отдельности */
.nav-body { display: flex; flex-direction: column; flex: 1; min-height: 0; gap: 4px; }

/* Бизнес — контекст всей страницы, поэтому стоит над меню, а не в подвале:
   с несколькими бизнесами вопрос «а чьи это цифры?» возникает первым. */
.workspace, .switcher > summary {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 12px; margin-bottom: 10px;
  background: var(--surface-2); border-radius: 10px;
  font-size: 13px; line-height: 1.3;
}
.workspace b, .switcher b { font-weight: 600; font-size: 13.5px; }
/* Строкой, а не колонкой: внутри лежат точка состояния и имя, и колонка
   ставила точку НАД названием бизнеса. Колонка осталась с тех пор, когда
   в блоке были только название и роль, — я добавил точку и проверил лишь
   вариант с несколькими бизнесами (там `summary`, он строкой). У кого
   бизнес один — а это большинство — точка уезжала вверх.
   Название с ролью складывает в столбик вложенный `.switcher-name`. */
.workspace { flex-direction: row; align-items: center; gap: 8px; }
.workspace .muted, .switcher .muted { font-size: 12px; }

.switcher { position: relative; }
.switcher > summary {
  cursor: pointer; list-style: none; justify-content: space-between;
  border: 1px solid transparent;
}
.switcher > summary::-webkit-details-marker { display: none; }
.switcher > summary:hover { border-color: var(--border); }
/* Имя занимает всю середину строки. Без `flex: 1` у `summary` оставалось
   три элемента и `justify-content: space-between`: точка уходила к левому
   краю, шеврон к правому, а имя выталкивалось в середину. На узкой
   панели рабочего стола это незаметно — там имя и так заполняет строку,
   — а на телефоне шапка во всю ширину, и название уплывало в центр.
   Замечание владельца 13.09 со снимком. */
.switcher-name { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.switcher-name b, .switcher-name .muted { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chev { color: var(--text-dim); font-size: 14px; line-height: 1; flex: 0 0 auto; }
.chev { display: flex; width: 14px; color: var(--text-dim); flex: 0 0 auto; }
.chev .ico { width: 14px; height: 14px; }
.switcher[open] .chev { transform: rotate(180deg); }

/* Точка бизнеса. Зелёная с мягким кольцом — тот же приём, что у чипов:
   цвет говорит «активно», кольцо отделяет её от фона панели. */
.biz-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto;
  background: var(--income); box-shadow: 0 0 0 3px var(--income-soft);
}

.switcher-list {
  position: absolute; z-index: 20; left: 0; right: 0; top: 100%; margin-top: -6px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow); padding: 5px;
}
.switcher-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 10px; border: 0; border-radius: 8px;
  background: none; color: var(--text); font: inherit; font-size: 13px;
  text-align: left; cursor: pointer;
}
.switcher-item:hover { background: var(--surface-2); }
.switcher-item.current { font-weight: 600; }
.switcher-item .mark { width: 12px; color: var(--accent); flex: 0 0 auto; }
.switcher-item .muted { display: block; font-size: 11.5px; }

.sidebar-foot {
  margin-top: auto;
  padding: 12px 6px 0;
  border-top: 1px solid var(--border-soft);
  color: var(--text-dim);
  font-size: 13px;
}
/* Имя в подвале — ссылка в личный раздел, но выглядит как подпись, а не
   как ссылка: подчёркивание и акцентный цвет здесь спорили бы с пунктами
   меню выше. Наведение и активное состояние показывают, что это нажимаемо. */
.sidebar-foot .who {
  font-weight: 600; color: var(--text); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-decoration: none;
}
.sidebar-foot .who:hover { color: var(--accent); text-decoration: none; }
.sidebar-foot .who.active { color: var(--accent); }
.foot-exit { display: inline-block; margin-top: 6px; font-size: 12.5px; }

/* ---------------------------------------------------------------- контент */

/* Ширину не ограничиваем: на широком мониторе колонка в 1180px занимала
   треть экрана, а остальное пустовало. Таблицы и графики от простора
   только выигрывают, а тексты и так заперты внутри карточек. */
.main { flex: 1; min-width: 0; padding: 26px 30px 60px; }

.page-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 20px;
}
h1 { font-size: var(--t-xl); font-weight: 650; letter-spacing: -.025em; margin: 0; }
/* Был 15px — ровно кегль основного текста. Полужирность одна заголовка
   не делает: разделы читались как абзацы, набранные жирным. Ступень
   выше плюс воздух под ней — и структура страницы видна до чтения. */
h2 {
  font-size: var(--t-md); font-weight: 620; letter-spacing: -.015em;
  margin: 0 0 var(--s-4);
}
.muted { color: var(--text-dim); font-size: 13px; }

.banner {
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  border-radius: 10px;
  padding: 9px 14px;
  font-size: 13px;
  margin-bottom: 18px;
}

/* -------------------------------------------------------------- карточки */

/* Карточка держится волосяной линией, а не рамкой с тенью. Рамка плюс
   тень — два способа сказать одно и то же, и вместе они дают ощущение
   наклеенных друг на друга бумажек. Одна линия толщиной в пиксель
   отделяет ровно столько, сколько нужно, и не спорит с содержимым. */
.card {
  background: var(--surface);
  border: 0;
  border-radius: var(--radius-lg);
  box-shadow: var(--ring);
  padding: var(--s-5) var(--s-6);
}

.grid { display: grid; gap: 16px; }
.kpi-grid { grid-template-columns: repeat(auto-fit, minmax(232px, 1fr)); margin-bottom: 16px; }
.split { grid-template-columns: 1.35fr 1fr; align-items: start; }
/* Ряд, где есть график, выравнивается по высокой колонке, а график
   забирает освободившееся место. Пустота между виджетами берётся
   отсюда: колонки почти никогда не равны, и под короткой оставался
   прямоугольник высотой в разницу.

   Растягиваем только ряды с графиком — ему есть чем занять место.
   Ряды из форм и списков («Добавить», «Мой профиль», «Категории»)
   остаются как были: список из трёх строк, размазанный на высоту
   соседней таблицы, хуже честной пустоты под ним. */
.split:has(.chart-box) { align-items: stretch; }
.split:has(.chart-box) > .card,
.split-side:has(.chart-box) > .card { display: flex; flex-direction: column; }
.card > .chart-box { flex: 1 1 auto; }
/* Правая половина нижнего ряда — столбец карточек, а не одна карточка:
   так «Как платят» встаёт под «Больше всего расходов», а ряд
   показателей остаётся ровным из трёх. */
.split-side { display: grid; gap: var(--s-4); align-content: start; min-width: 0; }
/* Гамбургер виден только на узком экране */
.burger {
  display: none; margin-left: auto; background: none; border: 0;
  color: var(--text); font-size: 20px; line-height: 1; cursor: pointer;
  padding: 4px 6px; border-radius: 8px;
}
.burger:hover { background: var(--surface-2); }

.foot-row { display: flex; align-items: center; gap: 8px; }
/* Имя забирает остаток строки, значки стоят справа и не ужимаются:
   `space-between` разводил их по краям, и с двумя значками между ними
   возникала дыра. */
.sidebar-foot .who { flex: 1 1 auto; }
/* Значки подвала — настройки и тема. Форма общая: обе про устройство, а
   не про данные, и разная форма читалась бы как разная природа. */
.foot-icon, .theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: 0 0 auto;
  background: var(--surface); border: 1px solid var(--border-soft);
  border-radius: 999px; cursor: pointer; color: var(--text-dim);
  box-shadow: var(--shadow-sm);
}
.foot-icon .ico, .theme-toggle .ico { width: 15px; height: 15px; }
.foot-icon:hover, .theme-toggle:hover {
  background: var(--surface-2); color: var(--text); text-decoration: none;
}
/* Открытые «Настройки» подсвечены так же, как активный пункт панели:
   значок остаётся единственным указателем на то, где человек стоит. */
.foot-icon.active {
  color: var(--accent); border-color: var(--accent-soft); background: var(--accent-soft);
}

.row-actions { display: flex; gap: 6px; justify-content: flex-end; align-items: center; }

/* Ячейка действий в строке таблицы. Каждое действие — своя форма (адреса
   разные), а форма блочная: без этого «выдать пароль» вставал НАД
   «убрать», и столбец разъезжался по высоте от строки к строке. */
td.cell-actions { white-space: nowrap; }
td.cell-actions > form { display: inline-block; margin: 0; }
td.cell-actions > form + form { margin-left: 6px; }

@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; }
  .layout { flex-direction: column; }
  .main { padding: 18px 16px 50px; }

  /* На телефоне сайдбар сворачивается в одну строку с гамбургером:
     горизонтальный ряд из пяти пунктов занимал пол-экрана. */
  .sidebar {
    width: auto; flex: none; padding: 10px 14px;
    border-right: 0; border-bottom: 1px solid var(--border);
    position: sticky; top: 0; z-index: 10;
    /* Высоту в 100vh с рабочего стола обязательно снять: здесь панель —
       одна строка с гамбургером, и во весь экран она перекрыла бы всё */
    height: auto; align-self: auto; overflow-y: visible;
  }
  .brand { margin: 0; }
  /* Панель на телефоне прилипшая, и каждый её пиксель — отнятый у
     содержимого. Подпись «управленческий учёт» здесь не работает: кто
     открыл сайт, уже знает, что это за сайт. */
  .brand-sub { display: none; }
  /* На телефоне меняется только плитка. Размер самой графики здесь не
     повторяем: поля заложены внутри рисунка, и второе объявление
     возвращало бы двойной отступ — знак тонул бы в плитке. */
  .brand-mark { width: 30px; height: 30px; border-radius: 9px; }
  .brand-biz {
    display: block; flex: 1; min-width: 0; margin-left: 4px;
    font-size: 12.5px; font-weight: 500; color: var(--text-dim);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .brand-biz::before { content: "· "; }
  /* Периоды одной строкой, замечание владельца 13.09 со снимком с
     телефона: «Свой период» уезжал на вторую строку, и переключатель из
     ряда превращался в блок.

     Перенос был разрешён, когда способы расчёта получили полные
     названия и строка фильтров перестала влезать. Лечение оказалось
     хуже болезни: перенос ломает ряд у ВСЕХ вкладок, а не только у той,
     что не влезла. Теперь ряд не переносится, а прокручивается, и для
     надёжности ужат: на 390 пикселях четыре периода помещаются целиком,
     на узких переводах — уедут под палец, а не под низ.

     `-webkit-overflow-scrolling` не ставим: в WebKit с 13-й версии
     инерция включена сама, а свойство осталось устаревшим. */
  .tabs {
    flex-wrap: nowrap; overflow-x: auto; max-width: 100%;
    scrollbar-width: none;
  }
  .tabs::-webkit-scrollbar { display: none; }
  .tabs a { padding: 7px 10px; font-size: 12.5px; white-space: nowrap; }

  /* 2. Ряд периодов и форма своего периода — одной ширины.
     Замечание того же снимка: «появляющееся меню отличается по размеру
     от поля выбора периода». Так и было: ряд вкладок растягивался на всю
     строку, а форма — только по содержимому, и две коробки друг под
     другом стояли разной ширины. На телефоне у них обеих одна ширина —
     вся строка. */
  .period-bar > .tabs, .period-bar > .custom-period { flex: 1 1 100%; }

  /* Палец — не мышь: 44px по рекомендациям Apple и Google */
  .burger { display: block; min-width: 44px; min-height: 44px; font-size: 22px; }
  .nav-body { display: none; padding-top: 10px; }
  .sidebar.open .nav-body { display: flex; }
  .nav-links { display: flex; flex-direction: column; gap: 2px; }
  .nav-link { min-height: 44px; }
  .sidebar-foot { margin-top: 10px; }
  .switcher-list { position: static; margin-top: 6px; }
}

/* Чип изменения и подпись «к прошлому периоду».

   Чип — `inline-flex`, и базовая линия у такой коробки берётся от
   первого её элемента после центрирования, а не от текста внутри.
   Рядом стоящая подпись — обычный inline меньшего кегля, и выравнивание
   по базовой линии их не сводит: замерено расхождение 2,21 px, ровно
   его владелец и увидел на снимке 14.09.

   Лечится на строке, а не внутри чипа: `align-items: baseline` у самого
   чипа сводит подписи (0,35 px), но роняет значок внутри него на 3,3 px.
   Ряд с центрированием даёт 0,58 px и не трогает значок. */
.kpi .delta {
  font-size: 13px; color: var(--text-dim);
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  /* Строка изменения занимает место, даже когда сказать нечего.

     `change_parts` возвращает пустой `Change`, если и текущий период, и
     прошлый — нули: «показатель и так ноль, говорить не о чем». Решение
     верное, а вот последствие — нет: пустой `div` схлопывался, и всё
     ниже в ЭТОЙ карточке поднималось на строку. Владелец 14.09 прислал
     снимок, где «последние 7 дней» у «Расхода» на 20 px выше, чем у
     соседей, — замерено 163 / 143 / 163.

     Резерв — ровно одна строка, и это не подогнанное число: природная
     высота строки чипа 20,14 px при `line-height` 20,15, то есть значок
     её не растягивает и она равна строке текста. `em` — запасной вариант
     для браузеров без единицы `lh` (до Safari 16.4): тот же размер,
     посчитанный от кегля.

     Прижимать график к низу карточки нельзя, хотя напрашивается:
     проверено замером — у «Дохода» и «Расхода» под подписью есть
     разбивка по способам расчёта, у «Прибыли» её не бывает, и выравнивание
     от низа разносит подписи на 39 px (153 / 153 / 192). */
  min-height: 1.55em;
  min-height: 1lh;
}
/* --------------------------------------------- карточки показателей */

/* Число, изменение и микро-график в одной карточке. Раскладка сверху
   вниз: подпись → сумма → чип → график, чтобы взгляд шёл по важности,
   а не по площади элементов. */
.kpi {
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--s-5) var(--s-6) var(--s-5);
  /* Тени у показателей больше нет. Она поднимала карточку над страницей,
     но поднимала вместе с ней и рамку, и цветное пятно — три приёма,
     чтобы сказать «это важно». Важность теперь несёт единственное, что
     на карточке и есть важного: размер самой суммы. */
  box-shadow: var(--ring);
  position: relative;
}

/* Цветного свечения за карточкой больше нет. Оно задумывалось как
   воздух, но на деле красило фон под суммой — а сумма и так своего
   цвета, и два носителя одного смысла спорят друг с другом. Убрано
   вместе с `overflow: hidden`, который существовал только ради него. */
.card.kpi { overflow: hidden; }
.kpi .label {
  color: var(--text-dim); font-size: var(--t-2xs); font-weight: 600;
  letter-spacing: .07em; text-transform: uppercase;
}
.kpi .value { margin: var(--s-2) 0 var(--s-1); }

/* Крупная сумма и мелкая единица рядом: «₽» того же кегля перетягивает
   внимание с самой суммы */
/* Сумма — единственное, что на карточке крупное. Раньше её вес делили
   тень, рамка и цветное пятно; теперь она несёт его одна, и потому
   выросла с 30 до 34. */
.big {
  font-size: var(--t-2xl); font-weight: 620; letter-spacing: -.035em; line-height: 1.05;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.big .unit {
  font-size: .48em; font-weight: 550; margin-left: 3px;
  color: var(--text-dim); letter-spacing: 0;
}

/* Изменение — отдельным значком, а не строкой текста: так «−61%» видно
   раньше, чем прочитано «к прошлому периоду» */
/* Изменение — цветным текстом со стрелкой, без цветной таблетки.
   Заливка делала из справки значок и ставила её вровень с суммой,
   хотя сумма здесь главная, а изменение — уточнение к ней. */
.chip {
  display: inline-flex; align-items: center; gap: var(--s-1);
  padding: 0; border-radius: 0; background: none;
  font-size: var(--t-sm); font-weight: 600; font-family: inherit;
}
.chip.up { color: var(--income); }
.chip.down { color: var(--expense); }
.chip-note { font-size: var(--t-xs); color: var(--text-dim); }

/* Спарклайн тянется на всю ширину карточки и занимает одну строку:
   он отвечает на «как шло», а не «сколько именно» */
.spark { width: 100%; height: 38px; margin-top: 10px; display: block; overflow: visible; }
.spark-line { fill: none; stroke: currentColor; stroke-width: 1.5; vector-effect: non-scaling-stroke;
              stroke-linejoin: round; stroke-linecap: round; }
/* Заливка берёт градиент из атрибута fill в макросе; opacity здесь
   больше не нужна — прозрачность задают стопы градиента */
.spark-area { stroke: none; }
/* Точка «сейчас» на конце линии. Толщина обводки — её диаметр:
   это отрезок нулевой длины, см. пояснение в _charts.html */
.spark-end {
  stroke: currentColor; stroke-width: 5px; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.spark.income { color: var(--income); }
.spark.expense { color: var(--expense); }
.spark.brass { color: var(--brass); }

.dots { width: 100%; height: 34px; display: block; margin-top: 10px; fill: var(--brass); }

/* Кольцо долей: подписи рядом, а не поверх — на 84px внутрь ничего
   читаемого не влезает */
.mix-body { display: flex; align-items: center; gap: 14px; margin-top: 6px; }
.donut { flex: 0 0 auto; transform: rotate(-90deg); }
.donut circle { fill: none; stroke-width: 5; }
.donut-track { stroke: var(--surface-2); }
/* Скруглённые концы: у кольца из двух-трёх долей срезанные торцы
   выглядят обрубленными, а не собранными. Доли при этом чуть заходят
   друг на друга — на кольце диаметром 84 px это незаметно, а порядок
   отрисовки (наличные под латунью) сохраняет читаемость границы. */
.donut-part { stroke-linecap: round; }
.donut-part.cash { stroke: var(--income); }
.donut-part.cashless { stroke: var(--brass); }
.donut-part.unknown { stroke: var(--border); }

.mix-legend { display: flex; flex-direction: column; gap: 5px; min-width: 0; font-size: 13px; }
.mix-row { display: flex; align-items: center; gap: 7px; }
.mix-row b { margin-left: auto; font-variant-numeric: tabular-nums; }
.mix-name { color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mix-row span[class^="dot-"] { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.dot-cash { background: var(--income); }
.dot-cashless { background: var(--brass); }
.dot-unknown { background: var(--border); }

@media (max-width: 700px) {
  .big { font-size: var(--t-xl); }
  .kpi { padding: 14px 15px 12px; }
}

.kpi.income .value { color: var(--income); }
.kpi.expense .value { color: var(--expense); }
/* Хорошо/плохо, а не вверх/вниз: рост расхода — это плохо. */
.good { color: var(--income); font-weight: 550; }
.bad { color: var(--expense); font-weight: 550; }
.kpi.profit-positive .value { color: var(--income); }
.kpi.profit-negative .value { color: var(--expense); }

/* --------------------------------------------------------------- периоды */

/* Переключатель периода — пилюля на поверхности карточки, а не вдавленный
   в фон жёлоб. Активный период залит акцентом: раньше он отличался лишь
   светлым фоном на светлом, и на какой период смотришь, приходилось
   вычитывать. */
.tabs {
  display: inline-flex; background: var(--surface); border: 1px solid var(--border-soft);
  border-radius: var(--radius-md); padding: 3px; gap: 2px; box-shadow: var(--ring);
}
.tabs a {
  padding: 7px 14px; border-radius: var(--radius-sm); font-size: var(--t-sm); font-weight: 600;
  color: var(--text-dim);
}
.tabs a:hover { text-decoration: none; color: var(--text); }
.tabs a.active { background: var(--accent); color: var(--btn-ink); }

/* --------------------------------------------------------------- таблицы */

/* Таблицы мельче основного текста и с большим междустрочьем: так набирают
   финансовые отчёты — цифры держит не кегль, а воздух вокруг них. */
table { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }
th {
  text-align: left; font-weight: 550; font-size: var(--t-2xs); letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-dim);
  padding: 0 var(--s-3) var(--s-3); border-bottom: 1px solid var(--border);
}
/* Строки дышат: воздух вокруг цифр и есть то, чем финансовая таблица
   отличается от базы данных. Разделитель — только между строками, и
   волосяной; рамок у ячеек нет. */
td {
  padding: var(--s-3); border-bottom: 1px solid var(--border-soft);
  vertical-align: middle;
}
tr:last-child td { border-bottom: 0; }
/* Подсветка строки под курсором: в длинном списке операций взгляд теряет
   строку между «когда» и «сколько» — это разные края таблицы */
tbody tr:hover { background: var(--surface-2); }

/* Липкой шапки у таблиц нет намеренно: `.card` из-за `overflow-x: auto`
   сам становится областью прокрутки, и `position: sticky` внутри него
   прилипал бы к карточке, а не к экрану. Убрать overflow нельзя — на
   телефоне тогда едет весь макет. */
/* Числа и их заголовки — по левому краю, одинаково. Раньше заголовок
   стоял слева (общее правило `th`), а значение справа, и колонка читалась
   как две несвязанные полосы: подпись у одного края, цифры у другого.

   Обычно суммы выравнивают вправо — так у них совпадают разряды. Здесь
   выбрано влево осознанно (решение владельца, план п. 21.4): подпись и
   значение важнее держать вместе, а `tabular-nums` всё равно даёт
   одинаковую ширину знака, поэтому цифры не «дрожат». */
.num { text-align: left; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Дата со временем не должна переноситься посреди значения: «08.09.2026»
   и «23:00» — одна величина, а разорванная надвое она удваивает высоту
   каждой строки списка. Время при этом приглушено: ищут по дате. */
.cell-when { white-space: nowrap; }
/* Названия статей вида «Продукты/Закупка» рвались по косой черте, и
   строка тоже становилась двойной. Длинное имя лучше подрезать. */
.cell-category { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 20ch; }
.cell-who { white-space: nowrap; }
/* «08.09 🌙» — одна величина: дата смены и её вид. Разорванная надвое,
   она снова поднимала высоту строки. */
.cell-shift { white-space: nowrap; }

/* Десять колонок влезают только на широком экране. Первым уходит канал
   («сайт» или «бот»): он говорит, откуда пришла запись, — это нужно при
   разборе, а не при чтении списка. Раньше он делил класс с «Кто внёс» и
   спрятать его отдельно было нечем. */
/* Порог 1400, а не 1200: на обычном ноутбуке (1280–1366) десять колонок
   складываются впритык — замер дал 970px таблицы в 973px карточки, и
   крестик удаления оказывался на самом краю. Лишние три пикселя — не
   запас. */
@media (max-width: 1400px) {
  .col-channel { display: none; }
}
.amount-income { color: var(--income); font-weight: 550; }
.amount-expense { color: var(--expense); font-weight: 550; }

/* Метка остаётся скруглённой намеренно: овал отличает то, что читают,
   от того, что нажимают, — а кнопки рядом теперь прямоугольные. */
.pill {
  display: inline-block; padding: 2px 9px; border-radius: 20px;
  font-size: 12px; font-weight: 550; background: var(--surface-2); color: var(--text-dim);
  white-space: nowrap;
}
.pill.income { background: var(--income-soft); color: var(--income); }
.pill.expense { background: var(--expense-soft); color: var(--expense); }
/* Не рассчитано с тем, кому платят каждый день. Цвет расхода: деньги
   человеку причитаются, и это долг заведения, а не просто отметка. */
.pill.overdue { background: var(--expense-soft); color: var(--expense); }

/* ------------------------------------------------------- значки в строке */

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

   1. Один размер. Эмодзи были разного кегля — 💰 крупнее ✏️, — и строки
      подсказок начинались с разной высоты. Здесь 16px везде, кроме мест,
      где размер задан осознанно.
   2. Выключка по верху, а не по центру. Подсказка в две строки с
      центрированным значком выглядит так, будто значок висит в воздухе
      посередине абзаца.
   3. Значок не участвует в чтении вслух: он `aria-hidden` в макросе, а
      смысл несёт текст рядом. Там, где текста нет (кнопка правки,
      удаления, сброса пароля), стоит `aria-label`. */

/* Значок в строке текста.

   `vertical-align` по умолчанию — `baseline`: низ картинки садится на
   базовую линию, и центр значка оказывается ВЫШЕ оптического центра
   строчных букв. Замерено на кнопках выбора: оптический центр «о» лежит
   в 3,25 px над базовой, центр значка — в 8,05, то есть промах на
   4,8 px. Владелец 14.09 указал на это в четырёх местах сразу.

   `middle` — не подгонка, а ровно это и означает: середина коробки
   совмещается с базовой линией плюс половина роста строчных. После
   правки промах 0,05 px, и он остаётся нулевым при любом размере значка
   и шрифта — в отличие от поправок в пикселях, которые верны только для
   того кегля, под который написаны.

   На flex-детей `vertical-align` не действует, поэтому места, где
   значок уже centrирован через `align-items`, не двигаются вовсе —
   проверено замером до и после по всем страницам. */
.ico { width: 16px; height: 16px; flex: 0 0 auto; vertical-align: middle; }

/* Подсказка и баннер: значок слева, текст своей колонкой */
.hint, .banner { display: flex; gap: 10px; align-items: flex-start; }
.hint > .ico, .banner > .ico { margin-top: 1.5px; }
.banner b { font-weight: 600; }

/* Кнопка со значком: подпись рядом, а если подписи нет — квадрат по
   размеру пальца, а не приплюснутый овал */
.btn .ico { width: 15px; height: 15px; }
.btn:has(> .ico:only-child) { padding-left: 11px; padding-right: 11px; }

/* Стрелка изменения в чипе и галочка выбранного пункта в списках */
.chip .ico { width: 13px; height: 13px; }
.mark .ico { width: 12px; height: 12px; }

/* Пилюля способа расчёта и подобные метки */
.pill { display: inline-flex; align-items: center; gap: 5px; }
.pill .ico { width: 13px; height: 13px; }

/* Переключатели типа операции и способа расчёта в формах */
.choice label .ico, label > .ico { width: 16px; height: 16px; }

/* Пустое состояние: крупный приглушённый знак вместо крупного эмодзи */
.empty .empty-icon { display: block; margin-bottom: 10px; opacity: .45; line-height: 0; }
.empty .empty-icon .ico { width: 30px; height: 30px; }

/* Календарь смен: значок дня и ночи в клетке, значок уборки в строке */
.cal-slot-ico .ico { width: 14px; height: 14px; }
.cal-clean .ico { width: 13px; height: 13px; }
.cal-clean label { gap: 6px; }

/* Часовой пояс и выход в подвале панели */
.tz .ico, .tz-static .ico { width: 14px; height: 14px; }

/* Разбивка по видам смен в зарплате: значки мельче цифр рядом */
.pay-breakdown .ico { width: 13px; height: 13px; }

/* ------------------------------------------------- расчёты с сотрудниками */

/* Три денежные колонки таблицы. Начислено — обычным цветом: это ещё не
   деньги из кассы. Выплачено — приглушённо: факт, а не задача. Остаток —
   единственное, что требует действия, и потому единственное цветное. */
.pay-paid { color: var(--text-dim); }
.pay-rest { color: var(--expense); font-weight: 600; }
.pay-over { color: var(--brass); font-size: var(--t-sm); }
.pay-diverged { color: var(--brass); }
.pay-who { font-weight: 600; }
a.pay-who { color: inherit; }
a.pay-who:hover { color: var(--accent); }

/* Форма расчёта раскрывается прямо в строке — тем же приёмом, что
   переименование участника в «Команде»: поле ввода на виду в каждой
   строке было бы шумом, а details работает без скриптов. Всплывающим
   окном не делаем: окно поверх таблицы прячет как раз ту строку, ради
   которой открыто. */
.payout > summary { list-style: none; cursor: pointer; }
.payout > summary::-webkit-details-marker { display: none; }
.payout[open] {
  position: relative; display: block;
  background: var(--surface-2); border-radius: var(--radius-md);
  padding: 12px; margin-top: 8px; text-align: left; min-width: 260px;
}
.payout[open] > summary { margin-bottom: 10px; }
.payout .field { margin-bottom: 10px; }
.settle-all { margin-top: 16px; }
.settle-all[open] { background: var(--surface-2); }

/* Гамбургер: раньше это был символ ☰ кеглем 20-22, теперь значок */
.burger .ico { width: 22px; height: 22px; }

/* ----------------------------------------------------------------- топ-3 */

.bar-row { margin-bottom: 14px; }
.bar-row:last-child { margin-bottom: 0; }
.bar-head { display: flex; justify-content: space-between; gap: 12px; font-size: 14px; margin-bottom: 6px; }
.bar-track { height: 8px; background: var(--surface-2); border-radius: 999px; overflow: hidden; }
/* Латунь, а не акцент: полоса показывает долю расхода, и зелёный на ней
   читался бы как доход — акцент теперь того же цвета, что и приход. */
/* Градиент по длине, а не плоская заливка: у полосы появляется
   направление, и глаз читает её как величину, а не как метку.
   Латунь, а не акцент — это данные, см. правило ролей цвета выше. */
.bar-fill {
  height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--brass-2), var(--brass));
}

.hint {
  display: flex; gap: 9px; padding: 10px 12px; border-radius: 10px;
  background: var(--surface-2); font-size: 13.5px; margin-bottom: 8px;
}
.hint:last-child { margin-bottom: 0; }

/* ----------------------------------------------------------------- формы */

label { display: block; font-size: 13px; font-weight: 550; margin-bottom: 6px; }
/* Поля перечислялись по типам — text, number, date, search, — и стоило
   появиться новому типу, как он оставался неоформленным: 07.09.2026 так
   вышло с полем пароля, оно выглядело чужим рядом с логином. Поэтому
   правило от обратного: оформляем всё, кроме флажков, переключателей и
   кнопок, — их вид задаётся отдельно. */
input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=file]),
select, textarea {
  width: 100%; padding: 10px 12px; font: inherit;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
}
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
}
.field { margin-bottom: 14px; }
.row { display: flex; gap: 12px; flex-wrap: wrap; }
.row > * { flex: 1; min-width: 160px; }

/* Строка настройки: что настроено — слева, «Изменить» — справа.

   Один класс на все карточки «Настроек». Пока разметка копировалась в
   каждую своим набором инлайновых стилей, четвёртая («Как устроена
   работа», заведена 11.09) собралась иначе — заголовок и кнопка в одной
   строке, — и её кнопка встала выше, чем у трёх соседних. Замечание
   владельца со снимком.

   Скопированная разметка расходится не «если», а «когда»: копию правят
   по месту, оригинал не трогают. */
.setting-row { display: flex; align-items: center; gap: 12px; }
.setting-row > .setting-what { flex: 1 1 auto; min-width: 0; }
.setting-row > .btn { flex: 0 0 auto; }

/* Сетка полей формы.
   Замечание владельца 11.09: «где-то поля разбросаны хаотично».

   Причина была не в вёрстке ряда, а в том, что ширину объявлял каждый
   ряд отдельно и инлайном. В форме договора рядов три, и пропорции у
   них стояли разные — 2:2:1, потом 1:1:1, потом вовсе никакие: «Объект»
   оказывался вдвое шире «Платежа» под ним, а «Начало» — шире обоих.
   Каждый ряд был разумен сам по себе и ломал линию предыдущего.

   Колонки объявлены здесь один раз, и ряды выстраиваются в них. Третья
   узкая намеренно: в ней всегда стоит короткое значение — номер
   договора, число месяца, число дней.

   Отступ между полями по вертикали даёт `.field` своим `margin-bottom`,
   поэтому в `gap` только горизонталь: иначе расстояние удвоилось бы. */
.form-grid { display: grid; grid-template-columns: 2fr 2fr 1fr; gap: 0 12px; align-items: start; }
.form-grid.pairs { grid-template-columns: 1fr 1fr; }
/* Поле обязано уметь ужиматься: без этого содержимое (а у поля даты оно
   своей ширины) распирает колонку, и сетка перестаёт быть сеткой. */
.form-grid > .field { min-width: 0; }
/* Подпись занимает две строки всегда. Иначе поле с длинной подписью
   («Оператор фискальных данных») опускается на строку ниже соседа, и ряд
   — ровно то, ради чего сетка заводилась, — снова разъезжается. Текст
   прижат к низу, поэтому у однострочной подписи расстояние до поля
   остаётся прежним. */
.form-grid > .field > label { min-height: 2.3em; display: flex; align-items: flex-end; }
.form-grid > .field-wide { grid-column: 1 / -1; }
/* На узком экране колонки теряют смысл: в 1fr от трёхсот пикселей не
   помещается ни дата, ни подпись над ней. */
@media (max-width: 760px) {
  .form-grid, .form-grid.pairs { grid-template-columns: 1fr; }
}

/* Полю даты нужно больше, чем общие 160px.
   Браузер рисует в нём «дд.мм.гггг» и значок календаря, и при здешней
   типографике с отступами ему нужно около 175 пикселей. `width: 100%`
   из общего правила ниже этого предела не сжимается — поле ввода
   вылезало из своего столбца на соседний. Замечание владельца 11.09 о
   ряде «Начало / Окончание / Предупредить за»: «наплывают друг на
   друга».
   Это второй заход на одни грабли: 10.09 то же самое чинили в
   `.custom-period` (см. ниже), но лечили там один блок, а не причину.
   Больше минимума — значит строка перенесёт поле на новую строку
   вместо того, чтобы расплющить его. */
.row > .field:has(input[type=date]) { min-width: 190px; }

.btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 10px 17px; border-radius: var(--radius-control); border: 1px solid transparent;
  background: var(--accent); color: var(--btn-ink); font: inherit; font-weight: 600;
  cursor: pointer;
}
.btn:hover { filter: brightness(1.06); text-decoration: none; }
.btn.ghost {
  background: var(--surface); color: var(--text);
  border-color: var(--border-soft); box-shadow: var(--shadow-sm);
}
.btn.small {
  padding: 6px 12px; font-size: var(--t-sm); border-radius: var(--radius-sm);
  min-height: var(--control-small);
}
.btn.danger { background: transparent; color: var(--expense); border-color: transparent; padding: 4px 8px; }
.btn.danger:hover { background: var(--expense-soft); }

/* Кнопка из одного значка — квадрат одного размера, какой бы она ни
   была. «Выдать пароль» (ghost) и «убрать» (danger) стоят рядом в одной
   ячейке «Участников», и разный размер читается как разная важность —
   хотя разница была случайной: `.btn.danger` перебивал `padding`
   целиком и выходил на четыре пикселя ниже соседа.

   Правило ниже `.btn.danger` намеренно: вес у них одинаковый, и решает
   порядок. Размер — 30px, как у значков в подвале панели: это тот же
   род кнопки, и совпадение здесь не случайность, а правило. */
.btn.small:has(> .ico:only-child) {
  width: var(--control-small); height: var(--control-small);
  padding: 0; justify-content: center; flex: 0 0 auto;
  background: var(--surface); border-color: var(--border-soft); box-shadow: var(--shadow-sm);
}
/* Красным остаётся сам знак, а не кнопка: заливка появляется в наведении,
   как и была. Одинаковая форма нужна ровно затем, чтобы рядом стоящие
   «выдать пароль» и «убрать» читались как две кнопки, а не как кнопка и
   значок. Цветом они и так различаются. */
.btn.danger.small:has(> .ico:only-child) { color: var(--expense); }
.btn.danger.small:has(> .ico:only-child):hover {
  background: var(--expense-soft); border-color: var(--expense-soft);
}
/* Необратимое действие, ставшее главным на экране подтверждения:
   заливка, но не алая кнопка во всю ширину. Пугать не нужно — нужно,
   чтобы не нажали не глядя. */
.btn.danger.filled {
  background: var(--expense-soft); color: var(--expense);
  padding: 10px 17px;
}

/* --------------------------------------------------- уведомление о cookie

   Полоса внизу экрана, а не окно поверх страницы: документы, на которые
   она ссылается, должны оставаться читаемыми, пока выбор не сделан.
   Перекрыть их модальным окном значило бы потребовать согласия раньше,
   чем человек смог прочитать, на что соглашается. */
.cookie-note {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  display: flex; flex-wrap: wrap; gap: 14px 24px; align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  background: var(--surface); border-top: 1px solid var(--border);
  box-shadow: 0 -8px 24px rgba(20, 22, 26, .06);
}
.cookie-note-text { flex: 1 1 380px; min-width: 0; font-size: var(--t-sm); }
.cookie-note-text p { margin: 4px 0 0; color: var(--text-dim); }
/* Кнопки одного веса и в один ряд: разный вес — это уже подсказка, какую
   нажать, а подсказывать здесь нельзя. */
.cookie-note-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.cookie-note-actions .btn { flex: 0 0 auto; }
@media (max-width: 700px) {
  /* На телефоне полоса стояла столбиком, и текст с `flex: 1 1 380px`
     растягивался по высоте на весь свободный объём: уведомление
     закрывало почти весь экран пустым полем между текстом и кнопками.
     Столбик оставляем, но текст больше не растёт, а кнопки — в ряд:
     каждая занимает половину, «Настроить» — строкой под ними. */
  .cookie-note { flex-direction: column; align-items: stretch; gap: 10px; padding: 12px 14px; }
  .cookie-note-text { flex: 0 0 auto; font-size: 12.5px; }
  .cookie-note-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
  .cookie-note-actions .btn { justify-content: center; padding: 9px 10px; font-size: 13px; }
  .cookie-note-actions .btn.ghost { grid-column: 1 / -1; }
}
/* Полоса не должна перекрывать последнюю строку страницы. Три обёртки —
   три оболочки: кабинет, страницы вне кабинета и вход с приглашением.
   У последней своя мера: она центрирует карточку по высоте экрана, и
   отступ снизу сдвигает её вверх ровно на высоту полосы. */
/* Полоса зафиксирована внизу экрана и потому лежит поверх последней
   строки страницы — а последняя строка везде одна и та же: ссылки на
   документы, ради которых полоса и показывается. Отступ снизу отдаёт ей
   место.
   Правила идут после медиазапроса выше намеренно: они одинаковой силы, и
   выигрывает то, что ниже. Первая попытка положить мобильный отступ
   внутрь медиазапроса не сработала именно поэтому — телефонная страница
   получала настольные 180 пикселей под полосу высотой 263. */
body:has(.cookie-note) .legal-wrap,
body:has(.cookie-note) .main { padding-bottom: 180px; }
body:has(.cookie-note) .login-wrap { padding-bottom: 200px; }
@media (max-width: 700px) {
  /* На телефоне полоса встаёт столбиком: текст, два ряда кнопок — около
     270 пикселей. Отступ с запасом, чтобы не подгонять его при каждой
     правке формулировки. */
  body:has(.cookie-note) .legal-wrap,
  body:has(.cookie-note) .main,
  body:has(.cookie-note) .login-wrap { padding-bottom: 310px; }
}

.segmented { display: flex; gap: 8px; }
.segmented input { position: absolute; opacity: 0; pointer-events: none; }
.segmented label {
  flex: 1; margin: 0; text-align: center; padding: 10px;
  border: 1px solid var(--border); border-radius: 10px; cursor: pointer;
  background: var(--surface); font-weight: 550;
}
.segmented input:checked + label { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }

.suggestion {
  border: 1px solid var(--accent); background: var(--accent-soft);
  border-radius: 10px; padding: 12px 14px; margin-bottom: 16px; font-size: 14px;
}

.empty { text-align: center; padding: 40px 20px; color: var(--text-dim); }
.empty .empty-title { color: var(--text); font-weight: 550; display: block; margin-bottom: 4px; }
.empty .btn { margin-top: 12px; }

.pager { display: flex; gap: 8px; align-items: center; margin-top: 16px; font-size: 14px; }

.login-wrap { display: grid; place-items: center; min-height: 100vh; padding: 20px; }
.login-card { width: 100%; max-width: 380px; position: relative; }

/* Заставка страницы входа (см. _login_backdrop.html).

   Цвета берутся из палитры — обе темы получаются сами, без второго
   набора файлов. Просьба владельца 15.09: крупнее, с движением и ярче,
   чем первая редакция. Это сознательное отступление от Р-009
   («сдержанный вид») и только здесь: страница входа — витрина, а не
   рабочий экран, и на ней нет ни одной цифры, которую можно заслонить.

   Движение — только `transform` и `opacity`: их считает видеокарта, и
   раскладка не пересчитывается. Ни скрипта, ни стороннего файла: вход
   открывается первым, и лишний запрос здесь дороже всего. */
.login-backdrop {
  position: fixed; inset: 0; overflow: hidden;
  pointer-events: none; z-index: 0;
}
.login-wrap { position: relative; z-index: 1; }

/* --- уходящая в перспективу сетка --- */
.lb-scene { position: absolute; inset: 0; perspective: 620px; overflow: hidden; }
.lb-floor {
  position: absolute; left: -60%; right: -60%; bottom: -14%; height: 128%;
  transform: rotateX(74deg); transform-origin: 50% 100%;
  background-image:
    repeating-linear-gradient(to right, var(--accent) 0 2px, transparent 2px 90px),
    repeating-linear-gradient(to bottom, var(--accent) 0 2px, transparent 2px 90px);
  opacity: .30;
  /* Растворение к горизонту: без него сетка обрывается поперёк экрана
     и вся глубина рассыпается */
  -webkit-mask-image: linear-gradient(to top, #000 8%, transparent 78%);
          mask-image: linear-gradient(to top, #000 8%, transparent 78%);
  animation: lb-floor 9s linear infinite;
}
@keyframes lb-floor { to { background-position: 0 90px; } }

/* --- световые пятна --- */
.lb-glow {
  position: absolute; display: block; border-radius: 50%;
  filter: blur(90px); opacity: .40;
}
.lb-glow-a {
  width: 620px; height: 620px; left: -140px; top: -180px;
  background: var(--accent-2); animation: lb-breathe 14s ease-in-out infinite;
}
.lb-glow-b {
  width: 520px; height: 520px; right: -120px; bottom: -160px;
  background: var(--brass-2); opacity: .26;
  animation: lb-breathe 18s ease-in-out infinite reverse;
}
@keyframes lb-breathe {
  0%, 100% { transform: scale(1)    translate3d(0, 0, 0); }
  50%      { transform: scale(1.14) translate3d(2%, -3%, 0); }
}

/* --- сам график --- */
.lb-chart { position: absolute; inset: 0; width: 100%; height: 100%; }

.lb-bars rect {
  fill: var(--accent); opacity: .16;
  transform-box: fill-box; transform-origin: 50% 100%;
  animation: lb-bar 7s ease-in-out infinite;
}
/* Столбцы дышат вразнобой: одновременно — это пульс, а не ритм недели.
   Задержки по порядку столбца, а не переменной в разметке: переменная
   потребовала бы значения по умолчанию в палитре, хотя к цвету и
   размерам отношения не имеет. */
.lb-bars rect:nth-child(1) { animation-delay: 0s; }
.lb-bars rect:nth-child(2) { animation-delay: -.9s; }
.lb-bars rect:nth-child(3) { animation-delay: -1.8s; }
.lb-bars rect:nth-child(4) { animation-delay: -2.7s; }
.lb-bars rect:nth-child(5) { animation-delay: -3.6s; }
.lb-bars rect:nth-child(6) { animation-delay: -4.5s; }
.lb-bars rect:nth-child(7) { animation-delay: -5.4s; }
@keyframes lb-bar {
  0%, 100% { transform: scaleY(.86); }
  50%      { transform: scaleY(1); }
}

.lb-area-top { stop-color: var(--income); stop-opacity: .34; }
.lb-area-bottom { stop-color: var(--income); stop-opacity: 0; }
.lb-stroke-a { stop-color: var(--accent-2); }
.lb-stroke-b { stop-color: var(--income); }

.lb-line {
  fill: none; stroke: url(#lb-stroke); stroke-width: 6; opacity: .85;
  stroke-linecap: round; stroke-linejoin: round;
  /* Линия прочерчивается один раз при открытии страницы и остаётся.
     Бесконечная перерисовка на экране входа — назойливость, а не жизнь. */
  stroke-dasharray: 1700; stroke-dashoffset: 1700;
  animation: lb-draw 2.4s cubic-bezier(.22, .61, .36, 1) .25s forwards;
}
.lb-line2 {
  fill: none; stroke: var(--brass-2); stroke-width: 4; opacity: .55;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 1700; stroke-dashoffset: 1700;
  animation: lb-draw 2.8s cubic-bezier(.22, .61, .36, 1) .5s forwards;
}
@keyframes lb-draw { to { stroke-dashoffset: 0; } }

.lb-dot {
  fill: var(--income); opacity: 0;
  animation: lb-dot 1s ease-out 2.4s forwards, lb-pulse 3.4s ease-in-out 3.4s infinite;
  transform-box: fill-box; transform-origin: 50% 50%;
}
@keyframes lb-dot { to { opacity: .9; } }
@keyframes lb-pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.22); }
}

/* На узком экране форма занимает почти весь экран, и от заставки
   остаются обрезки. Приглушаем до намёка. */
@media (max-width: 700px) {
  .login-backdrop { opacity: .6; }
  .lb-line { stroke-width: 5; }
}

/* Движение выключается по системной настройке: всё встаёт в конечное
   положение, а не исчезает — картинка остаётся целой. */
@media (prefers-reduced-motion: reduce) {
  .lb-floor, .lb-glow, .lb-bars rect { animation: none; }
  .lb-line, .lb-line2 { animation: none; stroke-dashoffset: 0; }
  .lb-dot { animation: none; opacity: .9; }
}
/* Кому лишний слой мешает — фон убирается целиком. */
@media (prefers-reduced-transparency: reduce) {
  .login-backdrop { display: none; }
}

/* Разбивка нал/безнал под суммой KPI */
.split-line {
  display: flex; gap: 12px; flex-wrap: wrap;
  margin-top: 8px; padding-top: 8px;
  border-top: 1px dashed var(--border);
  font-size: 12.5px; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* Быстрая простановка способа расчёта у старых операций */
.inline-set { display: flex; gap: 4px; }
.inline-set .btn { border-color: var(--border); }
/* Поле даты, и почему у него три лишние строки.
   Замечание владельца 11.09.2026 со снимками с iPad: рамка «Начало»
   уходит вправо под «Окончание», «Окончание» — под «Предупредить за».
   На Маке и в Chrome не воспроизводилось, и два захода я чинил не то:
   думал, что не хватает ширины КОЛОНКЕ, и поднимал ей минимум. Колонка
   была нужного размера — вылезал сам ввод.

   WebKit на iOS даёт `input[type=date]` собственную ширину по
   содержимому и `width: 100%` её не перебивает: `min-width` у контрола
   разрешается в размер содержимого, а не в ноль. Поэтому здесь три
   строки, и каждая закрывает свою дыру:

   `min-width: 0` — разрешает вводу сжиматься до своей колонки;
   `max-width: 100%` — страховка на случай, если движок всё же попросит
   больше: обрезанное поле лучше поля, наехавшего на соседнее;
   `appearance: none` — снимает у контрола родной размер, из-за которого
   всё и начиналось.

   `height: 3em` — следствие предыдущей строки и второе замечание
   владельца, 11.09: «строчки для ввода дат стали узкими, сделай их как
   остальные, даже если значение они не содержат». Сняв родной размер,
   мы сняли и родную высоту, а пустому полю набрать её нечем — текста
   внутри нет, строки нет, остаются одни отступы. На iPad такое поле
   схлопывалось в полоску. Три em при шрифте в 15px дают ровно те же
   45px, что у текстового поля рядом, и число следует за шрифтом, а не
   прибито гвоздём.

   Отступы здесь не задаются намеренно: общее правило для полей ввода
   выше перебивает этот селектор по весу (пять `:not()` против одного
   атрибута), и написанное тут всё равно не применилось бы. Пусть лучше
   не будет строки, чем будет строка, которая врёт. */
input[type=date] {
  -webkit-appearance: none; appearance: none;
  width: 100%; min-width: 0; max-width: 100%;
  height: 3em; font: inherit;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
}

/* Выбор периода: пресеты и произвольный диапазон */
.period-bar { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-bottom: 18px; }
/* Форму своего периода нельзя сжимать: у неё внутри перенос, и сжатая
   она складывается в две строки — кнопка «Показать» уезжает под поля
   дат. Замечание владельца 15.09 со снимком с iPad.

   На рабочем столе в Chrome этого не видно: там строка переносится
   целиком, и форма сохраняет свои 438 px. Safari вместо переноса ужал
   её до ~375 — воспроизведено замером: при ширине 375 высота формы
   растёт с 59 до 101, то есть строк становится две.

   `flex: 0 0 auto` убирает саму возможность сжатия: не поместилась
   рядом с вкладками — уходит на свою строку целиком, а не ломается
   внутри.

   Правило ограничено рабочим столом нарочно, и это не перестраховка:
   мобильное `flex: 1 1 100%` объявлено ВЫШЕ по файлу, вес у селекторов
   одинаковый, и без рамки по ширине это правило перебивало бы его даже
   на телефоне. Проверено замером: форма получала свои 438 px при полосе
   в 354 и вылезала за экран. */
@media (min-width: 901px) {
  .period-bar > .custom-period { flex: 0 0 auto; }
}
/* display:flex перебивает браузерный [hidden] — без этой строки форма
   произвольного периода видна всегда и на телефоне съедает пол-экрана */
.custom-period[hidden] { display: none; }
.custom-period {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 6px 10px;
}
/* Подписи и поля в одну строку: «с [дата] по [дата] Показать». Общее
   правило делает `label` блочным, а полю ввода задаёт `width: 100%`
   пятью селекторами `:not()` — по весу оно перебивает всё, что здесь
   можно написать через класс. Поэтому у поля не `width`, а `max-width`:
   ограничение сверху вес не разбирает (тот же приём, что у `.rate-input`).
   Замечание владельца 10.09: «даты от и до открываются друг над другом,
   хотя можно было бы немного уменьшить поля и разместить их в ряд». */
.custom-period label { display: inline; margin: 0; color: var(--text-dim); font-weight: 500; }
.custom-period input[type=date] { max-width: 150px; flex: 1 1 120px; padding: 6px 9px; }

/* Фильтр операций. Одна форма вместо ряда вкладок: способов расчёта было
   четыре, а отборов теперь пять — вкладками это превратилось бы в решётку
   из десятков ссылок. Поиск тянется на всю оставшуюся ширину. */
.filters {
  display: flex; flex-wrap: wrap; gap: 10px 14px; align-items: flex-end;
  margin-bottom: 16px; padding: 14px 16px;
}
.filters .filter { margin: 0; flex: 0 1 auto; min-width: 140px; }
.filters .filter.grow { flex: 1 1 200px; }
.filters .filter > span {
  display: block; font-size: 12px; font-weight: 500;
  color: var(--text-dim); margin-bottom: 4px;
}
.filters select, .filters input[type=search] {
  width: 100%; padding: 8px 10px; font: inherit;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
}
.filters input[type=search]:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.filter-actions { display: flex; gap: 8px; align-items: center; }

/* Итог по отобранному: отфильтровать и не увидеть сумму — половина ответа */
.filter-summary {
  display: flex; flex-wrap: wrap; gap: 6px 20px;
  margin: -6px 0 16px; padding: 0 4px; font-size: 13px; color: var(--text-dim);
}

/* Аналитика: ABC и прогноз */
.abc {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: 7px;
  font-size: 12px; font-weight: 650; color: var(--btn-ink);
}
.abc-A { background: var(--income); }
.abc-B { background: var(--brass); }
.abc-C { background: var(--text-dim); }
.abc-fill-A { background: var(--income); }
.abc-fill-B { background: var(--brass); }
.abc-fill-C { background: var(--text-dim); }
/* Накопительный процент и полоска к нему.

   Было `justify-content: flex-end`: содержимое прижималось к правому
   краю ячейки, а заголовок столбца — к левому, как у всех `.num`.
   Замерено: между «Накопительно» и первым знаком числа 47,6 px пустоты,
   и столбец читался съехавшим. Замечание владельца 15.09.

   Прижатие вправо делалось не зря — так полоски вставали столбиком.
   Поэтому не «выровнять влево», а выровнять влево И задать числу
   ширину: 4ch — это ровно «100%», а цифры здесь моноширинные
   (`font-variant-numeric: tabular-nums` у `.num`). Тогда заголовок
   совпадает с содержимым, а полоски по-прежнему начинаются с одного
   места. Проверено замером: расхождение 0, у полосок одна позиция. */
.cum-cell { display: flex; align-items: center; justify-content: flex-start; gap: 10px; }
.cum-cell > span { min-width: 4ch; }

.forecast-item .label { color: var(--text-dim); font-size: 13px; font-weight: 500; }
.forecast-item .value {
  font-size: 24px; font-weight: 650; letter-spacing: -.03em;
  margin: 5px 0 3px; font-variant-numeric: tabular-nums;
}
.income-text { color: var(--income); }
.expense-text { color: var(--expense); }

/* Баннеры: предупреждение и подтверждение действия */
.banner.error { background: var(--expense-soft); border-color: var(--expense); }
.banner.ok { background: var(--income-soft); border-color: var(--income); }

/* Строка категории: поле растягивается, кнопки не переносятся.
   Общий .row тут не годится — его min-width на детях ломает строку. */
.cat-row { display: flex; align-items: center; gap: 8px; }
.cat-rename { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }
.cat-rename input { flex: 1; min-width: 0; }
.cat-row .btn { flex: 0 0 auto; white-space: nowrap; }

/* Широкие таблицы не должны растягивать страницу.
   Без этого на телефоне по горизонтали ездит весь макет вместе с меню:
   в grid и flex у детей min-width по умолчанию auto, поэтому карточка
   раздувается под ширину таблицы вместо того, чтобы её прокручивать. */
.grid > *, .layout > *, .split > * { min-width: 0; }
.card { overflow-x: auto; }

@media (max-width: 700px) {
  /* На телефоне поле даты занимает строку целиком.

     Две даты рядом не помещаются в 375 пикселей даже при минимальной
     ширине: 190 + 190 плюс отступ — это 392. Поднятый ради настольного
     экрана минимум (см. `.row > .field:has(input[type=date])`) без этой
     строки выталкивал бы вторую дату за край. */
  .row > .field:has(input[type=date]) { min-width: 100%; }

  /* Канал ввода, тип и смена — наименее нужные колонки в узком экране:
     тип виден по знаку и цвету суммы, а смена — на своей странице */
  .col-source, .col-type, .col-shift { display: none; }
  /* См. `.col-accrued`: в узком экране сроки и остаток важнее истории */
  .col-accrued { display: none; }
  .only-wide { display: none; }
  /* В узком экране список договоров занимает свою строку целиком.

     Рядом с кнопкой он сжимался до сорока пикселей — до одного прочерка
     вместо названия объекта, — и выбрать в нём было нечего. Строка сама
     по себе не длинная: дата, сумма и выбор, — а перенос дешевле
     нечитаемого поля. */
  .link-charge { max-width: 100%; flex: 1 1 100%; }
  .link-form { justify-content: stretch; }
  /* Поля по два в ряд: в один столбец фильтр занимал целый экран, и
     до первой операции приходилось скроллить */
  .filters { padding: 12px; gap: 8px 10px; }
  .filters .filter { flex: 1 1 calc(50% - 5px); min-width: 0; }
  .filters .filter.grow { flex: 1 1 100%; }
  .filters .filter > span { font-size: 11px; }
  .filters select, .filters input[type=search] { padding: 7px 9px; font-size: 13px; }
  .filter-actions { flex: 1 1 100%; }
  .filter-actions .btn { flex: 1; }
  .main { max-width: 100%; }
  .page-head h1 { font-size: var(--t-lg); }
  .kpi .value { font-size: var(--t-xl); }
  th, td { padding: 9px 7px; }
  th { font-size: 11px; }
  table { font-size: 13px; }
}

/* На телефоне список операций перестаёт быть таблицей и становится
   карточками: в семь колонок самое важное — сумма — уезжало за правый
   край, и её приходилось доскроллить. В приложении про деньги так нельзя. */
@media (max-width: 700px) {
  .ops { display: block; }
  .ops thead { display: none; }
  .ops tbody, .ops tr, .ops td { display: block; }
  .ops tr {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    gap: 2px 12px;
    padding: 11px 0;
    border-bottom: 1px solid var(--border);
  }
  .ops tr:last-child { border-bottom: 0; }
  .ops td { border: 0; padding: 0; }

  .ops .cell-category { grid-column: 1; font-weight: 550; }
  .ops .cell-amount { grid-column: 2; grid-row: 1; text-align: right; font-size: 15px; }
  .ops .cell-when { grid-column: 1; font-size: 12px; }
  .ops .cell-text { grid-column: 1; font-size: 12px; }
  .ops .cell-text:empty { display: none; }
  .ops .cell-method { grid-column: 1; margin-top: 4px; }
  /* Специфичность `.ops td` перебивает `.col-source` и `.col-type`,
     поэтому скрытие колонок нужно повторить внутри карточного макета.
     Тип не теряется: сумма в карточке и так со знаком и цветом. */
  .ops .col-source, .ops .col-type, .ops .col-shift { display: none; }
  .ops .cell-actions { grid-column: 2; grid-row: 2 / span 3; align-self: center; }
  .ops .row-actions { justify-content: flex-end; gap: 8px; }
  /* Замер на 375px: правка 44×34, удаление 26×30 — самая опасная кнопка
     была самой мелкой и стояла вплотную к соседней. Промах пальцем вёл
     в удаление (подтверждение спасало, но спрашивать за промах — не то
     же самое, что не промахнуться). Обе мишени доводим до 40px и
     разводим зазором. */
  .ops .row-actions .btn { min-width: 40px; min-height: 40px; padding: 0 10px; }

  /* Поле занимает всю ширину, кнопка переименования уходит под него,
     а крестик удаления остаётся справа по центру строки: иначе он либо
     уезжает за край, либо забирает себе отдельную строку */
  .cat-rename { flex-wrap: wrap; }
  .cat-rename input { flex: 1 1 100%; }

  /* В таблице правил пять колонок не влезают, и кнопка «забыть правило»
     оказывается за краем. Способ расчёта и счётчик — справочные, фраза,
     категория и кнопка — нет. */
  .col-rule-method, .col-rule-times { display: none; }
}

/* Горизонтальному сравнению статей нужна высота: иначе Chart.js молча
   прячет часть подписей категорий, и непонятно, где чей столбик. */
/* Обёртка с явной высотой обязательна для каждого графика.
   `.card` из-за `overflow-x: auto` — область прокрутки, а Chart.js в
   такой не может вычислить доступную ширину и схлопывается до
   собственных 300×150. Поэтому размер задаёт обёртка, а у графика
   выключен `maintainAspectRatio`. */
.chart-box { position: relative; }
/* Холст выведен из потока намеренно. Пока высота коробки была жёсткой,
   это роли не играло; теперь она тянется, и холст, оставаясь в потоке,
   сам бы её задавал: Chart.js подгоняет холст под коробку, коробка
   растёт от холста — храповик, который при каждом ресайзе поднимает
   график на несколько десятков пикселей и назад не опускает.
   Проверено замером: 260 → 322 за пять изменений ширины. */
.chart-box > canvas { position: absolute; inset: 0; }
/* Высоты — нижняя граница, а не точная величина: карточку, растянутую
   до высоты соседней, график должен заполнить целиком, иначе пустота
   просто переезжает внутрь карточки. */
.chart-tall { min-height: 260px; }
.chart-md { min-height: 250px; }
.chart-sm { min-height: 210px; }
@media (max-width: 700px) { .chart-tall { min-height: 400px; } }

/* На широком экране блоки «сводка» и «аналитика» раскладываются плотнее:
   иначе один график растягивается на всю ширину и становится плоским. */
@media (min-width: 1700px) {
  .main { padding: 30px 40px 70px; }
  .split { grid-template-columns: 1.5fr 1fr; }
  .kpi-grid { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
}

/* ------------------------------------------ юридические документы */

/* Страница документа читается подряд, а не просматривается: узкая
   колонка, крупнее строка, больше воздуха между абзацами. */
.legal-wrap { max-width: 820px; margin: 0 auto; padding: 26px 20px 60px; }
.legal-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin-bottom: 18px;
}
.legal-head .brand { margin: 0; padding: 0; }

.legal { font-size: 15px; line-height: 1.7; padding: 28px 32px; }
.legal h1 { font-size: 24px; margin: 0 0 18px; }
.legal h2 { font-size: 19px; margin: 26px 0 10px; }
.legal h3 { font-size: 16px; margin: 22px 0 8px; }
.legal p { margin: 0 0 12px; }
.legal ul, .legal ol { margin: 0 0 14px; padding-left: 22px; }
.legal li { margin-bottom: 6px; }
.legal blockquote {
  margin: 14px 0; padding: 12px 16px;
  background: var(--surface-2); border-left: 3px solid var(--accent);
  border-radius: 0 10px 10px 0;
}
.legal blockquote p:last-child { margin: 0; }
.legal hr { border: 0; border-top: 1px solid var(--border); margin: 24px 0; }
/* Таблицы юридического документа — реестры, а не отчёты: в ячейке лежит
   предложение, а не цифра. Поэтому выравнивание по верху (у отчётных
   таблиц оно по середине — там строка в одну строку) и рамка сверху, без
   которой перечень сливается с текстом вокруг. Ширину не задаём: на
   узком экране карточка `.legal` прокручивается сама. */
.legal table { margin: 0 0 16px; }
.legal thead th { padding-top: 4px; }
.legal td { vertical-align: top; }
.legal td code { white-space: nowrap; }
.legal code {
  background: var(--surface-2); border-radius: 5px; padding: 1px 5px;
  font-family: "JetBrains Mono", ui-monospace, Menlo, monospace; font-size: .88em;
}
.legal-foot { margin-top: 20px; text-align: center; font-size: 13px; }

.legal-links { margin-top: 8px; font-size: 12px; }

/* Согласие: флажок и текст рядом, а не текст поверх флажка */
.consent {
  display: flex; gap: 10px; align-items: flex-start;
  margin: 16px 0; padding: 14px 16px;
  background: var(--surface-2); border-radius: 12px;
  font-size: 13.5px; line-height: 1.5;
}
.consent input[type=checkbox] {
  width: 18px; height: 18px; margin: 1px 0 0; flex: 0 0 auto;
  accent-color: var(--accent); cursor: pointer;
}
.consent label { margin: 0; font-weight: 400; font-size: inherit; cursor: pointer; }
/* Кнопка неактивна, пока флажок не поставлен — и это видно */
.btn[disabled] { opacity: .5; cursor: not-allowed; filter: none; }

/* Переименование участника — форма прячется в details, чтобы поля ввода
   не шумели в каждой строке таблицы. Карандаш появляется приглушённым и
   набирает контраст при наведении: подсказка есть, но взгляд не тянет.

   Форма всплывает поверх строки, как список бизнесов в переключателе.
   В потоке она растягивала ячейку и разгоняла таблицу по высоте — в
   строке на одного человека ломался весь список. */
.rename { display: inline-block; position: relative; }
.rename > summary {
  display: inline-block;
  cursor: pointer;
  list-style: none;
  opacity: .45;
  font-size: 12px;
  padding: 0 4px;
  border-radius: 5px;
}
.rename > summary::-webkit-details-marker { display: none; }
.rename > summary:hover,
.rename[open] > summary { opacity: 1; background: var(--surface-2); }
.rename > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rename form {
  position: absolute; z-index: 20; left: 0; top: 100%;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; box-shadow: var(--shadow);
  padding: 8px; margin-top: 2px !important;
  flex-wrap: nowrap;
}

/* Столбец «Доступ» в «Команде»: две двери к одному человеку — логин на
   сайт и привязанный Telegram. Строками одна под другой, а не в ряд:
   в ряд они на телефоне переносятся посередине формы и читаются как
   одно поле, разорванное пополам. */
.access-row {
  display: flex;
  align-items: center;
  gap: 5px;
  min-height: 22px;
}
.access-row + .access-row { margin-top: 3px; }
.access-row .ico { flex: 0 0 auto; opacity: .55; }
.access .linked {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--accent);
}
.access .linked .ico { opacity: 1; }
/* Кнопка «привязать» в тесной ячейке: без собственных отступов она
   поднимает строку таблицы выше остальных. */
.access .btn.small { padding: 1px 7px; }
/* Выданная ссылка показывается в строке того, кому выдана, — и это её
   единственная проблема: поле ввода в ячейке тянет таблицу за собой.
   Ширину задаём здесь, а не в разметке: карточка и так прокручивается
   (`.card { overflow-x: auto }`), но растягивать её на весь адрес
   значит заставлять возить страницу вбок ради одной строки. */
.access .field { max-width: 260px; }
.access .field input { width: 100%; font-size: 12px; }
@media (max-width: 700px) {
  .access .field { max-width: 190px; }
}

/* Подпись под спарклайном. Нужна там, где период однодневный: число в
   карточке за один день, а микро-график — за неделю, и без подписи ноль
   под живой кривой читается как ошибка в данных. */
.spark-note {
  font-size: 10.5px;
  color: var(--text-dim);
  letter-spacing: .01em;
  margin-top: 2px;
}

/* ------------------------------------------------------ график смен */

/* Календарь — обычная таблица, без библиотеки: сетка 7×N и подписи
   месяцев не стоят зависимости в образе. Тот же принцип, по которому
   в проекте свои шрифты и инлайновые SVG вместо чужих пакетов. */
.cal-wrap { overflow-x: auto; }
.cal { table-layout: fixed; min-width: 720px; border-collapse: separate; border-spacing: 6px; }
.cal th {
  padding: 0 0 4px; text-align: center; border: 0;
  font-size: 10.5px; letter-spacing: .07em;
}
.cal-cell {
  vertical-align: top; padding: 8px; height: 96px; border: 0;
  background: var(--surface-2); border-radius: var(--radius-sm);
}
/* Клетка не подсвечивается под курсором: в таблице операций подсветка
   помогает не потерять строку, а здесь строка — это неделя, и мигание
   всей недели только мешает */
.cal tbody tr:hover .cal-cell { background: var(--surface-2); }

.cal-date {
  display: flex; align-items: center; gap: 6px;
  font-size: 12.5px; font-weight: 700; color: var(--text-dim); margin-bottom: 6px;
}
/* Сегодня — акцентом и рамкой, как и всё активное в интерфейсе */
.cal-today { box-shadow: inset 0 0 0 2px var(--accent); }
.cal-today .cal-date { color: var(--accent); }
.cal-mark {
  font-size: 9px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em;
  background: var(--accent); color: var(--btn-ink); padding: 1px 6px; border-radius: 999px;
}
/* Дни соседних месяцев: видны, но явно не «свои» */
.cal-outside { background: transparent; }
.cal-outside .cal-date { opacity: .4; }

.cal-slot { display: flex; align-items: center; gap: 5px; margin-bottom: 4px; min-width: 0; }
.cal-slot:last-child { margin-bottom: 0; }
.cal-slot-ico { font-size: 11px; flex: 0 0 auto; line-height: 1; }
.cal-slot form { flex: 1; min-width: 0; }
/* Поле выбора в клетке — мелкое и без рамки, пока его не трогают:
   шестьдесят обведённых полей превратили бы месяц в решётку */
.cal-slot select {
  width: 100%; padding: 3px 4px; font-size: 11.5px;
  border-color: transparent; background: var(--surface); border-radius: 7px;
}
.cal-slot select:hover { border-color: var(--border); }
.cal-who { font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Бригада в клетке. Управление спрятано в `details`, потому что шесть
   имён с кнопками в клетку месячной сетки не помещаются — а владелец
   смотрит с планшета, где она и так тесная. Свёрнутое состояние
   показывает два имени и «+N»: этого хватает, чтобы узнать смену. */
/* Ужиматься обязана сама `details`, а не только строка внутри неё.
   `overflow: hidden` на строке считается от ширины родителя, а родитель
   — элемент flex-строки клетки и без `min-width: 0` растягивается по
   содержимому: имена вылезали в соседнюю колонку, и обрезка не
   срабатывала никогда. Та же пара правил уже стоит на `form` рядом. */
.cal-crew { flex: 1; min-width: 0; }
.cal-crew > summary {
  font-size: 11.5px; cursor: pointer; list-style: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-crew > summary::-webkit-details-marker { display: none; }
.cal-crew > summary:hover { color: var(--accent); }
.cal-crew[open] > summary { color: var(--text-dim); margin-bottom: 3px; }
.cal-crew-row {
  display: flex; align-items: center; gap: 4px;
  font-size: 11.5px; margin-bottom: 2px;
}
.cal-crew-row span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* Кнопка снятия мелкая, но не меньше пальца: клетка тесная, а промах
   здесь снимает человека со смены.

   Перебивает общий квадрат значковой кнопки намеренно и с весом: в
   клетке месячной сетки тридцать два пикселя занимают строку целиком, и
   имени рядом с ними не остаётся. Это единственное место, где размер
   отступает от общего, и оно названо здесь вслух. */
.cal-crew-row .btn.small:has(> .ico:only-child) {
  width: 26px; height: 26px; min-width: 26px; min-height: 26px;
}

/* Доход мимо смен в клетке дня — доставка и прочее. Приглушённее
   выручки смены и со своим словом: это доход дня, но не то, что сдала
   смена, и путать их нельзя. Отступ общий с суммами смен, чтобы цифры
   стояли одним столбиком (см. `--cal-money-indent`). */
.cal-away {
  display: block; margin: 2px 0 0 var(--cal-money-indent);
  font-size: 11px; font-weight: 500; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* Итог выручки за сутки — только когда смен в сутках больше одной.
   При одной смене он равен её же строке, и повтор того же числа ничего
   не сообщает. */
.cal-day-total {
  display: block; padding-top: 3px;
  border-top: 1px solid var(--border-soft);
  font-size: 11.5px; font-weight: 700; color: var(--brass);
  font-variant-numeric: tabular-nums;
  /* Отступ слева тот же, что у сумм по сменам: итог стоит под ними в
     одну колонку, а не левее. Замечание владельца 12.09 — у сумм смен
     отступ был, у итога нет, и столбик цифр ломался на последней строке.
     Значение берётся из общего места (см. `--cal-money-indent`), иначе
     одно из двух однажды поедет, а второе останется. */
  margin: 4px 0 0 var(--cal-money-indent);
}
/* Подпись «Итого:» — приглушённая и не жирная: она называет число, а
   главное здесь само число. Просьба владельца 14.09: без подписи итог
   читался как ещё одна смена, просто без имени. */
.cal-total-label { font-weight: 500; color: var(--text-dim); }
/* В сетке день недели называет заголовок столбца, поэтому в клетке он
   лишний. Появляется только в ленте на телефоне, где столбцов нет. */
.cal-weekday { display: none; color: var(--text-dim); font-weight: 400; margin-left: 6px; }

/* Выручка смены. Латунь, а не зелёный: это величина, а не действие и не
   «приход против расхода» — то же правило ролей цвета, что на графиках.
   Табличные цифры, чтобы суммы в соседних клетках стояли столбиком. */
.cal-money {
  display: block; margin: 2px 0 0 var(--cal-money-indent);
  font-size: 11.5px; font-weight: 600; color: var(--brass);
  font-variant-numeric: tabular-nums; text-decoration: none;
}
.cal-money:hover { text-decoration: underline; }

@media (max-width: 700px) {
  /* Месяц семью колонками на телефоне не читается. Замер на 375px:
     таблица 655px внутри контейнера 301px — видно три дня из семи,
     остальные за краем, о прокрутке ничто не намекает, а имена в
     клетках обрезаются стрелкой списка («Настя С⌄»). Прежняя правка
     лечила высоту клетки, но не ширину — то есть не то.

     Приём тот же, что со списком операций рядом: таблица перестаёт
     быть таблицей и становится днями подряд, сверху вниз. С телефона
     график и читают так — «кто завтра», а не «как выглядит месяц». */
  .cal-wrap { overflow-x: visible; }
  .cal, .cal tbody, .cal tr, .cal td { display: block; }
  .cal { min-width: 0; border-spacing: 0; }
  .cal thead { display: none; }
  /* Дни соседних месяцев в ленте — пустые строки без смысла. В сетке
     они держали неделю целой, здесь держать нечего. */
  .cal-cell.cal-outside { display: none; }
  .cal-cell { height: auto; padding: 10px 12px; margin-bottom: 6px; }
  /* В сетке дата — подпись клетки и потому приглушена. В ленте она
     заголовок строки: по ней и ищут нужный день, значит она должна
     читаться первой, а не последней. */
  .cal-date { font-size: 15px; color: var(--text); margin-bottom: 8px; }
  /* День недели виден только здесь: в сетке его называл заголовок
     столбца, а в ленте столбцов нет */
  .cal-weekday { display: inline; }
  .cal-slot { margin-bottom: 6px; gap: 8px; }
  .cal-slot-ico { font-size: 14px; }
  .cal-slot select { padding: 9px 10px; font-size: 14px; border-color: var(--border-soft); }
  .cal-who { font-size: 14px; }
  /* На телефоне клетка шире и подписи крупнее — отступ тоже. Меняется
     одно значение, и оба числа едут вместе. */
  .cal { --cal-money-indent: 22px; }
  .cal-money { font-size: 13px; }
  .cal-day-total { font-size: 13px; }
  .cal-clean { margin-top: 8px; padding-top: 8px; }
  .cal-clean label { padding: 4px 0; gap: 8px; }
  .cal-clean input { width: 20px; height: 20px; }
  .cal-clean span, .cal-clean > span { font-size: 13px; white-space: normal; }
}

/* Отметка об уборке — отдельной строкой под сменами, приглушённой.
   Она не соперничает со сменами и выручкой за внимание: это чек-лист,
   а не показатель. Отмеченный день окрашивается акцентом — так месяц
   читается одним взглядом: где галочки редеют, там и вопрос. */
.cal-clean { margin-top: 5px; padding-top: 4px; border-top: 1px dashed var(--border-soft); }
.cal-clean label { display: flex; align-items: center; gap: 5px; margin: 0; cursor: pointer; font-weight: 400; }
.cal-clean input { margin: 0; flex: 0 0 auto; accent-color: var(--accent); }
.cal-clean span, .cal-clean > span { font-size: 10.5px; color: var(--text-dim); white-space: nowrap; }
.cal-clean.done span, .cal-clean.done > span { color: var(--accent); font-weight: 600; }

/* ---------------------------------------------------------- зарплата */

/* Ставка в строке участника: только поле, без кнопки и без подписи —
   единицу называет заголовок столбца «Ставка за смену». Форма уходит по
   уходу из поля или по Enter, тем же приёмом, что выбор роли рядом.

   Число по левому краю, как и в остальных таблицах (см. `.num`): подпись
   столбца и значение держим вместе. Поле узкое — ставка это три-четыре
   знака, а широкое поле в таблице просит вписать в него больше, чем
   нужно. */
.col-pay form { display: flex; align-items: center; gap: 6px; }
/* Мелкое уточнение внутри ячейки таблицы — например ссылка «сменить
   пароль» рядом со своим логином. Своё имя, а не общее с подписью
   ставки: одно имя на два смысла однажды разойдётся, и проверка,
   стерегущая одно, начнёт сторожить другое (на этом я и споткнулся). */
/* Архив участников: свёрнут по умолчанию — это не то, что смотрят
   каждый день. Стрелку раскрытия рисуем сами, как у переключателя
   бизнеса: браузерная различается в Safari и Chrome. */
.archive > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: baseline; gap: 8px;
}
.archive > summary::-webkit-details-marker { display: none; }
.archive > summary::after {
  content: ""; width: 8px; height: 8px; margin-left: 2px;
  border-right: 2px solid var(--text-dim); border-bottom: 2px solid var(--text-dim);
  transform: rotate(45deg) translate(-2px, -2px);
}
.archive[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.archive > summary h2 { margin: 0; }

.cell-note { font-size: 11px; white-space: nowrap; }

.rate-input {
  /* max-width, а не width: общее правило для полей ввода задаёт
     `width: 100%` пятью селекторами `:not()` и по весу перебивает
     одиночный класс. Ограничение сверху вес не разбирает, и воевать
     специфичностью не приходится. */
  max-width: 76px; text-align: left; font-variant-numeric: tabular-nums;
  padding: 6px 8px;
}
/* Как часто платим — рядом со ставкой, мелко и без рамки: это уточнение
   к сумме, а не самостоятельное поле. Рамка появляется при наведении,
   тем же приёмом, что у выбора смены в клетке календаря. */
/* Ставка, единица и периодичность — три части одной клетки, и до
   11.09 они стояли подряд строчными элементами. У уборщицы между полем
   и выбором вставала подпись «за уборку», строка переносилась в другом
   месте, и её клетка выглядела шире и выше соседних. Теперь форма
   всегда одна: поле сверху, подписи под ним. */
/* Ручка перетаскивания в списке участников.

   Отдельным столбцом слева от имени — просьба владельца 15.09. Кнопкой,
   а не значком: до неё доходит табуляция, и стрелками вверх-вниз строка
   двигается без мыши. Перетаскивание — удобство, клавиатура — доступ, и
   второе не должно зависеть от первого. */
.col-grip { width: 1%; padding-right: 0; }

/* Заголовок «С какого дня» переносился на две строки: подпись длиннее
   самой даты под ней, а заголовки набраны в разрядку и прописными —
   это ещё шире. Замечание владельца 15.09 «где-то текст ушёл на вторую
   строчку».

   Перенос был и до столбца с ручкой (замерено: 91 px и две строки без
   него, 86 px и две с ним) — ручка его не создала, только сузила
   колонку. Запрет переноса даёт столбцу 105 px, и ничего другого от
   этого не переносится. */
.col-source { white-space: nowrap; }

/* Накопительные колонки раздела аренды — «Начислено» и «Оплачено».

   На широком экране они и отвечают на «сколько я уже заплатил по этому
   договору». В узком уступают место срокам и остатку: с телефона в
   раздел заходят не сверять историю, а посмотреть, что горит. Своим
   классом, а не `.col-source`: то имя означает «канал ввода», и
   чужая колонка под ним однажды исчезнет вместе с ним. */
.col-accrued { white-space: nowrap; }

/* Кусок строки, который в узком экране не нужен.

   Не колонка, а часть подписи внутри ячейки: контрагент в строке
   договора на телефоне только множит переносы — «Корзинков Юрий
   Геннадьевич» разваливался на четыре строки и растягивал ряд втрое.
   Кто арендодатель, видно в карточке договора, и на ходу этот вопрос
   не задают. */
.only-wide { display: inline; }

/* Список договоров в строке «платежей без договора».

   Ширину `select` задаёт самая длинная подпись, а подпись здесь длинная
   по делу — объект, период и остаток. Без потолка список раздвигал
   строку до ширины таблицы и выталкивал кнопку за край карточки. */
.link-charge { max-width: 300px; min-width: 0; }
.link-form { flex-wrap: wrap; }
.grip {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 32px; padding: 0;
  border: 0; background: none; color: var(--text-dim);
  cursor: grab; border-radius: var(--radius-sm);
}
.grip:hover { color: var(--text); background: var(--surface-2); }
.grip:active { cursor: grabbing; }
.grip .ico { width: 18px; height: 18px; }

/* Строка в полёте: приглушена и приподнята — видно, что её несут, и
   видно, куда встанут остальные. */
tr.dragging { opacity: .45; background: var(--surface-2); }

/* Кнопка сохранения порядка. Видна всегда, а не только после
   перестановки: без скрипта перетаскивания нет, а стрелками порядок
   поменять можно — и сохранить его тогда нечем. */
.order-save {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 14px 0 0;
}
.order-save .muted { font-size: var(--t-xs); }
/* После перестановки кнопка становится обычной, а не призрачной:
   несохранённый порядок — это незаконченное действие. */
.order-save.changed .btn.ghost {
  border-color: var(--accent); color: var(--accent); font-weight: 600;
}

.rate-form { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.rate-form .rate-line { display: flex; align-items: center; gap: 6px; }
.rate-period {
  width: auto; max-width: 150px; margin: 0;
  padding: 2px 4px; font-size: 11px;
  border-color: transparent; background: var(--surface); border-radius: 7px;
}
.rate-period:hover { border-color: var(--border); }

/* Выбор в мастере: строка целиком — мишень, а не только кружок.
   Вопросов три, ответов по два-четыре, и попадать в пятнадцать пикселей
   с планшета незачем. */
.setup-choice {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 12px; margin-bottom: 8px;
  border: 1px solid var(--border-soft); border-radius: var(--radius-control);
  cursor: pointer; font-weight: 400;
}
.setup-choice:hover { border-color: var(--border); background: var(--surface-2); }
.setup-choice input { width: auto; flex: 0 0 auto; margin-top: 2px; }
.setup-choice > span { min-width: 0; }

/* Таблица начислений. Оформление наследуется от общего `table`, здесь —
   только то, чего в нём нет: подпись под именем и итоговая строка. */
.pay-who { font-weight: 600; }
.pay-breakdown { font-size: 11px; color: var(--text-dim); margin-top: 2px; }
.pay-basis { font-size: 11.5px; }
.pay-amount { color: var(--income); font-weight: 650; }
/* Итог отделён линией потолще: это не ещё одна строка списка, а ответ на
   вопрос «сколько всего» — его ищут глазами первым. */
.pay-total td {
  border-top: 2px solid var(--border); border-bottom: 0;
  font-weight: 700; padding-top: 13px;
}

/* Колонку ставки на узком экране НЕ прячем. Сначала спрятал — «она
   настраивается, а не читается на ходу», — и это была ошибка: «Команда»
   единственное место, где ставка задаётся, и на телефоне задать её стало
   нельзя вовсе. Таблица и так прокручивается внутри карточки. */
@media (max-width: 700px) {
  .pay-form { flex-wrap: wrap; gap: 4px; }
  .pay-form .pay-rate { width: 70px; }
}

/* Часовой пояс в подвале панели. Меню на details, как переключатель
   бизнесов: одиннадцать поясов, никаких скриптов.

   Список раскрывается ВВЕРХ (bottom: 100%), а не вниз: подвал прижат к
   низу панели, и вниз ему некуда — список ушёл бы за край экрана. */
.tz { position: relative; }
.tz > summary {
  cursor: pointer; list-style: none; font-size: 12px; color: var(--text-dim);
  padding: 4px 6px; border-radius: 8px; border: 1px solid transparent;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tz > summary::-webkit-details-marker { display: none; }
.tz > summary:hover { border-color: var(--border); color: var(--text); }
.tz-list {
  /* Шире панели: подписи вроде «Самара, Ижевск (UTC+4)» в 200 px ломались
     на две строки, и список из одиннадцати поясов превращался в
     двадцать две строки. Всплывает поверх содержимого — оно от этого не
     страдает, список открыт секунду. */
  position: absolute; z-index: 20; left: 0; right: auto; width: max-content;
  min-width: 100%; max-width: 260px; bottom: 100%; margin-bottom: 4px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow-md); padding: 5px;
  max-height: 320px; overflow-y: auto;
}
.tz-note {
  font-size: 11px; color: var(--text-dim); padding: 6px 8px 8px;
  border-bottom: 1px solid var(--border-soft); margin-bottom: 4px;
}
.tz-static {
  font-size: 12px; color: var(--text-dim); padding: 4px 6px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ------------------------------------------------- обновление данных */

/* Строка «когда это показано» над содержимым раздела. Прижата вправо и
   набрана мелким: это отметка о свежести, а не действие, за которым
   пришли. Заметной она становится ровно тогда, когда нужна, — когда
   данные постарели и «5 минут назад» само бросается в глаза. */
.refresh {
  display: flex; align-items: center; justify-content: flex-end;
  gap: var(--s-3); margin-bottom: var(--s-3);
}
.refresh-age { font-size: var(--t-2xs); color: var(--text-dim); }
.refresh .btn { flex: 0 0 auto; }

/* Значок крутится, пока идёт запрос. Единственное место, где движение
   означает ожидание, а не изменение: всё остальное движение в проекте
   показывает, что данные пришли. */
@keyframes refresh-spin { to { transform: rotate(360deg); } }
.refresh .btn.spinning .ico { animation: refresh-spin .8s linear infinite; }
@media (prefers-reduced-motion: reduce) {
  .refresh .btn.spinning .ico { animation: none; opacity: .5; }
}

/* ---------------------------------------------------- область просмотра */

/* Переключатель филиалов. Показывается только когда их больше одного —
   с единственной точкой это был бы элемент управления без решения.
   Стоит над содержимым, а не в боковой панели: относится к тому, что на
   странице, а не к тому, куда пойти. */
.scope {
  display: flex; align-items: center; gap: var(--s-3);
  margin-bottom: var(--s-4); flex-wrap: wrap;
}
.scope-label {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-size: var(--t-2xs); font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-dim); flex: 0 0 auto;
}
.scope-label .ico { width: 14px; height: 14px; }
