/* Стили кабинета «Фенхеля».

   Фирменный стиль подключается отсюда, а не из шаблонов (2026-08-29):
   страниц со своим <head> в проекте шесть — лендинг, платёжная страница,
   правовые тексты, счёт, виджет записи, экраны блокировки, — и каждая
   подключает только app.css. Если бы токены висели отдельным <link> в
   base.html, любая из них осталась бы без цветов, а новая страница
   получала бы ту же ошибку по умолчанию.

   @import выполняется до всех правил файла, поэтому переменные бренда
   существуют к моменту, когда до них доходит дело. */
@import url("fonts.474d1333b250.css");
@import url("tokens.5aa1063b892c.css");


/* --- Как приложение, а не как сайт (2026-09-18) --------------------------

   Четыре строки, которые отличают «страницу в браузере» от чего-то
   установленного. Ни одна из них не воспроизводится в эмуляторе рабочего
   стола — только на живом телефоне.

   tap-highlight: iOS и Android красят серо-голубым любой элемент с
   обработчиком нажатия. Это самый громкий признак «это сайт», и он
   спорит с нашей собственной подсветкой нажатия.

   text-size-adjust: без него Safari сам раздувает шрифт в альбомной
   ориентации, и вёрстка, выверенная по колонке, разъезжается.

   touch-action у кнопок и ссылок: браузер ждёт после нажатия, не будет
   ли второго (двойное — это зум), и только потом шлёт click. Ожидание
   читается как подтормаживание, хотя ничего не тормозит.

   user-select у кнопок: долгое нажатие на web-кнопку выделяет её
   подпись, чего у настоящих кнопок не бывает. Только у кнопок: текст
   страницы — адрес, номер заказа, сообщение об ошибке — человек копирует,
   и запрещать это нельзя. */
html {
  -webkit-tap-highlight-color: transparent;
  -webkit-text-size-adjust: 100%;
}

button,
a,
[role="button"],
summary,
label.switch {
  touch-action: manipulation;
}

button,
[role="button"],
summary {
  user-select: none;
  -webkit-user-select: none;
}


/* Атрибут hidden всегда прячет элемент (2026-08-30, по жалобе основателя:
   «нажимаю „Понятно“ и ничего не происходит»).

   Браузер прячет hidden правилом `display: none` из своей встроенной
   таблицы, а у неё самая низкая специфичность: любое наше правило вида
   `.install-ios { display: flex }` её перебивает. Элемент, помеченный
   hidden, оставался на экране, а скрипт, выставляющий `hidden = true`,
   молча ничего не менял.

   Так было сломано сразу пять мест: плашка «поставьте кабинет на экран
   телефона» (висела даже на компьютере и не закрывалась), кнопка
   «Установить на телефон» (показывалась там, где установка невозможна),
   поле «Получено наличными» в карточке визита, подсказка про пол
   клиента и блок «Уведомления на телефоне».

   !important здесь оправдан: это не косметика, а восстановление
   поведения самого атрибута. Правило стоит первым, чтобы действовало на
   всё, что объявлено ниже. */
[hidden] { display: none !important; }


:root {
  /* Тень у края листающейся вбок ленты (2026-09-18, аудит мобильной
     версии). Переменной, потому что в тёмной теме чёрная тень на чёрном
     фоне не видна вовсе — там роль тени играет подсветка. */
  --scroll-hint: rgba(0, 0, 0, 0.22);
  /* Прикладная палитра выражена через токены бренда (static/css/tokens.css,
     2026-08-29, дизайн-пакет «Фенхеля»). Значения здесь намеренно не
     проставляются числами: правка фирменного цвета делается в одном месте
     и расходится по всему продукту. */
  --color-text: var(--ok-ink);
  --color-muted: var(--ok-text-muted);
  --color-border: var(--ok-border);
  --color-accent: var(--ok-sand);
  /* Текст поверх акцентной заливки. Раньше по коду был вписан #fff — с
     прежним тёмно-зелёным акцентом это работало, с песочным белый по
     светлому нечитаем (контраст 1,5). Своя переменная, а не просто замена
     на тёмный: в тёмной теме акцент остаётся тем же песочным, и текст на
     нём в обеих темах должен быть тёмным. */
  --color-on-accent: var(--ok-ink);
  --color-error: #dc2626;
  /* Денежный текст: «пришло» и «ушло» (2026-08-29). Отдельно от
     --color-error, потому что у того двойная роль — он же фон кнопки
     «Удалить», где поверх лежит белый текст, и затемнять его нельзя.

     Значения подобраны под песочный фон замером: прежний зелёный давал
     на нём контраст 2,93 при норме 4,5 — суммы доходов читались плохо
     (на белом было 3,30, тоже мало, но со сменой фона стало заметно).
     В тёмной теме те же роли получают светлые оттенки, см. ниже. */
  --color-success-text: #137034;
  --color-error-text: #C81E1E;
  /* Те же роли на плашках с собственным светлым фоном (бейджи, плашки
     ошибок): их фон не меняется по темам, поэтому и текст остаётся
     тёмным всегда — тот же приём, что у --color-chip-text. */
  --color-success-on-chip: #137034;
  --color-error-on-chip: #C81E1E;
  /* Янтарная метка «обрати внимание» (2026-08-29): минимальные остатки,
     пометка клиента, подходящее окно в листе ожидания. Подложка у неё
     полупрозрачная, поэтому в тёмной теме она ложится на тёмный фон, и
     тёмно-коричневый текст на ней давал 2,72 — метку было не прочесть
     ровно там, где она единственное цветное пятно. */
  --color-warn-text: #92400e;
  --color-warn-on-chip: #92400e;
  /* Гарантийный визит. Прежний бирюзовый #0d9488 не проходил нигде: белый
     текст на нём давал 3,74 (кнопка и бейдж), а сам он как цвет текста —
     3,33 на песочном фоне. Затемнён до значения, проходящего в обеих
     ролях; для текста в тёмной теме взят светлый бирюзовый. */
  --color-warranty: #0F766E;
  --color-warranty-hover: #115E59;
  --color-warranty-text: #0F766E;
  /* На плашках и карточках со своим светлым фоном — тёмный вариант,
     одинаковый в обеих темах. */
  --color-warranty-chip-bg: #CCFBF1;
  --color-warranty-on-chip: #115E59;
  /* Фон страницы белый, а не песочный (2026-08-29, решение основателя:
     «весь фон песочный не нравится, лучше белый, а блоки выделены»).

     Роли поменялись местами: раньше песочным был весь лист, а карточки
     белыми; теперь лист белый, а песочным выделяется то, что должно
     выступать — врезки, подсказки, шапки таблиц, вторичные панели.
     Фирменный песочный никуда не делся, но стал акцентом, как и задумано
     в дизайн-пакете: «песочный — только акцент». */
  --color-bg: var(--ok-surface);
  --color-surface: var(--ok-surface);
  --color-surface-alt: var(--ok-bg);
  /* Сайдбар тёмный — так он показан в референсе бренда, и это самая
     узнаваемая часть стиля: тёмная панель слева, песочный лист справа.
     Прежний светло-серый отделял меню тоном от рабочей области; тёмный
     делает то же самое, только сильнее. Цвета текста и иконок внутри
     панели переопределены ниже, в правиле .sidebar. */
  --color-sidebar: var(--ok-ink);
  /* Ссылки — затемнённый песочный, см. комментарий у --ok-sand-deep в
     tokens.css: сам фирменный песочный как цвет текста нечитаем. */
  --color-link: var(--ok-sand-deep);
  /* Подсветка пункта под курсором. На тёмной панели наведение должно
     ПРИБАВЛЯТЬ света, а не отнимать, поэтому полупрозрачный светлый, а не
     серый: он ложится на любой фон панели и не зависит от темы. */
  --color-sidebar-hover: rgba(246, 241, 232, 0.10);
  --color-accent-soft: rgba(220, 199, 160, 0.32);
  /* Категориальная палитра разбивки «Расходы по статьям» (2026-08-06) —
     пять цветов, проверенных dataviz-скиллом на различимость соседних
     секторов (adjacent CVD ΔE ≥ 8, normal-vision ΔE ≥ 15 в обеих темах,
     контраст к поверхности учтён — светлые из них показываются ТОЛЬКО
     рядом с подписью категории, никогда цветом без текста). Шестой,
     «Прочее», намеренно нейтрально-серый — переиспользует --color-muted,
     а не берёт следующий оттенок: это не категория, а «остальное». Сам
     фирменный акцент сюда не входит — он уже занят кнопками и активными
     элементами, как рядовая статья расхода он бы читался акцентом. */
  --finance-series-1: #2a78d6;
  --finance-series-2: #eb6834;
  --finance-series-3: #1baf7a;
  --finance-series-4: #eda100;
  --finance-series-5: #e87ba4;
  /* Текст поверх заведомо светлых цветных плашек (статусы записи, типы дня,
     бейджи) — сознательно НЕ меняется в тёмной теме: их фон не подстраивается
     под тему (см. комментарий у .schedule-cell--work ниже), поэтому и текст
     должен остаться тёмным независимо от того, светлая тема на странице или
     тёмная — иначе на тёмной теме такой текст стал бы светлым на светлом. */
  --color-chip-text: var(--ok-ink);
}


/* Тёмная тема (2026-08-04, по просьбе основателя — выбор в Настройках между
   Системная/Светлая/Тёмная). Явный выбор — атрибут data-theme на <html>
   (localStorage, читается в base.html до отрисовки — тот же приём, что и у
   sidebarCollapsed). «Системная» — data-theme не ставится вовсе, тогда решает
   только prefers-color-scheme: :not([data-theme]) не даёт медиа-запросу
   перебить явный выбор "Светлая", если ОС в тёмном режиме. */
:root[data-theme="dark"] {
  --scroll-hint: rgba(246, 241, 232, 0.16);
  /* Тёмная тема на фирменной палитре (2026-08-29): фон — оттенки
     брендового --ok-ink, текст — брендовый светлый --ok-on-dark. Акцент
     (--color-accent) наследуется светлым песочным: на тёмном фоне он
     читается сам по себе, поправка не нужна. */
  --color-text: var(--ok-on-dark);
  /* Светлее брендового --ok-on-dark-muted: тот рассчитан на основной
     тёмный фон (5,2), а на подложках карточек давал 4,24 при норме
     4,5 — «Движений за этот период нет» читалось с трудом. */
  --color-muted: #968D81;
  --color-border: #2E2C29;
  --color-bg: #0F0E0D;
  --color-surface: #1A1917;
  --color-surface-alt: #242220;
  /* Ссылки в тёмной теме — фирменный песочный «как есть»: контраст к
     тёмному фону 9,7. Затемнённая версия, нужная на светлом, здесь
     наоборот утонула бы. */
  --color-link: var(--ok-sand);
  /* Денежный текст в тёмной теме: тёмные версии, нужные на песочном
     фоне, здесь наоборот утонули бы (зелёный #137034 даёт на тёмном
     3,1). Плашечные варианты не переопределяются намеренно — их фон
     светлый в любой теме. */
  --color-success-text: #22C55E;
  --color-error-text: #EF4444;
  /* Янтарь и бирюза в тёмной теме — светлые версии: тёмные, нужные
     на светлом фоне, здесь нечитаемы. Плашечные варианты не
     переопределяются: их фон светлый в любой теме. */
  --color-warn-text: #FBBF24;
  --color-warranty-text: #2DD4BF;
  --color-sidebar: #080807;
  --color-sidebar-hover: rgba(246, 241, 232, 0.12);
  --color-accent-soft: rgba(220, 199, 160, 0.18);
  /* Та же палитра разбивки, шаг светлее/менее контрастный — под тёмную
     поверхность. */
  --finance-series-1: #3987e5;
  --finance-series-2: #d95926;
  --finance-series-3: #199e70;
  --finance-series-4: #c98500;
  --finance-series-5: #d55181;
}

@media (prefers-color-scheme: dark) {
  /* «Системная» тема: значения те же, что у явного выбора выше.
     :not([data-theme]) не даёт медиа-запросу перебить выбор
     «Светлая», если сама ОС в тёмном режиме. */
  :root:not([data-theme]) {
    --scroll-hint: rgba(246, 241, 232, 0.16);
    /* Тёмная тема на фирменной палитре (2026-08-29): фон — оттенки
       брендового --ok-ink, текст — брендовый светлый --ok-on-dark. Акцент
       (--color-accent) наследуется светлым песочным: на тёмном фоне он
       читается сам по себе, поправка не нужна. */
    --color-text: var(--ok-on-dark);
    /* Светлее брендового --ok-on-dark-muted: тот рассчитан на основной
       тёмный фон (5,2), а на подложках карточек давал 4,24 при норме
       4,5 — «Движений за этот период нет» читалось с трудом. */
    --color-muted: #968D81;
    --color-border: #2E2C29;
    --color-bg: #0F0E0D;
    --color-surface: #1A1917;
    --color-surface-alt: #242220;
    /* Ссылки в тёмной теме — фирменный песочный «как есть»: контраст к
       тёмному фону 9,7. Затемнённая версия, нужная на светлом, здесь
       наоборот утонула бы. */
    --color-link: var(--ok-sand);
    /* Денежный текст в тёмной теме: тёмные версии, нужные на песочном
       фоне, здесь наоборот утонули бы (зелёный #137034 даёт на тёмном
       3,1). Плашечные варианты не переопределяются намеренно — их фон
       светлый в любой теме. */
    --color-success-text: #22C55E;
    --color-error-text: #EF4444;
    /* Янтарь и бирюза в тёмной теме — светлые версии: тёмные, нужные
       на светлом фоне, здесь нечитаемы. Плашечные варианты не
       переопределяются: их фон светлый в любой теме. */
    --color-warn-text: #FBBF24;
    --color-warranty-text: #2DD4BF;
    --color-sidebar: #080807;
    --color-sidebar-hover: rgba(246, 241, 232, 0.12);
    --color-accent-soft: rgba(220, 199, 160, 0.18);
    /* Та же палитра разбивки, шаг светлее/менее контрастный — под тёмную
       поверхность. */
    --finance-series-1: #3987e5;
    --finance-series-2: #d95926;
    --finance-series-3: #199e70;
    --finance-series-4: #c98500;
    --finance-series-5: #d55181;
  }
}

/* Шапка таблицы выделена фоном (2026-08-29, вместе с переходом на белый
   лист). На песочном листе шапка отделялась от него сама — белая полоса
   поверх песочного; на белом листе она слилась со строками, и в списке
   на два экрана становилось непонятно, где какая колонка.

   Своё правило, а не общее для всех th: у матрицы доступа и других
   таблиц с липкой шапкой фон уже задан, и перебивать его нельзя — там
   он держит колонку при прокрутке. */
.content table:not(.access-matrix) thead th {
  background: var(--color-surface-alt);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  /* Фирменная гарнитура Onest (2026-08-29). Системные шрифты остались
     в конце цепочки в tokens.css: если файл шрифта почему-то не
     отдался, интерфейс не рассыпается, а рисуется системным. */
  font-family: var(--ok-font);
  /* Трекинг из дизайн-пакета: Onest на сплошном тексте идёт чуть
     плотнее нормы, иначе интерфейс выглядит разреженным. */
  letter-spacing: -0.01em;
  color: var(--color-text);
  background: var(--color-bg);
  line-height: 1.5;
}

/* Ссылки одного цвета во всём кабинете (2026-08-26). Подчёркивание
   оставляем: цветом одним ссылка отличается плохо, особенно у людей с
   нарушением цветовосприятия. Пункты меню, кнопки-ссылки и вкладки
   красятся своими правилами и сюда не попадают. */
a {
  color: var(--color-link);
}
a:hover {
  opacity: 0.85;
}

input, select, textarea {
  color: var(--color-text);
  border-color: var(--color-border);
  /* Гарнитура как у остального текста (2026-08-07, заметил основатель).
     Без этого браузер рисует поля своим шрифтом, и заметнее всего это на
     полях даты и времени: там Chrome берёт моноширинный, и «06.08.2026
     10:30» выглядело набранным другим шрифтом, чем всё вокруг. Размер
     намеренно не трогаем — только гарнитуру: font-size: inherit
     переставил бы все поля во всех формах разом. */
  font-family: inherit;
}
/* Рамка задаётся целиком, а не одним border-color (2026-08-06, заметил
   основатель): без явных стиля и толщины браузер рисует полям собственную
   объёмную рамку `inset`, и в тёмной теме она читается как тень. Раньше
   ровную рамку получали только формы с классом .appointment-form, а
   карточка клиента и прочие — нет. */
input:not([type="checkbox"]):not([type="radio"]), select, textarea {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 6px;
}

/* Поле комментария тянется только по высоте (2026-08-18, заметил
   основатель: «комментарий — очень подвижное окно, сделай его статичным
   по ширине»).

   По умолчанию браузер разрешает тянуть textarea за уголок в обе
   стороны. Достаточно один раз задеть его мышью — и поле уезжает шире
   формы или сжимается в столбик, а в модальном окне это выглядит как
   поломка. Ширину задаёт форма, и менять её пользователю незачем.

   Высоту оставляем: длинный комментарий администратор пишет редко, но
   когда пишет — ему нужно видеть текст целиком.

   max-width страхует от браузеров, которые игнорируют resize. */
textarea {
  resize: vertical;
  max-width: 100%;
}

/* --- Раскладка приложения: сайдбар слева + контент (2026-08-03) --------- */

/* dvh, а не vh (2026-09-18, навык mobile-native): 100vh на телефоне —
   это высота окна с УБРАННОЙ адресной строкой, поэтому при загрузке
   страница оказывается выше экрана ровно на её высоту, и нижняя панель
   уезжает под неё. dvh отслеживает видимую область. */
.app-shell { display: flex; min-height: 100dvh; }
.app-shell--no-sidebar { display: block; min-height: auto; }

.sidebar {
  width: 220px;
  flex-shrink: 0;
  background: var(--color-sidebar);
  border-right: 1px solid var(--color-border);
  /* Снизу отступа нет намеренно (2026-09-13): последняя в панели —
     прилипшая кнопка «Свернуть», и отступ под ней оставлял полоску, в
     которой виднелись проезжающие пункты меню. Свой отступ кнопка держит
     сама. */
  padding: 1rem 0 0;
  display: flex;
  flex-direction: column;
  /* Панель прокручивается сама, а не вместе со страницей (2026-09-14,
     просьба основателя: «промотал меню вниз, выбрал команду — и прокрутка
     ушла наверх»).

     Раньше меню было просто длинным куском страницы: на длинном списке
     клиентов, чтобы дойти до «Маркетинга», приходилось крутить всю
     страницу, а после перехода она открывалась сверху — и меню вместе с
     ней. Теперь панель занимает высоту экрана и живёт своей прокруткой,
     а скрипт ниже возвращает её на то же место после перехода. */
  position: sticky;
  top: 0;
  /* dvh: на телефоне адресная строка то появляется, то уходит, и с vh
     низ панели прятался под ней (2026-09-18). */
  height: 100dvh;
  overflow-y: auto;
  /* Полоса прокрутки панели тонкая и в цвет: обычная светлая полоса на
     тёмном меню бросается в глаза сильнее самих пунктов. */
  scrollbar-width: thin;
  scrollbar-color: rgba(246, 241, 232, 0.2) transparent;
}
/* Бургер слева от названия (2026-08-07, по образцу Gmail) — кнопка внутри
   сайдбара, которым она управляет, а не в верхней панели рядом с ним. */
/* Тёмная панель меню (2026-08-29, фирменный стиль).

   Переопределяются сами переменные, а не отдельные правила: все вложенные
   элементы панели — пункты, иконки, разделители, счётчики — уже написаны
   через --color-text, --color-muted и --color-border, и от локального
   переопределения перекрашиваются разом. Это и есть причина, по которой
   палитра держится в переменных: смена фона панели стоит пяти строк, а не
   правки полусотни селекторов. */
.sidebar {
  --color-text: var(--ok-on-dark);
  --color-muted: var(--ok-on-dark-muted);
  --color-border: rgba(246, 241, 232, 0.14);
  --color-surface: rgba(246, 241, 232, 0.06);
  /* Ссылки внутри панели — светлый песочный: затемнённый, нужный на белом
     листе, на тёмной панели не читается. */
  --color-link: var(--ok-sand);
  color: var(--ok-on-dark);
}

.sidebar__brand {
  display: flex; align-items: center; gap: 0.6rem;
  font-weight: 600; padding: 0 1rem 1rem;
}
.sidebar__logo { flex: none; display: block; }
/* Название набирается весом 500 с плотным трекингом — так задан логотип в
   дизайн-пакете. Отдельно от общего font-weight: 600 у .sidebar__brand,
   который остался для остальной части шапки панели. */
.sidebar__brand-text {
  white-space: nowrap;
  font-weight: 500;
  letter-spacing: -0.02em;
  font-size: 1.15rem;
}
.sidebar__nav { display: flex; flex-direction: column; }
.sidebar__nav-link {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.6rem 1rem;
  color: var(--color-text);
  text-decoration: none;
  font-size: 0.9rem;
  position: relative;
}
.sidebar__nav-link:hover { background: var(--color-sidebar-hover); }
/* «Чат поддержки» — тот же вид, что у ссылок сайдбара, но это <button>
   (2026-08-06): пункт не ведёт по адресу, а открывает панель, поэтому
   семантически кнопка, а не ссылка на "#". */
.sidebar__nav-link--button {
  width: 100%;
  border: none;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* Чат поддержки — панель справа (2026-08-06). Пока интерфейсная заготовка
   без сервера (см. комментарий в base.html): выезжает поверх содержимого,
   а не занимает часть layout — открывается редко и ненадолго. */
.support-chat {
  position: fixed;
  inset: 0;
  z-index: 15;
  display: flex;
  justify-content: flex-end;
}
.support-chat[hidden] { display: none; }
.support-chat__backdrop { position: absolute; inset: 0; background: rgba(17, 24, 39, 0.5); }
.support-chat__panel {
  position: relative;
  width: 22rem;
  max-width: 100%;
  height: 100%;
  background: var(--color-surface);
  box-shadow: -12px 0 32px rgba(0, 0, 0, 0.25);
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.support-chat__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.support-chat__header h2 { margin: 0; font-size: 1.1rem; }
.support-chat__hint { margin: 0; font-size: 0.85rem; color: var(--color-muted); }
.support-chat__log {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-height: 0;
}
.support-chat__message {
  margin: 0;
  padding: 0.5rem 0.75rem;
  border-radius: 8px;
  font-size: 0.9rem;
  max-width: 90%;
}
.support-chat__message--own {
  align-self: flex-end;
  background: var(--color-accent-soft);
  color: var(--color-link);
  /* Own-сообщение теперь <div> (2026-08-06) — может нести и текст, и
     картинки/файлы вложений, отсюда flex-column вместо простого <p>. */
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.support-chat__message--own p { margin: 0; }
.support-chat__message-image {
  max-width: 100%;
  border-radius: 6px;
  display: block;
}
.support-chat__message-file { margin: 0; font-size: 0.85rem; }
.support-chat__message--system {
  align-self: flex-start;
  background: var(--color-surface-alt);
  color: var(--color-muted);
  font-style: italic;
}
#support-chat-form { display: flex; flex-direction: column; gap: 0.5rem; }
#support-chat-input { width: 100%; padding: 0.6rem 0.75rem; resize: vertical; }

/* Вложения чата поддержки (2026-08-06, по просьбе основателя) — превью
   выбранных файлов над полем ввода, до отправки. Пусто — блок ничем не
   занимает места (:empty), чтобы форма не «прыгала» при открытии панели. */
.support-chat__attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.support-chat__attachments:empty { display: none; }
.support-chat__attachment {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.3rem 1.4rem 0.3rem 0.3rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface-alt);
  max-width: 10rem;
}
.support-chat__attachment img {
  width: 2rem;
  height: 2rem;
  object-fit: cover;
  border-radius: 4px;
  flex-shrink: 0;
}
.support-chat__attachment-file { font-size: 1.1rem; }
.support-chat__attachment-name {
  font-size: 0.78rem;
  color: var(--color-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.support-chat__attachment-remove {
  position: absolute;
  top: 2px;
  right: 2px;
  border: none;
  background: none;
  color: var(--color-muted);
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
  padding: 0.1rem 0.25rem;
}
.support-chat__attachment-remove:hover { color: var(--color-error-text); }

.support-chat__toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.support-chat__attach {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0;
  font-size: 0.85rem;
}
.support-chat__attach-icon {
  width: 16px;
  height: 16px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Иконки (2026-08-04, по образцу банковской панели из скриншота
   основателя) — svg-спрайт в base.html, без иконочного шрифта и без новой
   зависимости (правило №7). currentColor: иконка сама следует за цветом
   текста, отдельных правил под тёмную тему не нужно. */
.sidebar__icon {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--color-link);
}

/* Аккордеон в сайдбаре (2026-08-03) — нативный <details>/<summary>, без JS. */
.sidebar__group { border: none; }
.sidebar__group-toggle {
  padding: 0.6rem 1rem;
  font-size: 0.9rem;
  color: var(--color-text);
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.sidebar__group-toggle::-webkit-details-marker { display: none; }
.sidebar__group-toggle:hover { background: var(--color-sidebar-hover); }
.sidebar__group-toggle::after {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid var(--color-muted);
  border-bottom: 2px solid var(--color-muted);
  transform: rotate(45deg);
  transition: transform 0.15s ease;
  margin-left: 0.5rem;
}
.sidebar__group[open] > .sidebar__group-toggle::after { transform: rotate(-135deg); }
.sidebar__group-links { display: flex; flex-direction: column; }
.sidebar__group-links .sidebar__nav-link { padding-left: 2rem; font-size: 0.85rem; }

/* --- Свёрнутый сайдбар: только иконки + подсказка при наведении --------
   (2026-08-04, по образцу скриншотов основателя). Раньше бургер прятал
   сайдбар целиком — теперь остаётся узкая полоса иконок, как в банковских
   панелях: навигация не пропадает, но занимает 56px вместо 220px. */
html.sidebar-collapsed .sidebar { width: 56px; }
/* Прячется название, но не знак (2026-09-13, просьба основателя: «чтобы в
   свёрнутом состоянии оставался только логотип»). Раньше было наоборот —
   знак исчезал, а бургер оставался, и свёрнутая панель начиналась
   безымянной кнопкой. */
html.sidebar-collapsed .sidebar__brand-text,
html.sidebar-collapsed .sidebar__label,
html.sidebar-collapsed .sidebar__group-toggle { display: none; }
html.sidebar-collapsed .sidebar__brand { justify-content: center; padding: 0 0 1rem; }
html.sidebar-collapsed .sidebar__nav-link {
  justify-content: center;
  padding: 0.6rem 0;
  gap: 0;
}
html.sidebar-collapsed .sidebar__group-links .sidebar__nav-link { padding-left: 0; }
/* Вложенные пункты в свёрнутом виде показываем всегда: заголовка группы
   уже нет, а прятать половину навигации за невидимым аккордеоном нельзя.
   Перебивает браузерное `details:not([open]) > *:not(summary)`. */
html.sidebar-collapsed .sidebar__group-links { display: flex !important; }

/* Подсказка у свёрнутой панели (2026-09-14, переделана).

   Была чистым CSS — ::after у самой ссылки, выезжающий вправо. Не
   работала: панель прокручивается сама (overflow-y: auto), а по
   спецификации прокрутка по одной оси обрезает и другую. Подсказка
   упиралась в край панели шириной 56px и превращалась в огрызок вроде
   «Настр» — ровно то, что основатель увидел на скриншоте.

   Поэтому подсказка теперь отдельным элементом в конце страницы и
   position: fixed — её никакая прокрутка не обрежет. Координаты считает
   скрипт в base.html. */
.sidebar-tip {
  position: fixed;
  padding: 0.4rem 0.7rem;
  border-radius: 6px;
  background: #111827;
  color: #fff;
  font-size: 0.85rem;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.12s ease;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
  z-index: 60;
}
.sidebar-tip[data-visible="1"] { opacity: 1; }

.app-shell__main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* flex-end, а не space-between (2026-08-07): бургер уехал в сайдбар, и в
   панели остался один блок справа — при space-between он прижался бы
   влево, на освободившееся место. */
.topbar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 1rem;
  padding: 0.75rem 1.5rem;
  border-bottom: 1px solid var(--color-border);
}
/* Быстрый поиск клиента в шапке (2026-09-14, просьба основателя). */
.quick-search { position: relative; flex: 1 1 auto; max-width: 26rem; margin: 0 1rem; }
.quick-search__input {
  width: 100%;
  padding: 0.45rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface);
  color: var(--color-text);
  font-size: 0.9rem;
}
.quick-search__input::placeholder { color: var(--color-muted); }
/* Подсказки висят НАД содержимым, а не раздвигают его: страница под
   шапкой не должна прыгать от каждой введённой цифры. */
.quick-search__results {
  position: absolute;
  top: calc(100% + 0.35rem);
  left: 0;
  right: 0;
  z-index: 30;
}
.quick-search__list {
  margin: 0;
  padding: 0.25rem;
  list-style: none;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.18);
}
.quick-search__item {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.55rem 0.7rem;
  border-radius: 6px;
  color: var(--color-text);
  text-decoration: none;
  font-size: 0.9rem;
}
.quick-search__item:hover, .quick-search__item:focus-visible {
  background: var(--color-surface-alt);
}
.quick-search__phone { color: var(--color-muted); white-space: nowrap; }
.quick-search__empty {
  margin: 0;
  padding: 0.7rem 0.9rem;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  color: var(--color-muted);
  font-size: 0.85rem;
}

.topbar__right { display: flex; align-items: center; gap: 1rem; }
.topbar__user { color: var(--color-muted); font-size: 0.9rem; }
.topbar__logout { margin: 0; }

/* Кнопка сворачивания панели (2026-09-13, по образцу Яндекс.Метрики) —
   внизу, а не в шапке рядом с логотипом.

   margin-top: auto прижимает её к низу панели, sticky удерживает у края
   экрана: в меню шестьдесят шесть пунктов, и без прилипания кнопка
   пряталась бы под сгибом. Фон обязателен — иначе сквозь неё будут
   просвечивать проезжающие пункты. */
.sidebar__collapse {
  margin-top: auto;
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 0.65rem;
  width: 100%;
  padding: 0.7rem 1rem;
  border: none;
  border-top: 1px solid var(--color-border);
  background: var(--color-sidebar);
  color: var(--color-muted);
  font: inherit;
  font-size: 0.85rem;
  text-align: left;
  cursor: pointer;
}
/* Подсветка накладывается СЛОЕМ поверх непрозрачного фона, а не заменяет
   его (2026-09-13, замечание основателя по скриншоту: «навожу — и сквозь
   кнопку видно Инвентаризацию»).

   Причина была в том, что --color-sidebar-hover полупрозрачный. Обычным
   пунктам меню это не мешает: под ними фон панели. А эта кнопка прилипшая,
   под ней проезжают пункты, и полупрозрачный фон показывал их насквозь. */
.sidebar__collapse:hover {
  background-image: linear-gradient(var(--color-sidebar-hover), var(--color-sidebar-hover));
  color: var(--color-text);
}
/* Выше проезжающих пунктов и выше их подсказок при наведении. */
.sidebar__collapse { z-index: 6; }
.sidebar__collapse-icon { flex: none; width: 1.1rem; text-align: center; font-size: 0.75rem; }
.sidebar__collapse-text--mobile { display: none; }
/* В свёрнутом виде стрелка смотрит в другую сторону: то же действие,
   обратное направление — подсказка без слов. */
html.sidebar-collapsed .sidebar__collapse { justify-content: center; padding: 0.7rem 0; gap: 0; }
html.sidebar-collapsed .sidebar__collapse-icon { transform: rotate(180deg); }

.content { padding: 1.5rem; }

.login { max-width: 360px; margin: 4rem auto; }
/* Название сервиса над формой (2026-08-17). Тем же начертанием, что на
   лендинге: это одна и та же вывеска, и разнобой в ней читается как
   «попал не туда». */
/* Знак, подходящий фону темы (2026-08-29). Основная версия — тёмные
   панели, она же для светлой темы; inverse — светлые панели, для тёмной.

   Переключение показом, а не подменой адреса скриптом: страница входа
   рисуется до любого JS, и мигание чужой версией здесь заметнее всего. */
.brand-mark--on-dark { display: none; }
:root[data-theme="dark"] .brand-mark--on-light { display: none; }
:root[data-theme="dark"] .brand-mark--on-dark { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .brand-mark--on-light { display: none; }
  :root:not([data-theme]) .brand-mark--on-dark { display: block; }
}

/* Логотип над формой входа: знак и слово в строку (2026-08-29). Вес 500,
   как задан логотип в дизайн-пакете; цвет основной текстовый, а не
   ссылочный — это опознавательный знак, а не приглашение кликнуть, хотя
   ссылкой он остаётся. */
.login__brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
  font-size: 1.35rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--color-text);
  text-decoration: none;
}
.login label { display: block; margin-top: 1rem; font-size: 0.9rem; color: var(--color-muted); }
.login input {
  width: 100%;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  font-size: 1rem;
}

.btn {
  padding: 0.6rem 1.2rem;
  border: none;
  border-radius: 6px;
  font-size: 1rem;
  cursor: pointer;
}
.btn--primary { margin-top: 1.5rem; width: 100%; background: var(--color-accent); color: var(--color-on-accent); }
.btn--link { background: none; color: var(--color-muted); padding: 0; text-decoration: underline; }

.form__error { color: var(--color-error-text); margin-top: 1rem; }

.messages { list-style: none; padding: 0 1.5rem; margin: 1rem 0; }
.messages__item { padding: 0.6rem 1rem; border-radius: 6px; background: var(--color-surface-alt); margin-bottom: 0.5rem; }
.messages__item--error { background: #fee2e2; color: var(--color-error-on-chip); }

/* Полоса «подписка заканчивается» владельцу (2026-09-11). Ссылка целиком:
   единственное действие здесь — открыть счёт, и целиться в слово «Счёт»
   на телефоне незачем. Просрочка — красным, срок на подходе — янтарным. */
.subscription-notice {
  display: block;
  padding: 0.6rem 1rem;
  border-radius: 6px;
  margin-bottom: 0.75rem;
  font-weight: 600;
  text-decoration: none;
  background: #fef3c7;
  color: #78350f;
}
.subscription-notice--overdue { background: #fee2e2; color: var(--color-error-on-chip); }

.btn--danger { background: var(--color-error); color: #fff; margin-top: 1rem; }

/* Кнопка в строке (поиск, тулбар) — без полной ширины и верхнего отступа
   основного .btn--primary, который рассчитан на форму в столбик.
   Объявлена ПОСЛЕ .btn--primary и .btn--danger намеренно: у обоих свой
   margin-top, и при обратном порядке «+ Расход» в строке кнопок съезжал
   вниз относительно «+ Доход» (2026-08-05, заметил основатель). */
.btn--inline { margin-top: 0; width: auto; display: inline-block; text-decoration: none; }

/* --- Журнал записи (M2) ------------------------------------------------ */

.journal__toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.5rem;
  flex-wrap: wrap;
}
.journal__branch-form select {
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  font-size: 1rem;
}
.journal__date-nav { display: flex; align-items: center; gap: 1rem; }
.journal__date { font-weight: 600; min-width: 11rem; text-align: center; }
.journal__date--today { color: var(--color-link); }
.journal__empty {
  padding: 2rem;
  background: var(--color-surface-alt);
  border-radius: 8px;
  color: var(--color-muted);
}

.journal-grid {
  display: grid;
  overflow-x: auto;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  font-size: 0.85rem;
}

.journal-grid__corner {
  position: sticky;
  top: 0;
  left: 0;
  background: var(--color-surface);
  z-index: 3;
  border-bottom: 1px solid var(--color-border);
}

/* Шапка мастера (2026-08-04, по образцу присланного скриншота): аватарка
   и имя по центру, без цветной полоски сверху — цвет теперь занят под
   категорию услуги на карточках. */
/* Имя мастера — ссылка на его карточку (2026-08-07). Подчёркивание
   только при наведении: в шапке календаря синие подчёркнутые имена
   выглядели бы как ошибка вёрстки, а не как ссылки. */
.journal-grid__staff-link { color: inherit; text-decoration: none; }
.journal-grid__staff-link:hover { text-decoration: underline; }

/* Имя и должность столбиком справа от аватарки (2026-08-10) — сама шапка
   выстраивает элементы в строку, поэтому подпись нужно завернуть. */
.journal-grid__staff-titles {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* flex:1 + min-width:0 — обязательная пара (2026-08-10, по жалобе
     основателя «имена наезжают друг на друга», когда в смене шесть
     мастеров). Без min-width:0 flex-элемент не даёт себя сжать меньше
     содержимого, и text-overflow на имени просто не срабатывает: имя
     распирает колонку и лезет на соседей. */
  flex: 1 1 auto;
  min-width: 0;
}
/* Аватарка не сжимается. Именно из-за этого имена и наезжали: flex
   ужимал круглую картинку до нулевой ширины, и текст ложился прямо
   поверх неё — на экране это выглядело как буква, просвечивающая
   сквозь имя. */
.journal-grid__staff-header .staff-avatar { flex-shrink: 0; }
/* Должность — подпись, а не второе имя: мельче и приглушённо, чтобы глаз
   по-прежнему цеплялся за имя. Длинная должность обрезается, иначе
   растянула бы колонку и сузила соседние. */
.journal-grid__staff-position {
  font-size: 0.75rem;
  color: var(--color-muted);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.journal-grid__staff-header {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  padding: 0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  overflow: hidden;
}
.journal-grid__staff-name {
  font-weight: 600;
  font-size: 0.9rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* max-width обязателен (2026-08-10, по жалобе основателя «имена
     наезжают друг на друга»). Родитель — flex-колонка с
     align-items:center, а в такой колонке дочерний элемент получает
     ширину по содержимому и спокойно вылезает за её края: text-overflow
     срабатывает только когда элементу есть куда упереться. У должности
     ограничение стояло с самого начала, у имени — нет, поэтому
     обрезалась она, а имя лезло на соседей. */
  max-width: 100%;
}

/* Подпись времени (2026-08-06, по просьбе основателя: «должны быть по
   центру и по середине») — flex вместо ручного padding-хака. Каждая
   подпись занимает ровно одну 15-минутную строку сетки (20px), и раньше
   текст просто прижимался к верху и к левому краю этой ячейки паддингом
   (padding: 1px 0.5rem 0); flex с align-items/justify-content центрирует
   и по вертикали, и по горизонтали без подбора отступов на глаз. */
.journal-grid__time-label {
  position: sticky;
  left: 0;
  /* Выше карточек записей и линии времени (2026-08-10, по скриншоту
     основателя: «при прокрутке вправо записи заезжают на столбец
     времени»). Столбец липкий, но при равном z-index побеждает тот, кто
     ниже в разметке, — а записи идут после подписей. Отсюда и наезд. */
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-surface);
  border-right: 1px solid var(--color-border);
  border-top: 1px solid var(--color-border);
  color: var(--color-muted);
  font-size: 0.62rem;
  line-height: 1;
  overflow: hidden;
}

/* Высота одной 15-минутной строки. Переменной, а не числом: на телефоне
   она другая (см. мобильный блок ниже), и шаблон с JS читают её отсюда —
   grid-template-rows и линия текущего времени обязаны совпадать. */
.journal-grid { --journal-slot-h: 20px; }

/* Клик по свободному слоту. Прозрачная сама по себе — видна только сетка
   границ; крупная зона клика на весь час важнее визуальной лёгкости
   (журнал — рабочий инструмент, не витрина). */
.journal-grid__slot {
  border-top: 1px solid var(--color-border);
  border-right: 1px solid var(--color-border);
  display: block;
}
.journal-grid__slot:hover { background: var(--color-accent-soft); }

/* Карточка записи (2026-08-04, переделана по образцу присланного
   скриншота). «Воздух» между карточками — не косметика: при плотной записи
   вплотную стоящие блоки читались как один сплошной, и глаз не находил
   границу между соседними клиентами. Отступ справа и снизу больше, чем
   слева и сверху — так карточка «прижата» к своему часу, а пустота уходит
   в сторону следующей. */
.journal-grid__appointment {
  position: relative;
  z-index: 1;
  margin: 1px 4px 3px 1px;
  padding: 0.3rem 0.5rem;
  background: var(--card-color, #ede9fe);
  border-radius: 6px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
  text-decoration: none;
  /* Фон карточки фиксированный (не подстраивается под тёмную тему, тот же
     принцип, что у .schedule-cell--*), поэтому и текст — var(--color-chip-text),
     а не var(--color-text): в тёмной теме тот станет светлым и пропадёт на
     этом светлом фоне. */
  color: var(--color-chip-text);
  font-size: 0.8rem;
  line-height: 1.15;
}
/* Значок «есть фото работы» (2026-08-04) — показывается всегда, если к
   записи приложено фото (галочка-переключатель убрана по просьбе
   основателя: не нужна, значок должен быть виден сразу). Верхний правый
   угол карточки. */
.journal-grid__appointment-photo-badge {
  position: absolute;
  top: 2px;
  right: 4px;
  font-size: 0.85rem;
  line-height: 1;
}
.journal-grid__appointment:hover { filter: brightness(0.97); }

/* Статусы записи (переработаны 2026-08-04 вместе с цветом по категории).
   Раньше каждый статус заливал карточку своим фоном — теперь фон занят
   категорией услуги, и заливка статуса стирала бы её. Поэтому статусы
   показываются иначе: значком, рамкой и приглушением. Исключение —
   отменённая запись: она «мертва», её категория уже не важна, серый фон
   уместен и однозначен. */
.journal-grid__appointment--cancelled {
  background: var(--color-surface-alt);
  color: var(--color-muted);
  text-decoration: line-through;
}
/* Неявка — карточка выцветает и обводится красным: цвет категории остаётся
   различим, но видно, что клиент не пришёл. */
.journal-grid__appointment--no_show {
  box-shadow: inset 0 0 0 2px var(--color-error);
  opacity: 0.65;
}
/* Оплачено (2026-08-03) — теперь галочка в углу, а не зелёная заливка.
   Правый НИЖНИЙ угол: правый верхний уже занят значком «есть фото»
   (.journal-grid__appointment-photo-badge), они бы наложились. */
.journal-grid__appointment--paid::before {
  content: "✓";
  position: absolute;
  bottom: 2px;
  right: 5px;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--color-success-on-chip);
}
/* Новый клиент (2026-08-04) — рамка справа: фон занят категорией, левая
   грань — общий отступ-«воздух» между карточками. */
/* Цвет через токен (2026-09-18, аудит): зашитый #f59e0b не менялся
   вместе с темой и в тёмной горел ярче всего остального. */
.journal-grid__appointment--new-client {
  border-right: 3px solid var(--color-warning, #d97706);
}
.journal-grid__appointment[draggable="true"] { cursor: grab; }
.journal-grid__appointment[draggable="true"]:active { cursor: grabbing; }
.journal-grid__appointment--hidden {
  background: repeating-linear-gradient(
    45deg, var(--color-surface-alt), var(--color-surface-alt) 6px,
    var(--color-border) 6px, var(--color-border) 12px
  );
  color: var(--color-muted);
  cursor: default;
  align-items: center;
  justify-content: center;
  text-align: center;
}
/* Порядок как на скриншоте: услуга — подпись сверху, клиент — главное,
   время — снизу. var(--color-muted) для приглушённых строк не годится: он
   светлеет в тёмной теме и пропадает на фиксированно светлом фоне карточки
   (см. .journal-grid__appointment). */
.journal-grid__appointment-services {
  color: #4b5563;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.journal-grid__appointment-client {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.journal-grid__appointment-time { color: #4b5563; font-size: 0.7rem; }
/* «по гарантии» — строкой под временем (2026-08-06, по просьбе основателя,
   вместо убранного в этот же день зелёного кружка «Г»). Не прячется вместе
   с именами: это не личные данные клиента, а признак самой записи, и
   администратору он нужен всегда — с этого визита денег не будет. */
.journal-grid__appointment-warranty-note {
  display: block;
  font-size: 0.65rem;
  /* Чуть плотнее остальных строк: у гарантийной записи строк пять, а не
     четыре, и в часовую карточку (76px) пятая иначе не влезает. */
  line-height: 1.1;
  font-weight: 600;
  color: var(--color-warranty-on-chip);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Способ оплаты на карточке (2026-08-27, просьба салонов): «Наличные»,
   «Терминал». Приглушённый и мелкий — это подсказка для сверки кассы, а
   не то, что администратор читает первым. */
.journal-grid__appointment-payment {
  display: block;
  font-size: 0.65rem;
  line-height: 1.1;
  color: #4b5563;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Пятой строке нужно место, и берём его из нижнего поля самой карточки, а
   не из высоты всей сетки: поднимать её ради одной редкой записи значило
   бы растянуть весь календарь. Обычных записей это правило не касается. */
.journal-grid__appointment--warranty { padding-bottom: 1px; }
/* Постоянная пометка клиента (2026-08-27): «аллергия на аммиак», «всегда
   опаздывает». В отличие от комментария к записи — не курсивом и заметнее:
   это предупреждение, которое должно поймать взгляд, а не примечание.
   Тоже одной строкой с многоточием — длинную никто не дочитает. */
.journal-grid__appointment-note {
  color: var(--color-warn-text);
  background: rgba(251, 191, 36, 0.18);
  border-radius: 3px;
  padding: 0 0.2rem;
  font-size: 0.68rem;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Та же пометка в карточке записи и в карточке клиента — крупнее, чем в
   тесной сетке календаря, но с той же жёлтой заливкой: узнаётся глазом. */
.client-note {
  display: inline-block;
  padding: 0.1rem 0.4rem;
  border-radius: 4px;
  background: rgba(251, 191, 36, 0.18);
  color: var(--color-warn-text);
  font-weight: 500;
}

/* Комментарий записи — нижней строкой (2026-08-06, по образцу DIKIDI).
   Курсивом и приглушённо: это заметка, а не данные записи. В одну строку
   с многоточием — длинный комментарий не должен растягивать карточку. */
.journal-grid__appointment-comment {
  color: #4b5563;
  font-size: 0.68rem;
  font-style: italic;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Вне смены мастера (расписание смен настроено, но на этот час/день смены
   нет) — от «занято записью» отличается направлением штриховки и без
   текста внутри, чтобы визуально не путать с реальной записью или с
   чужой скрытой записью (--hidden). z-index ниже обычной записи —
   защита на случай, если у мастера всё же есть запись на нерабочее
   время (расписание смен не проверяется при создании записи, только
   пересечение времени, ADR-0003): штриховка «нет смены» не должна
   перекрывать настоящую карточку записи, даже если раскладка вдруг
   даст сбой при перерисовке. */
.journal-grid__appointment--off-shift {
  background: repeating-linear-gradient(
    -45deg, var(--color-bg), var(--color-bg) 6px,
    var(--color-surface-alt) 6px, var(--color-surface-alt) 12px
  );
  border-left: none;
  cursor: default;
  z-index: 0;
}

/* «Занять время» (2026-08-03) — вручную закрытый слот, не запись: сплошная
   серая карточка (в отличие от штриховки --off-shift и --hidden), кликом
   освобождается. */
.journal-grid__appointment--blocked {
  background: var(--color-surface-alt);
  color: var(--color-muted);
  border-left: none;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.journal-grid__appointment--blocked:hover { filter: brightness(0.95); }

/* Подсказка над карточкой записи (2026-08-08, по просьбе
   основателя, по образцу DIKIDI). Системный title всплывал через секунду
   с лишним и рисовался шрифтом ОС; у короткой записи карточка высотой в
   одну клетку, и это был единственный способ прочитать, что там.

   position: fixed — координаты считаются от окна, поэтому подсказку не
   режет прокрутка сетки и не обрезает overflow таблицы.
   pointer-events: none — подсказка не должна перехватывать мышь у карточки
   под ним, иначе наведение мигало бы. */
.card-tooltip {
  position: fixed;
  z-index: 30;
  max-width: 22rem;
  padding: 0.5rem 0.7rem;
  border-radius: 8px;
  background: var(--color-text);
  color: var(--color-bg);
  font-size: 0.82rem;
  line-height: 1.35;
  box-shadow: 0 4px 16px rgb(0 0 0 / 25%);
  pointer-events: none;
}
.card-tooltip__title { font-weight: 600; margin-bottom: 0.15rem; }
.card-tooltip__line { white-space: nowrap; }

/* «Кто-то сейчас записывается» (2026-08-08, по просьбе основателя, по
   образцу DIKIDI) — полупрозрачная карточка на время, которое посетитель
   виджета выбирает прямо в эту минуту. Пунктир и прозрачность вместо
   сплошной заливки: это ещё не запись, а намерение, и выглядеть как
   настоящая запись оно не должно.

   pointer-events: none — карточка не перехватывает клик по слоту под ней:
   если клиент передумает, администратор запишет сюда сам, не дожидаясь,
   пока бронь истечёт. */
.journal-grid__appointment--holding {
  background: var(--color-surface-alt);
  color: var(--color-muted);
  border: 1px dashed var(--color-border);
  border-left: none;
  opacity: 0.65;
  align-items: center;
  justify-content: center;
  text-align: center;
  pointer-events: none;
}

/* Призрак цели переноса при перетаскивании (2026-08-03, по образцу DIKIDI) —
   позиционируется в пиксельных координатах viewport (position: fixed),
   не через CSS Grid, поэтому не завязан на структуру сетки и работает
   независимо от прокрутки. */
.journal-grid__ghost {
  position: fixed;
  z-index: 20;
  pointer-events: none;
  box-sizing: border-box;
  border: 2px dashed var(--color-accent);
  background: rgba(27, 89, 83, 0.12);
  border-radius: 4px;
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  padding: 0.2rem 0.4rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-link);
}
/* Тот же баг №9 из STATUS.md (модалка поверх пустой страницы) — авторский
   display:flex выше перебивает браузерный [hidden]{display:none}
   независимо от специфичности, иначе ghostEl.hidden=true в JS ничего не
   даёт визуально, и след от переноса остаётся висеть навсегда. */
.journal-grid__ghost[hidden] { display: none; }
.journal-grid__time-label--target {
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-weight: 600;
  border-radius: 4px;
}

/* --- Меню слота журнала (2026-08-05, по образцу DIKIDI) ----------------
   Всплывает по клику на пустую ячейку: точное время начала, «Добавить
   запись», «Занять время». position: fixed — координаты считаются от
   экрана (getBoundingClientRect ячейки), поэтому меню не зависит от
   прокрутки внутри сетки журнала и не обрезается её overflow. */

.slot-menu {
  position: fixed;
  z-index: 15;
  min-width: 15rem;
  padding: 1rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}
/* Тот же приём, что у .modal-overlay: авторский стиль перебивает
   браузерное [hidden]{display:none}, поэтому скрытие задаём явно. */
.slot-menu[hidden] { display: none; }
.slot-menu__title { color: var(--color-muted); font-size: 0.85rem; margin-bottom: 0.6rem; }
.slot-menu__times { display: flex; gap: 0.5rem; margin-bottom: 0.9rem; }
.slot-menu__time {
  flex: 1;
  padding: 0.5rem 0.25rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface);
  color: var(--color-text);
  font-size: 0.95rem;
  cursor: pointer;
}
.slot-menu__time:hover { border-color: var(--color-link); }
.slot-menu__time--active {
  background: var(--color-accent);
  border-color: var(--color-link);
  color: var(--color-on-accent);
}
.slot-menu__primary { margin-top: 0; width: 100%; }
.slot-menu__secondary {
  display: block;
  width: 100%;
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-border);
  color: var(--color-link);
  text-align: center;
  text-decoration: none;
}
.slot-menu__secondary:hover { text-decoration: underline; }

/* Хвостик: по умолчанию снизу (меню над ячейкой), у --below — сверху. */
.slot-menu::after {
  content: "";
  position: absolute;
  left: 50%;
  margin-left: -7px;
  border: 7px solid transparent;
  border-top-color: var(--color-surface);
  top: 100%;
}
.slot-menu--below::after {
  top: auto;
  bottom: 100%;
  border-top-color: transparent;
  border-bottom-color: var(--color-surface);
}

/* --- Сайдбар журнала: мини-календарь + «Финансы за сегодня» (2026-08-03,
   по образцу DIKIDI) ------------------------------------------------------ */

.journal__layout { display: flex; align-items: flex-start; gap: 1.5rem; }
.journal__main { flex: 1; min-width: 0; }
.journal__sidebar { width: 280px; flex-shrink: 0; display: flex; flex-direction: column; gap: 1.5rem; }
.journal__sidebar-body { display: flex; flex-direction: column; gap: 1.5rem; }

/* Сворачивание правого сайдбара журнала вправо (2026-08-04, по просьбе
   основателя) — зеркало левой панели навигации: класс на <html>, состояние
   в localStorage, применение в <head> (см. base.html).

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

   Кнопка живёт в тулбаре над календарём, рядом с шестерёнкой (2026-08-06,
   по просьбе основателя). Пока она была внутри самой панели, свёрнутая
   панель не могла исчезнуть совсем, и от неё оставался узкий столбец во
   всю высоту страницы — он и съедал место у сетки. */
.journal__toolbar-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
}
/* margin-left у самой шестерёнки больше не нужен — вправо её отжимает
   контейнер, иначе она бы расталкивала кнопку сворачивания. */
.journal__toolbar-actions .journal-settings { margin-left: 0; }
.journal__sidebar-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 2026-09-18, аудит: было 35×35 — меньше нормы касания. */
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: transparent;
  color: var(--color-muted);
  cursor: pointer;
  line-height: 1;
  font-size: 0.9rem;
  flex-shrink: 0;
}
.journal__sidebar-toggle:hover { color: var(--color-text); background: var(--color-surface-alt); }

/* Свёрнутая панель пропадает целиком, и сетка занимает всю ширину. */
html.journal-sidebar-collapsed .journal__sidebar { display: none; }
/* Стрелка разворачивается в обратную сторону — сама показывает, куда
   поедет панель по нажатию. */
html.journal-sidebar-collapsed .journal__sidebar-toggle-icon {
  display: inline-block;
  transform: rotate(180deg);
}

.journal-calendar { border: 1px solid var(--color-border); border-radius: 8px; padding: 1rem; }
.journal-calendar__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.75rem;
  font-weight: 600;
}
.journal-calendar__month { text-transform: capitalize; }
.journal-calendar__table { width: 100%; border-collapse: collapse; text-align: center; font-size: 0.8rem; }
.journal-calendar__table th { color: var(--color-muted); font-weight: 400; padding-bottom: 0.4rem; }
.journal-calendar__table td { padding: 1px; }
.journal-calendar__day {
  display: block;
  padding: 0.35rem 0;
  border-radius: 6px;
  text-decoration: none;
  color: var(--color-text);
}
.journal-calendar__day:hover { background: var(--color-accent-soft); }
.journal-calendar__day--muted { color: var(--color-muted); opacity: 0.6; }
.journal-calendar__day--today { font-weight: 600; color: var(--color-link); }
.journal-calendar__day--selected { background: var(--color-accent); color: var(--color-on-accent); }

/* Лист ожидания в сайдбаре журнала (2026-08-06, по просьбе основателя) —
   тот же корпус-карточка, что у «Финансы за сегодня». */
.journal-waitlist {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 1rem;
  margin-top: 1rem;
}
.journal-waitlist__summary {
  cursor: pointer;
  font-size: 0.9rem;
  color: var(--color-muted);
  list-style: none;
}
.journal-waitlist__summary::-webkit-details-marker { display: none; }
.journal-waitlist__count {
  display: inline-block;
  margin-left: 0.35rem;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  background: var(--color-accent-soft);
  color: var(--color-link);
  font-size: 0.75rem;
  font-weight: 600;
}
.journal-waitlist__empty { margin: 0.75rem 0 0; font-size: 0.85rem; color: var(--color-muted); }
.journal-waitlist__list { list-style: none; margin: 0.75rem 0 0; padding: 0; }
.journal-waitlist__item {
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--color-border);
  font-size: 0.85rem;
}
.journal-waitlist__item:last-child { border-bottom: none; }
.journal-waitlist__who { display: flex; align-items: baseline; gap: 0.5rem; }
/* Имя забирает свободное место и сжимается первым: телефон и кнопки
   ужимать некуда, а длинное имя переносится по словам. */
.journal-waitlist__name { font-weight: 600; flex: 1; min-width: 0; }
.journal-waitlist__phone { color: var(--color-muted); white-space: nowrap; }
.journal-waitlist__what { color: var(--color-muted); margin-top: 0.15rem; }
.journal-waitlist__comment { margin-top: 0.15rem; font-style: italic; color: var(--color-muted); }
/* Метка «с сайта» (2026-08-08) — заявку оставил сам клиент из виджета, а
   не завёл администратор со слов по телефону. */
.journal-waitlist__source {
  display: inline-block; padding: 0 0.35rem; border-radius: 4px;
  background: var(--color-accent-soft); color: var(--color-link);
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.03em;
}
/* «✎» и «×» — обычный элемент строки с именем, а не наложенный поверх неё
   (2026-08-06: при position: absolute карандаш налезал на телефон — ширину
   блока кнопок приходилось угадывать отступом справа у всей строки). */
.journal-waitlist__row-actions {
  display: flex;
  align-items: center;
  gap: 0.1rem;
  flex: none;
}
.journal-waitlist__remove { margin: 0; }
.journal-waitlist__edit-btn,
.journal-waitlist__remove-btn {
  border: none;
  background: none;
  color: var(--color-muted);
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.1rem 0.3rem;
}
.journal-waitlist__remove-btn { font-size: 1rem; }
.journal-waitlist__edit-btn:hover { color: var(--color-link); }
.journal-waitlist__remove-btn:hover { color: var(--color-error-text); }
/* «Записать» — отдельной строкой под заявкой (2026-08-06). Ссылка, а не
   кнопка-иконка: в узком сайдбаре третий значок рядом с «✎» и «×» был бы
   непонятен, а слово читается сразу. */
.journal-waitlist__book {
  display: inline-block;
  margin-top: 0.3rem;
  font-size: 0.8rem;
  color: var(--color-link);
  text-decoration: none;
  border-bottom: 1px dashed currentColor;
}
.journal-waitlist__book:hover { border-bottom-style: solid; }
.journal-waitlist__add { margin-top: 0.75rem; }
.journal-waitlist__add summary { cursor: pointer; list-style: none; }
.journal-waitlist__add summary::-webkit-details-marker { display: none; }
.journal-waitlist__form { margin-top: 0.5rem; display: flex; flex-direction: column; gap: 0.4rem; }
.journal-waitlist__form label { font-size: 0.8rem; color: var(--color-muted); margin-top: 0.3rem; }
.journal-waitlist__form input,
.journal-waitlist__form select {
  padding: 0.4rem 0.5rem;
  font-size: 0.9rem;
}
.journal-waitlist__form button { margin-top: 0.5rem; }
/* Форма редактирования заявки — свёрнута по умолчанию, разворачивается
   кнопкой «✎» (2026-08-06). Не <details>, т.к. в строке уже есть кнопка
   удаления рядом — у <details> кликабелен только сам <summary>. */
.journal-waitlist__edit-form { display: none; margin-top: 0.6rem; }
.journal-waitlist__edit-form--open { display: flex; }

.journal-finances { border: 1px solid var(--color-border); border-radius: 8px; padding: 1rem; }
.journal-finances__title { margin: 0 0 0.5rem; font-size: 0.9rem; color: var(--color-muted); }
.journal-finances__amount { margin: 0; font-size: 1.5rem; font-weight: 600; }
.journal-finances__expected {
  margin: 0.35rem 0 0;
  font-size: 0.85rem;
  color: var(--color-muted);
}
.journal-finances__breakdown {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  font-size: 0.85rem;
  color: var(--color-muted);
}
.journal-finances__breakdown li { padding: 0.15rem 0; }
.journal-finances__cash {
  margin: 0.75rem 0 0;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-border);
  font-size: 0.9rem;
}

/* --- Модальное окно (карточка записи поверх журнала, HTMX, 2026-08-03) -- */

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Атрибут hidden скрывает элемент через UA-стиль [hidden]{display:none} без
   !important — наше правило .modal-overlay{display:flex} в авторской
   таблице стилей перебивает его независимо от специфичности (авторские
   стили всегда старше UA), поэтому оверлей был виден на каждой странице.
   Явный override возвращает ожидаемое поведение. */
.modal-overlay[hidden] { display: none; }
.modal-overlay__backdrop { position: absolute; inset: 0; background: rgba(17, 24, 39, 0.5); }
.modal-overlay__content {
  position: relative;
  background: var(--color-surface);
  border-radius: 10px;
  padding: 2rem;
  /* 960 вместо 640 (2026-09-13, просьба основателя: «сделай в полтора
     раза шире, чтобы услуги помещались в одну строку»). В карточке
     сотрудника таблица услуг из четырёх колонок — галочка, название,
     цена, длительность, — и на 640 пикселях название ломалось на две
     строки у каждой второй услуги: список из полусотни позиций
     растягивался вдвое и читался как каша.

     Ограничение по ширине экрана остаётся: на узком ноутбуке окно
     по-прежнему занимает всё, кроме полей. */
  max-width: 960px;
  width: calc(100% - 2rem);
  max-height: calc(100dvh - 4rem);
  overflow-y: auto;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
}
.modal-overlay__content .appointment-detail { max-width: none; }
/* Полоса прокрутки в модальных окнах скрыта (2026-08-06, по просьбе
   основателя) — прокрутка остаётся, исчезает только сама полоса. Правило
   распространяется и на вложенные прокручиваемые блоки (список услуг при
   изменении состава, подсказки поиска), иначе внутри окна оставались бы
   свои полосы. Скрывать полосы вне модалок незачем: на длинных страницах
   она единственный признак, что ниже что-то есть. */
.modal-overlay__content,
.modal-overlay__content * {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.modal-overlay__content::-webkit-scrollbar,
.modal-overlay__content *::-webkit-scrollbar {
  width: 0;
  height: 0;
}
.modal-overlay__close {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  border: none;
  background: none;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  color: var(--color-muted);
}
.modal-overlay__close:hover { color: var(--color-text); }

/* --- Форма записи и карточка записи ------------------------------------ */

.appointment-form, .appointment-detail, .client-detail { max-width: 640px; }
/* ВАЖНО: форма-столбик (подпись над полем) обязана нести класс
   .appointment-form — без него подписи остаются inline, и вся форма
   выстраивается в одну строку вперемешку с полями. На этом уже погорели
   карточки услуги, ресурса и сотрудника (2026-08-06, заметил основатель):
   стили были привязаны только к .appointment-form, а формы внутри
   .appointment-detail класс не несли. Табличным формам
   (.staff-services-table) класс НЕ нужен — там поля стоят в ячейках. */
.appointment-form label { display: block; margin-top: 1rem; font-size: 0.9rem; color: var(--color-muted); }
.appointment-form input[type="text"],
.appointment-form input[type="tel"],
.appointment-form input[type="email"],
.appointment-form input[type="date"],
.appointment-form input[type="number"],
.appointment-form input[type="datetime-local"],
.appointment-form select,
.appointment-form textarea {
  width: 100%;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  font-size: 1rem;
}
.appointment-form fieldset { border: 1px solid var(--color-border); border-radius: 6px; margin-top: 1rem; padding: 0.75rem; }
.appointment-form__hint { font-size: 0.8rem; color: var(--color-muted); margin: 0.25rem 0 0; }
.appointment-form__checkbox { display: flex; align-items: center; gap: 0.5rem; margin-top: 1rem; }
.appointment-form__checkbox label { margin: 0; }

/* Список месяцев в модалке «Скопировать» (2026-08-04) — CheckboxSelectMultiple
   рендерит Django сам как вложенные <div>, без класса — задаём отступы им. */
.field--checkbox-list label {
  display: flex; align-items: center; gap: 0.5rem; margin: 0; padding: 0.25rem 0;
  font-size: 0.95rem; color: var(--color-text); font-weight: 400;
}

.staff-form__branches ul { list-style: none; padding: 0; margin: 0; }
.staff-form__branches li { display: flex; align-items: center; gap: 0.5rem; padding: 0.2rem 0; }
.staff-form__branches label { margin: 0; font-size: 0.95rem; color: var(--color-text); }

.client-search-results {
  border: 1px solid var(--color-border); border-radius: 6px; margin-top: 0.25rem;
  max-height: 12rem; overflow-y: auto; background: var(--color-surface);
}
/* color обязателен: подсказки — это <button>, а кнопки не наследуют цвет
   текста от родителя, браузер ставит им системный (тёмный). В тёмной теме
   подсказки услуг и клиентов из-за этого были нечитаемы (2026-08-06). */
.client-search-result {
  display: block; width: 100%; text-align: left; padding: 0.5rem 0.75rem;
  border: none; background: none; font-size: 0.9rem; cursor: pointer;
  color: var(--color-text);
}
.client-search-result:hover { background: var(--color-border); }
/* Заголовок группы в подсказках услуг (2026-08-07) — «Уже делали у нас» и
   «Остальные услуги». Не кнопка: по нему не кликают, он только разделяет. */
.service-search-group {
  padding: 0.4rem 0.75rem 0.2rem;
  font-size: 0.75rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--color-muted);
}
.service-search-group:not(:first-child) {
  margin-top: 0.25rem; border-top: 1px solid var(--color-border);
}

/* «Перенос данных» в «Настройках» (2026-08-06) — два коротких списка
   ссылок «загрузить»/«выгрузить» и строка выбора периода. */
.settings__subheading {
  margin: 1.1rem 0 0.4rem;
  font-size: 0.9rem;
  color: var(--color-muted);
  font-weight: 600;
}
.settings__links { list-style: none; margin: 0; padding: 0; }
.settings__links li { padding: 0.25rem 0; }
.settings__links a { color: var(--color-link); }
/* Ссылка с меткой источника (2026-08-18) — полем, а не текстом: её нужно
   скопировать целиком и вставить в карточку салона на карте, а длинный
   адрес мышью выделяется криво. Клик выделяет всё сразу. */
.settings__links li:has(.settings__link-input) {
  display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
}
.settings__link-label { min-width: 7.5rem; font-size: 0.9rem; }
.settings__link-input {
  flex: 1 1 18rem;
  min-width: 0;
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface-alt);
  font-size: 0.85rem;
}
.settings__note { color: var(--color-muted); font-size: 0.85rem; }
.settings__period {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 0.9rem;
}
.settings__period label { color: var(--color-muted); font-size: 0.9rem; }
.settings__period input[type="date"] { padding: 0.4rem 0.5rem; }

/* Цвета карточек календаря в «Настройках» (2026-08-06) — строка на
   состояние: подпись, квадрат-палитра, поле с кодом цвета, «Очистить». */
.card-colors__row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.75rem;
  flex-wrap: wrap;
}
.card-colors__row label { min-width: 11rem; color: var(--color-muted); font-size: 0.9rem; }
.card-colors__swatch {
  width: 2.4rem;
  height: 2.1rem;
  padding: 0.1rem;
  cursor: pointer;
  flex-shrink: 0;
}
.card-colors__row input[type="text"] { width: 8rem; padding: 0.4rem 0.5rem; }
.card-colors__clear { padding: 0; font-size: 0.85rem; }

/* Одно поле поиска клиента вместо четырёх полей (2026-08-06, по просьбе
   основателя) — подсказка в две строки: имя сверху, телефон под ним. */
.client-picker { position: relative; }
.client-picker__selected { margin-bottom: 0.35rem; }
.client-search-result__name { display: block; }
/* Номер клиента в подсказке (2026-08-30) — первым, приглушённо и мельче
   имени: ищут по имени, а номером уточняют, кто из тёзок нужен.

   Своя колонка фиксированной ширины: номера у салона бывают и
   однозначные, и четырёхзначные, а имена в столбце должны начинаться на
   одной вертикали, иначе список читается рвано. */
.client-search-result__number {
  display: inline-block;
  min-width: 2.2rem;
  font-size: 0.8rem;
  color: var(--color-muted);
  font-weight: 400;
}
.client-search-result__phone {
  display: block; font-size: 0.8rem; color: var(--color-muted); margin-top: 0.1rem;
}
/* «Ничего не нашлось» — не кнопка, нажимать нечего (2026-08-06). */
.client-search-result--empty {
  padding: 0.5rem 0.75rem;
  font-size: 0.9rem;
  color: var(--color-muted);
}

/* «+ Добавить нового клиента» — последним пунктом и своим цветом, чтобы
   не путался с найденными людьми. */
.client-search-result--new {
  border-top: 1px solid var(--color-border);
  color: var(--color-link);
  font-weight: 500;
}

/* minmax(0, 1fr) во второй колонке, а не голый 1fr (2026-08-14): внутри
   лежит таблица услуг, и голый 1fr позволяет ей раздвигать колонку по
   своему содержимому. Первая колонка тоже перестала быть жёсткими 10rem
   — на узком экране она отъедала треть ширины у значений.

   Замечено при перестановке блока оплаты. Отдельно от этого: на телефоне
   кабинет целиком шире экрана (body.scrollWidth 548 при экране 375),
   из-за чего модальные окна растягиваются по странице, а не по экрану.
   Это другой дефект, здесь не чинится. */
/* Перезапись на выходе (2026-09-17, выбор основателя). Блок наверху
   карточки, появляется сразу после оплаты.

   Рамка акцентом и мягкая заливка, но не сплошной цвет: это подсказка, а
   не тревога. Ошибки и предупреждения в кабинете красные, и если бы
   «клиент уходит без записи» выглядело так же, администратор искал бы в
   нём поломку. */
.rebooking {
  border: 1px solid var(--color-link);
  background: var(--color-accent-soft);
  border-radius: 8px;
  padding: 0.75rem 0.9rem;
  margin-bottom: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  align-items: flex-start;
}
.rebooking__title { margin: 0; font-weight: 600; }
.rebooking__when { margin: 0; }
.rebooking__busy { color: var(--color-muted); }
.rebooking__reason { margin: 0 0 0.35rem; font-size: 0.85rem; color: var(--color-muted); }

.appointment-detail__facts {
  display: grid;
  grid-template-columns: minmax(6rem, 10rem) minmax(0, 1fr);
  gap: 0.5rem 1rem;
}
.appointment-detail__facts dt { color: var(--color-muted); }
/* На телефоне подпись и значение — друг под другом: две колонки на 375
   пикселях оставляют значению меньше половины экрана. */
@media (max-width: 480px) {
  .appointment-detail__facts { grid-template-columns: minmax(0, 1fr); gap: 0.15rem; }
  .appointment-detail__facts dt { margin-top: 0.5rem; font-size: 0.85rem; }
}

.sale-summary { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.sale-summary th, .sale-summary td { text-align: left; padding: 0.25rem 0.5rem 0.25rem 0; }
.sale-summary th { color: var(--color-muted); font-weight: 600; font-size: 0.8rem; }
.sale-summary__total td { border-top: 1px solid var(--color-border); font-weight: 600; padding-top: 0.4rem; }

.appointment-detail__actions h2 { font-size: 1rem; margin: 0 0 0.25rem; }

/* Отступы урезаны вдвое (2026-08-14): каждые лишние полсотни пикселей
   над блоком оплаты — это прокрутка, которую администратор делает по
   десять раз в день. Разделительная линия отделяет не хуже воздуха. */
.appointment-detail__actions {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.appointment-detail__inline-form {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.appointment-detail__inline-form label { margin: 0; font-size: 0.9rem; color: var(--color-muted); }

/* «Удалить запись навсегда» (2026-08-06) — своим блоком, не рядом с
   обычными действиями: это единственное действие, которое нельзя
   отменить, и оно не должно теряться среди «Отменить»/«Не пришёл». */
.appointment-detail__actions--danger { padding-top: 0.75rem; border-top: none; margin-top: 0.5rem; }
.appointment-detail__delete-btn { color: var(--color-error-text); }
.appointment-detail__delete-btn:hover { text-decoration: underline; }
.appointment-detail__inline-form input { width: 8rem; padding: 0.5rem 0.6rem; border: 1px solid var(--color-border); border-radius: 6px; }

/* --- Оформление продажи в карточке записи (2026-08-14) -------------------

   По просьбе основателя: «кнопка оплаты слишком высоко, почти в середине
   окна, сделай максимально удобно для админа».

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

   Теперь это выделенный блок с коротким путём: сумма → способ → кнопка.
   Редкое (сертификат, абонемент) и опасное (отмена, удаление) — под
   спойлерами. */
.sale-box {
  padding: 1rem 1.15rem 1.15rem;
  border: 1px solid var(--color-border);
  border-radius: 12px;
  background: var(--color-surface-alt);
}
.sale-box__fields {
  display: flex;
  flex-wrap: wrap;
  gap: 0.85rem;
  margin-bottom: 1rem;
}
/* Поля растут вместе, но не уже 11rem: в узкой модалке они встают друг
   под друга, а не сплющиваются до нечитаемых. */
.sale-box__field { display: flex; flex-direction: column; gap: 0.3rem; flex: 1 1 11rem; }
/* Комментарий после процедуры — на всю ширину блока (2026-08-31):
   рядом с суммой он сжимался до пары сантиметров, а формула
   окрашивания в них не помещается. */
.sale-box__field--wide { flex: 1 1 100%; margin-top: 0.6rem; }

/* Кнопка «Продать товар» в сайдбаре журнала (2026-08-31) — на всю
   ширину узкой панели, отделена от цифр кассы. */
.journal-finances__sell { display: block; width: 100%; margin-top: 0.75rem; }

/* Галочка-фильтр в строке поиска склада (2026-08-31): в один ряд с
   выпадающими списками, чтобы фильтры читались как одна панель. */
.inventory-filter__check { display: inline-flex; align-items: center; gap: 0.4rem; }
.inventory-filter__check span { font-size: 0.9rem; }

/* Блок «завести новый товар» внутри формы прихода (2026-09-08).
   Свёрнут по умолчанию: в девяти случаях из десяти товар в справочнике
   есть, и разворачивать его каждый раз незачем. */
.inventory-new-product {
  margin: 0.5rem 0 1rem;
  padding: 0.5rem 0.75rem;
  background: var(--color-surface-alt);
  border-radius: 8px;
}
.inventory-new-product summary { cursor: pointer; }
.sale-box__field label {
  margin: 0;
  font-size: 0.85rem;
  color: var(--color-muted);
}
.sale-box__field input,
.sale-box__field select {
  width: 100%;
  min-height: 44px;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface);
  color: var(--color-text);
}
/* Сумма — главное число экрана: администратор сверяет её с тем, что
   говорит клиенту, и мелкий шрифт здесь стоит ошибок. */
.sale-box__price { font-size: 1.25rem; font-weight: 600; font-variant-numeric: tabular-nums; }
/* Кнопка во всю ширину блока: промахнуться мимо неё невозможно, и она
   же служит границей — ниже начинается редкое и опасное. */
.sale-box__submit { width: 100%; min-height: 48px; font-size: 1rem; }

.sale-box__extra { margin-top: 0.9rem; }
.sale-box__extra > summary {
  cursor: pointer;
  font-size: 0.9rem;
  color: var(--color-muted);
  padding: 0.35rem 0;
}
.sale-box__extra > summary:hover { color: var(--color-text); }
.sale-box__extra .sale-box__field { margin-top: 0.6rem; }

/* Неявка, отмена, удаление. Свёрнуты: каждое делается заметно реже
   продажи, и одно лишнее нажатие дешевле одного случайного попадания. */
.sale-other { margin-top: 1rem; }
.sale-other > summary {
  cursor: pointer;
  padding: 0.6rem 0;
  font-weight: 600;
  font-size: 0.95rem;
}
.sale-other[open] > summary { margin-bottom: 0.5rem; }
.sale-other__form { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 0.85rem; }
.sale-other__form .btn { align-self: flex-start; }
.sale-other--danger > summary { color: var(--color-error-text); }

.appointment-detail__done {
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--color-border);
}
.appointment-detail__done .btn { width: 100%; }
.appointment-detail__inline-form select { padding: 0.5rem 0.6rem; border: 1px solid var(--color-border); border-radius: 6px; }
.appointment-detail__facts dd { margin: 0; }

/* --- Изменение услуг в карточке записи (2026-08-05) ---------------------
   Свёрнутый блок: правка состава — не самое частое действие в карточке,
   разворачивается по клику, как разделы сайдбара. */

.appointment-services-edit {
  margin-top: 1.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--color-border);
}
.appointment-services-edit > summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--color-link);
}
.appointment-services-edit__choices {
  margin: 0.75rem 0;
  /* Услуг у мастера может быть много — список прокручивается внутри себя,
     иначе он выталкивает кнопку «Сохранить» за пределы модального окна. */
  max-height: 15rem;
  overflow-y: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 0.5rem 0.75rem;
}
.appointment-services-edit__choices ul { list-style: none; margin: 0; padding: 0; }
.appointment-services-edit__choices li { margin: 0.25rem 0; }
.appointment-services-edit__choices label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 400;
}

/* --- Фото работ в карточке записи (2026-08-03, по образцу DIKIDI) ------- */

.appointment-photos {
  margin-top: 1.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--color-border);
}
.appointment-photos h2 { font-size: 1rem; margin: 0 0 0.75rem; }
.appointment-photos__grid {
  list-style: none;
  padding: 0;
  margin: 0 0 1rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}
.appointment-photos__item { position: relative; }
.appointment-photos__thumb {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--color-border);
  display: block;
}
.appointment-photos__item form { position: absolute; top: -0.4rem; right: -0.4rem; margin: 0; }

/* Все снимки одной работы — в одной ячейке (2026-08-06, по просьбе
   основателя: «одна строчка — одна работа»). Переносом, а не в одну
   линию: к записи бывает и пять фото, и растягивать из-за них таблицу
   по горизонтали не нужно. */
.photo-row { display: flex; flex-wrap: wrap; gap: 0.35rem; max-width: 320px; }
/* Кнопки удаления идут столбиком — по одной на снимок, в том же порядке,
   что превью слева. */
.photo-row__delete { margin: 0; }

/* Оценка работы в списке «Фото работ» (2026-08-06, по просьбе основателя)
   — пять звёзд, как на маркетплейсе. Кому оценивать нельзя, тот видит те
   же звёзды, но без кнопок: право «Оценивает работы» в матрице доступа.

   Ряд развёрнут (row-reverse), а звёзды в разметке идут от 5 к 1 — на
   экране всё равно получается 1…5 слева направо. Ради подсветки при
   наведении: соседний селектор «~» достаёт только СЛЕДУЮЩИЕ в разметке
   элементы, а закрасить надо предыдущие; при обратном порядке «следующие»
   и оказываются теми, что левее. Иначе понадобился бы JS на каждую
   строку таблицы. */
.photo-rating { margin: 0; display: flex; align-items: center; gap: 0.25rem; }
.photo-rating__stars {
  display: inline-flex;
  flex-direction: row-reverse;
  /* justify-content: flex-end — при row-reverse ряд иначе прижимается
     вправо, и звёзды уезжали бы от левого края ячейки. */
  justify-content: flex-end;
}
.photo-rating__star {
  border: none;
  background: none;
  padding: 0 0.05rem;
  font-size: 1.05rem;
  line-height: 1;
  color: var(--color-border);
  cursor: pointer;
}
.photo-rating__star--filled { color: #f59e0b; }
/* Наведение показывает будущую оценку: гасим весь ряд, затем красим
   наводимую звезду и все, что левее неё. Правила ниже правила --filled
   намеренно: при равной специфичности побеждает последнее. */
.photo-rating__stars:hover .photo-rating__star { color: var(--color-border); }
.photo-rating__star:hover,
.photo-rating__star:hover ~ .photo-rating__star { color: #f59e0b; }
/* Звёзды без права на оценку — не кнопки, курсор обычный. */
span.photo-rating__star { cursor: default; padding: 0 0.05rem; }
.photo-rating__clear {
  border: none;
  background: none;
  color: var(--color-muted);
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.2rem;
}
.photo-rating__clear:hover { color: var(--color-error-text); }
.photo-rating__empty { color: var(--color-muted); }
.appointment-photos__delete {
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 50%;
  border: none;
  background: var(--color-surface);
  color: var(--color-error-text);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
}
.appointment-photos__upload { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }

/* --- История изменений записи (2026-08-04, по просьбе основателя) ------ */

.appointment-history {
  margin-top: 1.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--color-border);
}
.appointment-history h2 { font-size: 1rem; margin: 0 0 0.75rem; }
.appointment-history__list { list-style: none; padding: 0; margin: 0; }
.appointment-history__item {
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--color-border);
  font-size: 0.85rem;
}
.appointment-history__item:last-child { border-bottom: none; }
.appointment-history__when { color: var(--color-muted); margin-right: 0.5rem; }
.appointment-history__who { font-weight: 600; margin-right: 0.25rem; }

/* --- Клиентская база (M3) ----------------------------------------------- */


/* Тумблер «Показывать заблокированных» (2026-08-06) — обычный чекбокс
   с подписью, без переключателя-«switch»: в строке поиска он один такой
   и не нуждается в заметности переключателей настроек. */
.client-list__blacklist-toggle {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.9rem;
  color: var(--color-muted);
  white-space: nowrap;
  cursor: pointer;
}

.client-list__search {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.5rem 0;
  /* Переносится на вторую строку, а не толкает страницу вбок
     (2026-09-13, поймано при проверке меню на окне 1100 px): на складе в
     этой строке поиск, два выпадающих списка и галочка — вместе они шире
     колонки, и вбок уезжала вся страница вместе с меню. */
  flex-wrap: wrap;
}
/* Поле, список, галочка и кнопка в строке фильтров — одной высоты
   (2026-09-14, замечание основателя по скриншотам каталога услуг и
   отзывов: «скачут, не выровненные»).

   Было две причины. Первая: правило стояло на ЛЮБОМ input внутри
   строки — и галочка «Скрыть архивные» получала растяжку flex: 1,
   рамку и отступы поля ввода, превращаясь в большой квадрат. Вторая:
   выпадающему списку и кнопке отступы не задавались вовсе, и каждый
   браузер рисовал их своей высоты.

   Правим по типу элемента, а не по «любому input»: галочки и
   переключатели остаются обычного размера. */
.client-list__search input:not([type="checkbox"]):not([type="radio"]),
.client-list__search select,
.client-list__search .btn {
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  font-size: 1rem;
  line-height: 1.25;
  /* Одна высота всем троим: одних отступов мало — выпадающий список
     браузер рисует по своим правилам и получается на три пикселя выше
     соседей (замерено в браузере 2026-09-14). */
  height: 2.65rem;
}
.client-list__search input:not([type="checkbox"]):not([type="radio"]) {
  flex: 1;
  max-width: 24rem;
}
/* Кнопка «Найти»: те же отступы, что у поля, но своя заливка и без
   растяжки — иначе она тянулась бы на всю оставшуюся ширину. */
.client-list__search .btn {
  flex: none;
  background: var(--color-surface-alt);
  color: var(--color-text);
  cursor: pointer;
}
/* Галочка рядом с полями: подпись выравнивается по центру, сама
   галочка остаётся браузерного размера. */
.client-list__search label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.95rem;
}
.client-list__search input[type="checkbox"] {
  width: auto;
  margin: 0;
}

/* Подпись НАД полем поиска (2026-09-17, просьба основателя: «везде, где
   есть подсказки прямо внутри полей, убери эти подсказки»).

   Раньше «Поиск по имени» писалось серым внутри самого поля. Так короче,
   но у этого два известных изъяна: подпись исчезает, как только человек
   начинает печатать (и он уже не может проверить, в то ли поле пишет), а
   серый текст читается как уже введённое значение — люди пытаются его
   стереть.

   Обёртка <label>, а не подпись с for=id: половина полей поиска в проекте
   заведена без id, и связывать их пришлось бы, придумывая идентификаторы
   на пятнадцати экранах. Обёртка связывает поле с подписью сама. */
.search-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.85rem;
  color: var(--color-muted);
  /* Растягивается вместо самого поля: в строке фильтров растяжка стояла
     на input, а теперь он лежит внутри обёртки.

     Базис 14rem, а не ноль (2026-09-17, поймано на телефоне): при нулевом
     базисе обёртка в строке с галочками и кнопками сжималась до ширины
     буквы — подпись вставала в столбик по одному слову, а поле
     превращалось в квадратик. С базисом она сначала занимает свои
     14rem, а не помещается — переносится на новую строку целиком. */
  flex: 1 1 14rem;
  max-width: 24rem;
  min-width: 0;
}
.search-field input { width: 100%; max-width: none; }
/* Перебивает правило для галочек в строке фильтров: у него вес выше, чем
   у одного класса, и без этого подпись встала бы в строку с полем. */
.client-list__search .search-field {
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

/* Действия у способа оплаты (2026-09-18): «Сохранить», «Скрыть» и
   «Удалить» с выбором, куда перенести записи, — три отдельные формы в
   одной ячейке. В строку, а не столбиком: строк в справочнике мало, и
   столбик растянул бы таблицу втрое. */
.payment-options__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}
.payment-options__inline { display: inline-flex; align-items: center; gap: 0.35rem; }

.client-table { width: 100%; border-collapse: collapse; }
.client-table th, .client-table td {
  text-align: left;
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--color-border);
}
.client-table th { color: var(--color-muted); font-weight: 600; font-size: 0.85rem; }
.client-table a { color: var(--color-text); text-decoration: none; }
.client-table a:hover { text-decoration: underline; }
/* Подсветка всей строки при наведении (2026-08-04, по образцу DIKIDI) —
   не только у ссылки с именем, у всей строки целиком. */
.client-table tbody tr:hover { background: var(--color-accent-soft); }
/* Строка целиком открывает карточку (2026-08-06) — курсор должен об этом
   говорить, иначе кликабельной выглядит только ссылка на имени. */
.row-link { cursor: pointer; }
/* Подчёркивание при наведении на имя убрано (2026-08-06, заметил
   основатель): строка и так подсвечивается целиком (.client-table
   tbody tr:hover выше), а подчёркнутое имя намекало, что кликабельно
   именно оно одно, а не вся строка. */
.row-link a:hover { text-decoration: none; }
.client-table__row { cursor: pointer; }

/* Тумблер картинок в списках (2026-08-10, по просьбе основателя): фото
   имущества и аватарки клиентов. Скрывается вся колонка вместе с
   заголовком — оставленный пустой столбец выглядел бы как сбой вёрстки.
   Атрибут висит на <html>, потому что сами таблицы подменяются htmx-ом. */
[data-asset-photos="off"] .list-photo--asset,
[data-client-avatars="off"] .list-photo--client { display: none; }

/* Перетаскивание строк мышью (2026-08-04) — категории услуг и порядок
   услуг внутри категории. Ручка ⠿ — тот же сигнал "можно тащить", что
   курсор grab у карточек журнала. */
.draggable-row { cursor: grab; }
.draggable-row--dragging { opacity: 0.4; }
.draggable-row__handle {
  color: var(--color-muted);
  width: 1.5rem;
  text-align: center;
}
.client-table__birthday-soon { color: var(--color-muted); font-size: 0.8rem; }

.client-list__categories { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 1rem 0; }
.client-list__category {
  padding: 0.4rem 0.8rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  font-size: 0.85rem;
  color: var(--color-text);
  text-decoration: none;
  background: var(--color-surface);
}
.client-list__category:hover { border-color: var(--color-link); }
.client-list__category--active { background: var(--color-accent); color: var(--color-on-accent); border-color: var(--color-link); }

/* Сайдбар категорий на экране «Склад» (2026-08-04) — по образцу YCLIENTS,
   в отличие от чипов у клиентов: категорий товаров может быть много, и
   вертикальный список читается лучше, чем перенос строк у чипов. */
.inventory-layout { display: flex; gap: 1.5rem; align-items: flex-start; }
.inventory-sidebar {
  flex: 0 0 220px;
  display: flex;
  flex-direction: column;
}
.inventory-sidebar__link {
  padding: 0.5rem 0.75rem;
  border-radius: 6px;
  color: var(--color-text);
  text-decoration: none;
  font-size: 0.9rem;
}
.inventory-sidebar__link:hover { background: var(--color-accent-soft); }
.inventory-sidebar__link--active { background: var(--color-accent); color: var(--color-on-accent); }
.inventory-content { flex: 1 1 auto; min-width: 0; }

.client-list__filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  align-items: flex-end;
  margin: 1rem 0;
  padding: 1rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
}
.client-list__filters .field { display: flex; flex-direction: column; gap: 0.3rem; }
.client-list__filters label { font-size: 0.8rem; color: var(--color-muted); }
.client-list__filters select, .client-list__filters input[type="number"] {
  padding: 0.4rem 0.5rem; border: 1px solid var(--color-border); border-radius: 6px;
}
.client-list__filters .field--checkbox { flex-direction: row; align-items: center; gap: 0.4rem; }

.tag-chip {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  /* Подложка и текст фиксированные, не по теме (2026-08-29). Цвет ссылки
     здесь стоять не может: в тёмной теме он светлый песочный, а фон
     плашки светлый всегда — получалось 1,4 при норме 4,5. Заодно
     сиреневая подложка заменена на фирменную песочную. */
  background: var(--ok-sand-soft);
  color: var(--color-chip-text);
  font-size: 0.75rem;
  margin: 0 0.25rem 0.25rem 0;
}

/* Поиск услуг по набиранию текста в форме новой записи (2026-08-04, по
   образцу DIKIDI) — выбранные услуги чипами с крестиком, подсказки — тот
   же вид, что и у поиска клиента в этой же форме (.client-search-results). */
.service-picker { position: relative; }
.service-picker__selected:empty { display: none; }
/* Тег в списке клиентов — ссылка «показать таких же» (2026-09-14).
   Цвет наследуется от плашки: подчёркивание при наведении говорит, что
   это ссылка, а синева ссылки на песочной плашке читалась бы плохо. */
.tag-chip--link { text-decoration: none; cursor: pointer; }
.tag-chip--link:hover { text-decoration: underline; }

/* Галочки тегов в фильтрах — в строку, а не столбиком: тегов у салона
   обычно меньше десятка, и столбик растянул бы панель фильтров вдвое. */
.field--tags ul { list-style: none; margin: 0.3rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 0.3rem 0.9rem; }
.field--tags li { display: flex; align-items: center; gap: 0.35rem; }
.field--tags label { font-size: 0.85rem; }

.tag-chip--removable { display: inline-flex; align-items: center; gap: 0.3rem; }
.tag-chip__remove {
  border: none;
  background: none;
  color: inherit;
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
  padding: 0;
}

/* Номер клиента в заголовке карточки (2026-08-10) — приглушённо и перед
   именем: он для сверки («тот самый шестнадцатый?»), а не имя человека. */
.client-detail__reason-label { display: block; margin-top: 0.5rem; font-size: 0.85rem; color: var(--color-muted); }
.client-detail__number { color: var(--color-muted); font-weight: 400; margin-right: 0.35rem; }
.client-detail__header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.client-detail__tags { margin: 0.5rem 0 1.5rem; }

.badge { padding: 0.15rem 0.6rem; border-radius: 999px; font-size: 0.75rem; margin-left: 0.5rem; }
.badge--danger { background: #fee2e2; color: var(--color-error-on-chip); }

/* --- Платёжный календарь (2026-08-04) ----------------------------------- */

.payment-calendar-row--due_soon { background: #fff4e0; color: var(--color-chip-text); }
.payment-calendar-row--overdue { background: #fee2e2; color: var(--color-chip-text); }
/* Ссылка внутри строки (.client-table a) иначе перебивает унаследованный
   --color-chip-text своим собственным var(--color-text) — тот в тёмной
   теме светлеет и пропадает на этом фиксированном светлом фоне строки. */
.payment-calendar-row--due_soon a, .payment-calendar-row--overdue a { color: var(--color-chip-text); }

/* --- Сотрудники (аватарки) ---------------------------------------------- */

.staff-avatar {
  width: 32px; height: 32px; border-radius: 50%; object-fit: cover;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--color-accent); color: var(--color-on-accent); font-weight: 600; font-size: 0.85rem;
}
/* Тот же случай, что у .modal-overlay и .photo-viewer выше: display выше
   перебивает браузерное [hidden]{display:none}, и скрытие надо задать
   явно (2026-08-17, заметил основатель: «в карточке сотрудника две
   авы»). Без этого правила фото и заглушка с буквой показывались рядом:
   у сотрудника с фото — картинка и буква, у сотрудника без фото —
   буква и пустой битый <img>. */
.staff-avatar[hidden] { display: none; }
.staff-avatar--placeholder { text-decoration: none; }
.staff-avatar--large { width: 96px; height: 96px; font-size: 2rem; }

/* Пара полей в одну строку (2026-08-10, по просьбе основателя: имя и
   фамилия, телефон и почта). На узком экране расходятся в столбик —
   два поля по 150 пикселей читаются хуже, чем два поля друг под другом. */
.form-row { display: flex; flex-wrap: wrap; gap: 0 1rem; }
.form-row__cell { flex: 1 1 220px; min-width: 0; }

/* Работа с аватаркой (2026-08-10) — фото и действия рядом, а системное
   поле файла спрятано: «Файл не выбран» рядом с готовой аватаркой сбивало
   с толку, а Django-галочка «Очистить» третьей строкой не читалась как
   «удалить фото». */
.staff-photo-edit { display: flex; align-items: center; gap: 1rem; margin-top: 1rem; }
.staff-photo-edit__actions { display: flex; flex-direction: column; align-items: flex-start; gap: 0.25rem; }
.staff-photo-edit__actions .btn { margin-top: 0; }
.staff-photo-edit__field { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

.tabs { display: flex; gap: 1.5rem; border-bottom: 1px solid var(--color-border); margin-bottom: 1rem; }
.tabs__link {
  padding: 0.5rem 0; color: var(--color-muted); text-decoration: none;
  font-size: 0.9rem; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tabs__link--active { color: var(--color-link); border-bottom-color: var(--color-link); font-weight: 600; }
/* Вкладка видна, но недоступна до первого сохранения (2026-08-06) — тот
   же .tabs__link, только не ссылка: цвет приглушённее обычного и курсор
   "не сюда", а не "ссылка". Title на самом элементе объясняет, почему. */
.tabs__link--disabled { opacity: 0.5; cursor: not-allowed; }

.staff-services-table { width: 100%; border-collapse: collapse; }
.staff-services-table th, .staff-services-table td {
  text-align: left; padding: 0.5rem 0.75rem; border-bottom: 1px solid var(--color-border);
}
.staff-services-table th { color: var(--color-muted); font-weight: 600; font-size: 0.85rem; }
.staff-services-table input[type="number"] { width: 6rem; padding: 0.35rem 0.5rem; }

/* --- Публичный виджет онлайн-записи (M4) -------------------------------- */

/* Шапка виджета: название слева, переключатель темы справа
   (2026-08-08, по просьбе основателя). flex, чтобы переключатель прижался
   к правому краю и на узком экране не уехал под название. */
/* Шапка виджета. Липкая (2026-08-14): страница записи длинная — до
   «Моих записей» из середины списка услуг иначе не дотянуться иначе как
   прокруткой вверх.

   Название в одну строку с многоточием. Раньше оно переносилось: в
   демо-салоне «Салон "Ромашка" (демо)» вставало тремя строками в 88
   пикселей ширины, и шапка занимала 105 пикселей — восьмую часть экрана
   телефона ещё до первой услуги. */
.widget-header {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-bg);
  font-weight: 600;
}
.widget-header__brand {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Мельче, чем в настройках кабинета: здесь это не настройка, за которой
   пришли, а мелкое удобство рядом с названием салона. */
.widget-header__theme { flex-shrink: 0; padding: 2px; }
.widget-header__theme .theme-switch__option { padding: 0.3rem; line-height: 1; }
.widget-header__theme .theme-switch__icon { width: 16px; height: 16px; }
.widget-content { max-width: 640px; }
.widget-subtitle { color: var(--color-muted); margin-top: -0.5rem; }

/* Обложка страницы онлайн-записи (2026-08-08, по образцу DIKIDI): фото,
   круглый логотип поверх него, название с адресом и телефоном.

   Логотип «наезжает» на фотографию отрицательным отступом — приём тот же,
   что у DIKIDI и соцсетей. Когда фотографии нет, наезжать не на что:
   отступ включается только вместе с ней (--with-photo), иначе кружок
   уехал бы за верхний край страницы. */
.widget-cover { margin-bottom: 1.75rem; }
.widget-cover__photo {
  display: block; width: 100%; max-height: 260px; object-fit: cover;
  border-radius: 14px;
}
.widget-cover--with-photo .widget-cover__logo { margin-top: -44px; }
.widget-cover__logo {
  display: block; width: 88px; height: 88px; border-radius: 50%;
  object-fit: cover; border: 3px solid var(--color-bg);
  background: var(--color-bg); position: relative;
  /* Тень отрывает логотип от фотографии: без неё круглая картинка на
     пёстром снимке интерьера сливается с ним. */
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.14);
}
/* Название салона крупное, но с потолком: без clamp() «Студия красоты и
   эстетической косметологии» на телефоне занимала четыре строки. */
.widget-cover__name {
  margin: 0.85rem 0 0;
  /* Жирность задана явно: с 2026-09-18 это <p>, а не <h1> (название
     салона — шапка, а не заголовок содержимого), и браузерной
     полужирности заголовка здесь больше нет. */
  font-weight: 700;
  font-size: clamp(1.4rem, 6vw, 1.9rem);
  line-height: 1.2;
  letter-spacing: -0.01em;
}
.widget-cover__place { margin: 0.35rem 0 0; color: var(--color-muted); }
.widget-cover__phone { margin: 0.25rem 0 0; }
.widget-cover__about { margin-top: 0.85rem; }
.widget-cover__about p { margin: 0 0 0.5rem; }

/* Салон, не загрузивший фотографию (2026-08-14). Раньше страница
   начиналась прямо с названия на белом — пусто и дёшево, а первое
   впечатление о салоне клиент составляет за эту секунду.

   Подложка берёт фирменный цвет салона (--widget-accent подставляется в
   <head> из настроек) и растворяет его почти до фона: цветной, но
   спокойный прямоугольник, на котором название читается как вывеска. */
.widget-cover:not(.widget-cover--with-photo) {
  padding: 1.5rem 1.25rem 1.35rem;
  border-radius: 14px;
  background:
    linear-gradient(160deg,
      color-mix(in srgb, var(--widget-accent, var(--color-accent)) 14%, transparent),
      color-mix(in srgb, var(--widget-accent, var(--color-accent)) 3%, transparent));
}
.widget-cover:not(.widget-cover--with-photo) .widget-cover__name { margin-top: 0; }
.widget-cover:not(.widget-cover--with-photo) .widget-cover__logo { margin-bottom: 0.6rem; }

.widget-list { list-style: none; padding: 0; margin: 1rem 0; }

/* Строка списка — сетка, а не flex-строка (2026-08-14).

   Раньше здесь стоял flex со space-between, и всё содержимое строки
   вставало в один ряд. Пока элементов было два (название и цена), это
   работало. Как только к ним добавились фото услуги и «ближайшее окно»,
   строка развалилась: на телефоне «Макияж вечерний» получал 74 пикселя
   ширины, «90 мин · 4290 ₽» — 39 пикселей и вставало в четыре строки, а
   предупреждение о занятости — 123. Читать это было нельзя.

   Сетка раскладывает то же самое по смыслу: фото слева во всю высоту,
   название и цена в одной строке, подробности под ними во всю ширину.
   Строкам из двух элементов (а таких в виджете большинство) она даёт
   ровно прежний вид — название слева, подпись справа. */
.widget-list__item {
  display: grid;
  /* minmax(0, 1fr) для названия и max-content для цены — не украшение:
     с обычными auto/1fr длинное название выдавливало колонку цены до
     25 пикселей, и «4290 ₽» обрезалось на «4». Теперь сжимается и
     переносится название, а цена всегда показана целиком. */
  grid-template-columns: auto minmax(0, 1fr) max-content;
  grid-template-areas:
    "photo title meta"
    "photo note  note";
  align-items: center;
  /* Отступы задают сами ячейки, а не column-gap: строка без фотографии
     иначе получила бы пустой зазор слева от названия. */
  column-gap: 0;
  padding: 0.9rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  margin-bottom: 0.5rem;
  text-decoration: none;
  color: var(--color-text);
  /* Плавное подсвечивание под пальцем и курсором. */
  transition: border-color 0.15s, background-color 0.15s;
}
/* Занятия и мастер-классы в онлайн-записи (2026-08-29). Карточка сделана
   по образцу строки услуги рядом, но с датой в отдельной колонке слева:
   у занятия время задано салоном, и оно здесь главное — человек сначала
   смотрит, когда, и только потом что. */
.widget-sessions { display: flex; flex-direction: column; gap: 0.5rem; }
.widget-session {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr) max-content;
  align-items: center;
  gap: 0.9rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  text-decoration: none;
  color: var(--color-text);
  transition: border-color 0.15s, background-color 0.15s;
}
.widget-session:focus-visible {
  border-color: var(--color-link);
  background: var(--color-accent-soft);
}
.widget-session__when {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 3.2rem;
  font-weight: 600;
  line-height: 1.2;
}
.widget-session__when span { font-size: 0.85rem; color: var(--color-muted); font-weight: 400; }
/* Название обычным цветом, а не фирменным: карточка — ссылка целиком, и
   правило «ссылки внутри виджета фирменного цвета» перекрасило бы каждое
   название. Нажимаемость даёт сама карточка. Та же причина, что у списка
   услуг рядом. */
.widget-session__title { font-weight: 600; line-height: 1.3; color: var(--color-text); }
.widget-session__meta { font-size: 0.85rem; color: var(--color-muted); margin-top: 0.15rem; }
.widget-session__price { white-space: nowrap; font-weight: 600; }
.widget-session__summary { font-size: 1.05rem; margin: 0 0 0.25rem; }
.widget-session__about {
  padding: 0.75rem 1rem;
  border-radius: 10px;
  background: var(--color-surface-alt);
  margin: 1rem 0;
}

.widget-list__item:focus-visible {
  border-color: var(--color-link);
  background: var(--color-accent-soft);
}
.widget-list__title { grid-area: title; font-weight: 600; line-height: 1.3; }
/* Название услуги обычным цветом, а не фирменным (2026-08-14). Строка
   списка — ссылка целиком, и правило «ссылки внутри виджета красятся
   фирменным цветом» перекрашивало каждое название: список из сорока
   услуг выходил сплошь зелёным. Нажимаемость даёт сама карточка, а
   фирменный цвет остаётся там, где он что-то значит, — на «ближайшем
   окне» и кнопках.

   Три класса в селекторе не для красоты: правило с фирменным цветом
   стоит в <head> страницы и весит два класса с атрибутом. */
.widget-content .widget-list__item .widget-list__title,
.widget-content .widget-shortcut .widget-shortcut__title { color: var(--color-text); }
/* Цена не переносится: «4 290 ₽», разорванное между строками, читается
   как две разные цифры. */
.widget-list__meta { grid-area: meta; color: var(--color-muted); font-size: 0.85rem; }
/* Прижимаем вправо только в самой строке списка: тот же класс носят
   подписи внутри карточек выбора времени и на экране успеха, а там он
   стоит обычным текстом под названием. */
.widget-list__item > .widget-list__meta {
  padding-left: 1rem;
  text-align: right;
}
/* Ширину подписи справа процентами не ограничиваем: в колонке шириной
   max-content процент считается от её собственного содержимого, и
   «4290 ₽» обрезалось до «4», а должность мастера — до трёх букв.
   Место у названия отбирают только длинные подписи, а такие в виджете
   стоят под названием (widget-list__note), а не справа. */
/* Цена — то, из-за чего клиент выбирает между двумя услугами: не серая
   подпись, а обычный текст, читаемый с одного взгляда. */
.widget-list__price {
  color: var(--color-text);
  font-weight: 600;
  font-size: 0.95rem;
  /* «4 290 ₽», разорванное между строками, читается как две разные цифры. */
  white-space: nowrap;
}
/* Подробности под названием: длительность, ближайшее окно, должность
   мастера — всё, чему нужна вся ширина строки. */
.widget-list__note { grid-area: note; }

/* «Ближайшее окно — сегодня в 16:30» (2026-08-08, по просьбе основателя).
   Акцентным цветом, а не серым как остальные подписи: это то, ради чего
   клиент и пришёл, и оно должно цеплять взгляд раньше цены. */
.widget-list__nearest {
  /* Своей области хватает и без обёртки: строка «Любой свободный мастер»
     показывает ближайшее окно прямо под названием, без подробностей. */
  grid-area: note;
  display: block;
  margin-top: 0.15rem;
  font-size: 0.85rem;
  color: var(--color-link);
}
/* Свободного времени нет — это не акцент, а честное предупреждение. */
.widget-list__nearest--none { color: var(--color-muted); }
/* Длительность — самая тихая строка карточки: она уточняет выбор, но
   сама его не решает. */
.widget-list__duration {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.85rem;
  color: var(--color-muted);
}

/* Строка мастера: фото и имя слева, пара работ справа. Работы прячутся на
   узком экране — на телефоне важнее имя и ближайшее окно, а миниатюры
   сжимали бы их до нечитаемого. */
.widget-staff-row {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
}
.widget-staff-row__head { display: flex; align-items: center; gap: 0.6rem; min-width: 0; }
.widget-staff-row__who { display: flex; flex-direction: column; min-width: 0; }
.widget-staff-row__works { display: flex; gap: 0.3rem; flex-shrink: 0; }
.widget-staff-row__work {
  width: 44px; height: 44px; object-fit: cover; border-radius: 6px;
}
@media (max-width: 480px) {
  .widget-staff-row__works { display: none; }
}

.widget-slots {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));
  gap: 0.5rem;
  margin-top: 1rem;
}
/* Время со скидкой за непопулярные часы (2026-09-23). Акцентная рамка и
   маленькая метка под временем: видно в сетке с одного взгляда, но цифра
   времени остаётся главной — клиент выбирает час, а не скидку. */
.widget-slots__item--offpeak {
  flex-direction: column;
  gap: 0.05rem;
  border-color: var(--color-accent);
}
.widget-slots__deal {
  font-size: 0.7rem;
  line-height: 1;
  color: var(--color-accent);
  font-weight: 600;
}

.widget-slots__item {
  /* 46 пикселей: время выбирают пальцем, и мимо кнопки в 38 пикселей
     промахиваются чаще, чем попадают. */
  min-height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  background: var(--color-surface);
  text-decoration: none;
  color: var(--color-text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  transition: border-color 0.15s, background-color 0.15s;
}
.widget-slots__item:focus-visible {
  border-color: var(--color-link);
  background: var(--color-accent-soft);
}
/* «ещё 7 →» — та же плитка, но не время, поэтому приглушённая и без жирного:
   иначе её читают как ещё одно доступное время. */
.widget-slots__item--more { font-weight: 400; color: var(--color-muted); }

/* --- Правила бронирования (2026-09-11) ----------------------------------
   Свёрнутый текст правил и обязательная галочка на последнем шаге.
   Галочка крупнее обычной: её ставят пальцем, и промах здесь — это
   «почему не записывается». */
.widget-rules {
  margin: 1rem 0 0.5rem;
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  background: var(--color-surface);
}
.widget-rules__title { cursor: pointer; font-weight: 600; }
.widget-rules__text { margin-top: 0.5rem; font-size: 0.92rem; line-height: 1.5; }
.widget-rules__text p { margin: 0 0 0.5rem; }
.widget-rules__accept {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  margin: 0.5rem 0 0.75rem;
  font-weight: 600;
  cursor: pointer;
}
.widget-rules__accept input { width: 1.25rem; height: 1.25rem; margin-top: 0.1rem; }

/* --- Четыре руки (2026-09-10) -------------------------------------------
   Плитка времени с именами двух мастеров под ним. Шире обычной: имена
   не помещаются в 4,5 rem, а перенести их на следующий экран нельзя —
   «кто со мной будет» здесь главный вопрос, а не «во сколько». */
.parallel-pick__part { margin-bottom: 1rem; }
.parallel-pick__part select { width: 100%; margin-bottom: 0.5rem; }
.widget-slots:has(.widget-slots__item--pair) {
  grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
}
.widget-slots__item--pair {
  flex-direction: column;
  justify-content: center;
  gap: 0.15rem;
  padding: 0.5rem 0.6rem;
  min-height: 56px;
  text-align: center;
}
.widget-slots__who {
  font-weight: 400;
  font-size: 0.8rem;
  color: var(--color-muted);
  line-height: 1.2;
}

/* --- «Мне удобно в определённое время» (2026-08-08) ---------------------
   Обратный поток: сначала день и часть дня, потом услуга и мастер. Кнопки
   дней прокручиваются вбок — две недели вперёд в столбик заняли бы весь
   экран телефона раньше, чем клиент увидит результат. */
.widget-chips {
  display: flex;
  gap: 0.5rem;
  margin: 1rem 0;
  flex-wrap: wrap;
}
.widget-chips--days {
  flex-wrap: nowrap;
  overflow-x: auto;
  /* Полоса прокрутки скрыта — как в модалках: она здесь только мешает. */
  scrollbar-width: none;
  /* Прокрутка «прилипает» к дням, а не замирает на половине кнопки. */
  scroll-snap-type: x proximity;
  scroll-padding-left: 0.5rem;
}
.widget-chips--days .widget-chip { scroll-snap-align: start; }
.widget-chips--days::-webkit-scrollbar { display: none; }
.widget-chip {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Та же тач-цель, что у кнопок времени. */
  min-height: 44px;
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  text-decoration: none;
  color: var(--color-text);
  font-size: 0.9rem;
  white-space: nowrap;
  transition: border-color 0.15s, background-color 0.15s;
}
/* Выбранный день. Двойная рамка вместо заливки акцентом: фирменный цвет
   салона задаёт он сам, и на светло-бежевом белый текст не прочитать. */
.widget-chip--active {
  border-color: var(--color-link);
  box-shadow: inset 0 0 0 1px var(--color-accent);
  background: var(--color-accent-soft);
  font-weight: 600;
}
.widget-chip--period { display: flex; flex-direction: column; align-items: center; gap: 0.1rem; }
.widget-chip__hint { color: var(--color-muted); font-size: 0.75rem; }
/* В этой части дня свободного нет — видно до нажатия, а не после. */
.widget-chip--empty { opacity: 0.45; }

.widget-period-service {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 0.9rem 1rem;
  margin-bottom: 0.8rem;
}
.widget-period-service__head { display: flex; flex-direction: column; gap: 0.15rem; }
.widget-period-staff { margin-top: 0.7rem; }
.widget-period-staff__name { font-size: 0.9rem; color: var(--color-muted); }
.widget-period-staff .widget-slots { margin-top: 0.4rem; }

/* Экран успеха (2026-08-08, по просьбе основателя). Главное на нём — когда
   прийти: дата и время крупнее и акцентным цветом, остальное подробностями
   ниже. Клиент смотрит на этот экран одну секунду и запоминает именно
   строку «когда». */
.widget-success__title { margin-bottom: 0.2rem; }
.widget-success__when {
  margin-top: 0;
  font-size: 1.2rem;
  font-weight: 600;
  color: var(--color-link);
}
.widget-success__actions {
  display: flex; flex-wrap: wrap; gap: 0.6rem; margin: 1.25rem 0;
}

/* Предложение добавить салон на экран телефона. Пунктирная рамка и
   приглушённый текст — намеренно: это необязательное дополнение, а не
   ещё один шаг записи. Блок скрыт, пока браузер не подтвердит, что
   установка вообще возможна (см. widget/success.html). */
.widget-install {
  margin-top: 1.5rem;
  padding: 1rem;
  border: 1px dashed var(--color-border);
  border-radius: 8px;
}
.widget-install__text { margin: 0 0 0.6rem; color: var(--color-muted); font-size: 0.9rem; }
/* Кнопки предложения в строку: «добавить» и «не нужно» рядом, чтобы
   отказ был так же доступен, как согласие (2026-08-29). */
.widget-install__actions { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }

/* Частичный тупик (2026-08-08): у выбранного мастера занято, у других на
   этот же день свободно. Приглушённее основного списка окон — это
   запасной вариант, а не то, за чем клиент пришёл. */
.widget-alt { margin-top: 1.5rem; }
.widget-alt__title { font-size: 1rem; color: var(--color-muted); font-weight: 600; }
.widget-alt__staff { margin-top: 1rem; }
.widget-alt__name { margin: 0; font-weight: 600; }

/* Вход в лист ожидания. Кнопкой — из тупика (другого выхода там нет),
   тихой строкой — когда свободное время есть, но не подходит: она не
   должна конкурировать со списком окон над ней. */
.widget-waitlist-cta { margin-top: 1.5rem; }
/* Вход в очередь под списком свободных окон (2026-08-08). Была тихая
   строка ссылкой — основатель её не нашёл, стала обычной кнопкой. Отступ
   сверху остаётся большим: кнопка не должна прилипать к последнему окну
   и читаться как ещё одно время. */
.widget-waitlist-hint { margin-top: 1.5rem; }

/* Чипы «будни / выходные / любой день» и «утро / день / вечер» в заявке.
   Радиокнопки, а не кнопки на JS: выбор одного из трёх — ровно то, для
   чего радиокнопка и придумана, и она работает без единой строки скрипта.
   Сам кружок спрятан, кликается подпись целиком. */
.widget-chips { border: none; padding: 0; margin: 1.25rem 0 0; }
.widget-chips legend { padding: 0; font-size: 0.9rem; color: var(--color-muted); }
.widget-chips > div { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem; }
.widget-chips label {
  display: inline-flex; align-items: center; margin: 0;
  padding: 0.5rem 0.9rem; border: 1px solid var(--color-border); border-radius: 999px;
  cursor: pointer; font-size: 0.95rem; font-weight: 400; color: var(--color-text);
}
.widget-chips input[type="radio"] { position: absolute; opacity: 0; width: 0; height: 0; }
.widget-chips label:has(input:checked) {
  border-color: var(--color-link); background: var(--color-accent-soft); font-weight: 600;
}
/* Обводка с клавиатуры: сам кружок спрятан, и без этого правила по Tab
   было бы не видно, где ты находишься. */
.widget-chips label:has(input:focus-visible) { outline: 2px solid var(--color-accent); }

/* --- График работы (сетка мастер×день месяца) --------------------------- */

.schedule__toolbar { display: flex; align-items: center; gap: 1.5rem; margin-bottom: 1rem; flex-wrap: wrap; }
.schedule__layout { display: flex; align-items: flex-start; gap: 1.5rem; }
.schedule__grid-wrap { flex: 1; min-width: 0; overflow-x: auto; }
.schedule__sidebar {
  width: 200px; flex-shrink: 0; position: sticky; top: 1rem;
  border: 1px solid var(--color-border); border-radius: 8px; padding: 1rem;
}
.schedule__sidebar-title { margin: 0 0 1rem; font-size: 1.1rem; }
.schedule__sidebar label { display: block; margin-top: 1rem; font-size: 0.9rem; color: var(--color-muted); }
.schedule__sidebar select {
  width: 100%; padding: 0.5rem 0.6rem; border: 1px solid var(--color-border); border-radius: 6px;
  font-size: 0.95rem;
}
.schedule__sidebar .btn { margin-top: 1rem; width: 100%; }
.schedule__selected-info {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  font-size: 0.9rem; color: var(--color-muted); padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--color-border);
}

.schedule-grid { border-collapse: collapse; table-layout: fixed; }
.schedule-grid th, .schedule-grid td { border: 1px solid var(--color-border); padding: 0; }
.schedule-grid__corner { width: 8rem; }
/* Столбец с именами не уезжает при прокрутке вправо (2026-08-07, по просьбе
   основателя): в месяце 31 столбец, и на дальних числах было не понять, чья
   это строка.

   Фон обязателен — иначе ячейки дней просвечивали бы сквозь прилипший
   столбец. Разделительная линия справа нарисована тенью, а не рамкой: при
   border-collapse: collapse рамка прилипшей ячейки принадлежит схлопнутой
   сетке и вместе с ячейкой не едет.

   transform: translateZ(0) — отдельный слой композиции для этой ячейки
   (2026-08-07, заметил основатель: «маленькая полоска слева у мастеров,
   как будто прозрачная, и записи снизу просвечивают»). Без него браузер
   во время скролла иногда красит sticky-ячейку со сдвигом на доли пикселя
   от её настоящей границы — в эту щель на миг проглядывает прокрученное
   содержимое дневных колонок под ней. Сам приём ничего не двигает и
   визуально ничего не меняет — только заставляет отрисовать эту ячейку
   на своём слое с ровной, а не дробной границей. */
.schedule-grid__corner,
.schedule-grid__staff {
  position: sticky; left: 0; z-index: 2;
  background: var(--color-bg);
  box-shadow: 1px 0 0 var(--color-border);
  transform: translateZ(0);
}
/* Все столбцы дней одной ширины (2026-08-07, по просьбе основателя): ширина
   задана и заголовку, и ячейке, а содержимое обрезается по границе — иначе
   время смены («09:00/18:00») распирало свой столбец, и дни с временем
   получались шире пустых. */
.schedule-grid__day-header,
.schedule-grid tbody td {
  width: 3.1rem; min-width: 3.1rem; max-width: 3.1rem; overflow: hidden;
}
.schedule-grid__day-header {
  padding: 0.3rem 0.1rem; font-size: 0.75rem; color: var(--color-muted);
}
/* Сегодняшний столбец в графике (2026-08-30, просьба основателя).

   Раньше сегодня отмечалось только цветом числа в шапке. Шапка уезжает
   вверх, стоит промотать список мастеров, и в таблице на тридцать один
   одинаковый столбец человек терял, где он, — а цена ошибки здесь
   рабочий день, поставленный соседней дате.

   Выделяем боковыми линиями, а не заливкой: у ячеек свои цвета — белая
   смена, розовый больничный, зелёный отпуск, — и заливка столбца либо
   не видна под ними, либо спорит с ними за смысл. Линии же идут поверх
   любых ячеек сверху донизу и ни с чем не путаются. */
.schedule-grid__day-header--today {
  color: var(--color-link);
  font-weight: 600;
  box-shadow:
    inset 2px 0 0 var(--color-link),
    inset -2px 0 0 var(--color-link),
    inset 0 2px 0 var(--color-link);
}
/* Линии рисуются накладками поверх содержимого, а не внутренней тенью
   ячейки: сама плашка дня занимает почти всю ширину столбца и съедала
   от линии по пикселю с каждой стороны — в строках со сменой полоса
   пропадала. z-index намеренно ниже прилипшего столбца с именами (у
   него 2), иначе линии проезжали бы поверх имён при прокрутке. */
.schedule-grid__cell--today { position: relative; }
.schedule-grid__cell--today::before,
.schedule-grid__cell--today::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  /* Цвет ссылок, а не акцент: акцент — светлый песочный, и на белом
     фоне светлой темы линия почти не читалась. --color-link темнеет в
     светлой теме и светлеет в тёмной, то есть виден всегда — и это тот
     же цвет, которым отмечено сегодняшнее число в шапке. */
  background: var(--color-link);
  z-index: 1;
  pointer-events: none;
}
.schedule-grid__cell--today::before { left: 0; }
.schedule-grid__cell--today::after { right: 0; }
/* Нижняя черта — только у последней строки, чтобы столбец получился
   обведённой полосой, а не набором отдельных рамок. */
.schedule-grid tbody tr:last-child .schedule-grid__cell--today {
  box-shadow: inset 0 -2px 0 var(--color-link);
}
/* Примечание к мастеру на день в шапке колонки журнала (2026-09-10,
   просьба салонов — тема 1369 у Арники). Мелко и в одну строку: место в
   шапке занято именем, должностью и выручкой, а примечание не должно
   разъезжать колонку. */
/* Ссылка «месяц» рядом с именем мастера (2026-09-10, тема 716 у
   Арники). Значок, а не слово: в шапке колонки места нет, а искать
   свободные окна администратор идёт именно от конкретного человека. */

/* Значок «месяц» и имя мастера в одной строке (2026-09-11). Раньше
   значок стоял отдельной строкой в столбце шапки и поднимал её высоту
   на двадцать пикселей — при том, что рядом с именем места хватает. */
.journal-grid__staff-nameline {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 0.25rem;
  max-width: 100%;
  min-width: 0;
}

.journal-grid__note { font-size: 0.75rem; }
.journal-grid__note summary {
  cursor: pointer;
  color: var(--color-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  /* Без треугольника раскрытия (2026-09-13): рядом с кнопкой «+» он
     читался как «▶ +» — два значка подряд, из которых ни один ничего не
     объясняет. Само примечание, когда оно есть, тоже не нуждается в
     стрелке: это текст, а не раздел. */
  list-style: none;
}
.journal-grid__note summary::-webkit-details-marker { display: none; }
.journal-grid__note summary::marker { content: ""; }
.journal-grid__note input[type="text"] { width: 100%; font-size: 0.8rem; }

/* Маленькая кнопка вместо слова «примечание» (2026-09-13, замечание
   основателя: «главное, чтобы была небольшая кнопка»).

   Первая попытка — приглушённый до цвета рамки знак — не читалась как
   нажимаемая: серая палочка среди текста выглядит опечаткой, а не
   кнопкой. Теперь это именно кнопка: рамка, фон, ощутимая область
   нажатия. Маленькая, чтобы не спорить с именем мастера и выручкой, но
   явная. */
.journal-grid__note-add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.35rem;
  height: 1.35rem;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-surface);
  color: var(--color-muted);
  font-size: 0.9rem;
  line-height: 1;
}
.journal-grid__note summary:hover .journal-grid__note-add {
  border-color: var(--color-muted);
  color: var(--color-text);
}
/* Дата у примечания, написанного раньше (2026-09-10): оно действует,
   пока его не сняли, и без даты читалось бы как сегодняшнее. */
.journal-grid__note-date { color: var(--color-muted); font-size: 0.75rem; }
.journal-grid__note-text {
  display: block;
  font-size: 0.75rem;
  color: var(--color-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.schedule-grid__day-header--weekend { color: var(--color-error-text); }
/* Суббота и воскресенье разными цветами (2026-09-09, просьба салонов —
   тема 71 у Арники): в салоне суббота обычно полный рабочий день, а
   воскресенье часто закрыто, и одинаковый красный на обоих заставляет
   каждый раз пересчитывать числа глазами. */
.schedule-grid__day-header--saturday { color: var(--color-warning-text, #a86a00); }
.schedule-grid__day-header--sunday { color: var(--color-error-text); }
/* Повтор чисел внизу таблицы — та же шапка, только снизу; выглядеть она
   должна так же, иначе читается как отдельная строка данных. */
/* Нижняя строка дат — точная копия верхней (2026-09-14, замечание
   основателя по скриншоту).

   Было три расхождения, и все три мешали одинаково: строка внизу должна
   читаться так же, как шапка, иначе глаз ищет число дважды.

   1. Фон. Внизу стоял `--color-surface`, а у шапки `--color-surface-alt`
      (общее правило для thead любой таблицы). Полоса внизу выглядела
      частью сетки, а не подписью к ней.
   2. Первая ячейка не липла при прокрутке вправо — и числа заезжали под
      колонку с именами, которая липнет. Наверху такого не было: там
      угловая ячейка прилипшая.
   3. Сегодняшний день внизу был обведён со всех сторон: класс общий с
      шапкой, а в нём линия СВЕРХУ. В паре «шапка + подвал» линия сверху
      принадлежит шапке, снизу — подвалу, и вместе они замыкают столбец;
      две верхние линии превращали подпись в отдельную рамку. */
.schedule-grid tfoot th {
  border-top: 2px solid var(--color-border);
  background: var(--color-surface-alt);
}
.schedule-grid__staff-header {
  position: sticky; left: 0; z-index: 2;
  background: var(--color-surface-alt);
  box-shadow: 1px 0 0 var(--color-border);
  transform: translateZ(0);
}
/* Сегодня в нижней строке: боковые линии те же, а горизонтальная —
   снизу. Вместе с линией сверху у шапки столбец получается обведённым
   ровно по краям таблицы. */
.schedule-grid tfoot .schedule-grid__day-header--today {
  box-shadow:
    inset 2px 0 0 var(--color-link),
    inset -2px 0 0 var(--color-link),
    inset 0 -2px 0 var(--color-link);
}
.schedule-grid__weekday { font-weight: 400; }
/* Сама ячейка остаётся обычной ячейкой таблицы — только так она тянется на
   высоту строки. Раскладка «аватарка + имя» живёт во вложенном блоке
   (2026-08-07): пока flex стоял на <th>, ячейка была ниже строки на
   несколько пикселей, и снизу вылезала полоса чужих границ. */
/* th.schedule-grid__staff, а не просто .schedule-grid__staff (2026-08-07):
   иначе правило проигрывало по специфичности «.schedule-grid th, .schedule-grid
   td { padding: 0; }» выше, и отступ ниже просто не применялся. */
th.schedule-grid__staff {
  /* Справа больше отступа, чем слева: иначе длинное имя обрезалось
     многоточием впритык к границе столбца. */
  padding: 0.4rem 0.9rem 0.4rem 0.5rem; font-size: 0.8rem; font-weight: 400; text-align: left;
}
.schedule-grid__staff-inner {
  display: flex; align-items: center; gap: 0.4rem; min-width: 0;
}
.schedule-grid__staff-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Заголовок группы должностей (2026-08-07) — строка во всю ширину сетки. */
.schedule-grid__group-title {
  padding: 0.45rem 0.5rem; text-align: left; font-size: 0.75rem;
  font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--color-muted); background: var(--color-surface-alt);
}
/* Прилипает сам текст, а не ячейка: ячейка растянута на всю ширину сетки
   (colspan), и прилепить её значило бы не двигать вообще ничего. */
.schedule-grid__group-label { position: sticky; left: 0.5rem; display: inline-block; }

/* Заливка дня занимает ячейку целиком (2026-08-30, заметил основатель:
   «есть сверху и снизу щели»).

   Высоту строки задаёт самая высокая ячейка в ней — например, та, где
   стоит значок отпуска. Плашка дня при этом оставалась ростом со своё
   содержимое (min-height), и в строках повыше сверху и снизу
   проглядывала полоса фона: цветной день выглядел незакрашенным по
   краям.

   height: 1px на ячейке таблицы — известный приём, а не опечатка: без
   заданной высоты процент внутри ячейки браузеру не от чего считать, и
   `height: 100%` у плашки не срабатывает. Саму ячейку этот пиксель ни к
   чему не обязывает — таблица всё равно растянет её по содержимому
   строки. */
.schedule-grid tbody td { height: 1px; }

.schedule-cell {
  position: relative;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  height: 100%; width: 100%; box-sizing: border-box;
  min-height: 2.4rem; margin: 0; font-size: 0.68rem; line-height: 1.2; color: var(--color-muted);
}
/* Буква типа дня в углу рабочей ячейки (2026-08-07, уточнение основателя):
   время по центру, а «С»/«З» — маленьким значком сверху слева, чтобы с
   первого взгляда было видно и часы, и то, смена это или «под запись».
   Абсолютным позиционированием, а не в потоке: ячейка узкая (3.1rem), и
   буква рядом с временем отняла бы у него половину ширины. */
.schedule-cell__badge {
  position: absolute; top: 1px; left: 3px;
  font-size: 0.68rem; font-weight: 700; line-height: 1;
}
.schedule-cell input[type="checkbox"] { position: absolute; opacity: 0; pointer-events: none; }
/* Цвета типов дня фиксированные, не подстраиваются под тёмную тему (решение
   Сергея — см. запись в docs/STATUS.md про недостающие типы дня), поэтому
   их текст — var(--color-chip-text) (всегда тёмный), а не var(--color-text)
   (тот в тёмной теме станет светлым и пропадёт на этом светлом фоне). */
/* Смена и «под запись» различаются цветом фона и буквой в углу («С»/«З»),
   как и все остальные типы дня. Полосу у смены и рамку у «под записи»
   пробовали 2026-08-07 и в тот же день убрали по просьбе основателя: они
   ничего не добавляли к букве, а читались как непонятное украшение —
   «что означают полоски вокруг?». Букву заодно укрупнили. */
.schedule-cell--work { color: var(--color-chip-text); background: #f5f3ff; }
/* Отпуск зелёный (2026-08-15, по просьбе основателя; был бежевый
   #fff4e0). Светлый оттенок, а не тот же зелёный, что у выделения:
   выделенная ячейка красится var(--color-accent) — тёмным фирменным, — и
   совпади они, отпуск было бы не отличить от «эту клетку я только что
   выбрал мышью». */
.schedule-cell--vacation { color: var(--color-chip-text); background: #d6f0dd; }
.schedule-cell--sick { color: var(--color-chip-text); background: #ffe8e8; }
/* Пять новых типов дня (2026-08-04, по просьбе основателя) — цвета по
   присланной картинке-легенде; у «Рабочий»/«Отпуск»/«Больничный» цвета
   сознательно оставили прежними (решение Сергея), поэтому палитра
   получилась смешанной, не 1:1 с картинкой целиком. */
.schedule-cell--on_call { color: var(--color-chip-text); background: #ffedd5; }
.schedule-cell--business_trip { color: var(--color-chip-text); background: #fef9c3; }
.schedule-cell--absence { color: #fff; background: #4b5563; }
/* Нерабочий день (2026-08-07) — прочерк на сером, а не пустая клетка:
   выходной должен читаться как «день определён, мастер не работает».
   Пустая клетка (--unset) осталась ровно одна и значит ровно одно:
   график на этот день ещё не сдан. */
.schedule-cell--off { color: var(--color-muted); background: var(--color-surface-alt); }
.schedule-cell--unset { background: transparent; }
/* Выделение мышью перебивает цвет типа дня (2026-08-15).
   
   Цвет типа дня салон настраивает сам, и приходит он инлайновым
   style="background: …" — а инлайн сильнее любого класса. Из-за этого
   выделенными подсвечивались только клетки без своего цвета: в демо-базе
   147 ячеек из 248 на выделение никак не отзывались, и администратор,
   выделяя неделю мышью, не видел, что именно выбрал.
   
   !important здесь по делу: инлайн приходит из ДАННЫХ (настройка
   салона), а выделение — это СОСТОЯНИЕ интерфейса, и оно должно быть
   видно поверх любых настроек. */
.schedule-cell--selected {
  background: var(--color-accent) !important;
  color: var(--color-on-accent) !important;
}

.schedule-legend {
  list-style: none; display: flex; flex-wrap: wrap; gap: 0.75rem 1.5rem;
  padding: 0; margin: 0 0 1rem; font-size: 0.85rem; color: var(--color-muted);
}
.schedule-legend__item { display: flex; align-items: center; gap: 0.5rem; }
.schedule-legend__badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.6rem; height: 1.6rem; border-radius: 50%; font-weight: 600;
  font-size: 0.8rem; color: var(--color-chip-text);
}
.schedule-legend__badge--work { background: #f5f3ff; }
.schedule-legend__badge--vacation { background: #d6f0dd; }
.schedule-legend__badge--sick { background: #ffe8e8; }
.schedule-legend__badge--on_call { background: #ffedd5; }
.schedule-legend__badge--off {
  color: var(--color-muted); background: var(--color-surface-alt);
}
.schedule-legend__badge--business_trip { background: #fef9c3; }
.schedule-legend__badge--absence { color: #fff; background: #4b5563; }
/* «Показать записи» (2026-08-07) — точка в углу ячейки, где у мастера уже
   сидят клиенты. Разметку сервер отдаёт всегда, показывает её только класс
   на сетке: тумблер не должен ходить на сервер за каждой галочкой.
   Противоположный угол от буквы типа дня, чтобы они не наезжали. */
.schedule-cell__busy-dot { display: none; }
.schedule-grid--show-busy .schedule-cell__busy-dot {
  display: block;
  position: absolute; right: 2px; bottom: 2px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--color-error);
}
.schedule__busy-toggle { display: inline-flex; margin: 0 0 1rem; }
/* Подпись рядом с тумблером, а не враспор (2026-08-07, по просьбе
   основателя: «очень-очень далеко друг от друга»). Общее правило
   .journal__privacy-label растягивает подпись (flex: 1), чтобы в узком
   меню журнала переключатели встали в одну колонку — но здесь строка во
   всю ширину страницы, и растянутая подпись уносила тумблер к правому
   краю экрана. */
.schedule__busy-toggle .journal__privacy-label { flex: none; }

/* Чужая строка мастеру видна, но не нажимается (2026-08-07): курсор
   обычный, наведение ничего не подсвечивает. Приглушать её не стали —
   смотрят на неё именно затем, чтобы понять, кто ещё выходит в смену. */
.schedule-grid__row--readonly .schedule-cell { cursor: default; }

/* Сводка под сеткой графика (2026-08-07). Своя прокрутка по горизонтали:
   столбцов девять, и на узком экране таблица иначе распирала бы страницу. */
.schedule__summary { margin-top: 2rem; }
.schedule__summary-wrap { overflow-x: auto; }
.schedule-summary td, .schedule-summary th { white-space: nowrap; }

/* Пояснение под легендой (2026-08-07) — узкой колонкой, а не во всю
   ширину экрана: длинную строку в 200 символов глазом не прочитать. */
.schedule__legend-hint { max-width: 62ch; margin: -0.5rem 0 1rem; }

/* --- Финансы: отчёт по кассе за период (2026-08-03) -------------------- */

.finance__branch-form select {
  padding: 0.5rem 0.6rem; border: 1px solid var(--color-border); border-radius: 6px; font-size: 0.95rem;
}

.finance-tabs { display: flex; gap: 0.25rem; margin: 1rem 0; border-bottom: 1px solid var(--color-border); }
.finance-tabs__link {
  padding: 0.5rem 1rem; color: var(--color-muted); text-decoration: none; font-size: 0.9rem;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.finance-tabs__link:hover { color: var(--color-text); }
.finance-tabs__link--active { color: var(--color-link); border-bottom-color: var(--color-link); font-weight: 600; }

.finance__period-nav { margin-bottom: 1.5rem; }

/* Поиск по сумме над лентой операций (2026-09-08). Одной строкой и
   узкими полями: это уточнение к уже выбранному периоду, а не главная
   форма экрана — занимать собой пол-экрана ей незачем. */
.finance-amount-filter {
  display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
  margin: 0 0 1rem;
}
.finance-amount-filter label { color: var(--color-muted); font-size: 0.9rem; }
.finance-amount-filter input { width: 9rem; }

/* Галочки удаления в ленте операций (2026-09-08): узкая колонка, кнопка
   под таблицей — там же, где заканчивается список, который отмечали. */
.finance-table__check { width: 2rem; text-align: center; }
.finance-bulk { display: flex; align-items: center; gap: 0.75rem; margin: 0.75rem 0 1.5rem; }

/* Значок результата отправки в истории уведомлений (2026-09-08). Цвет
   плюс сама форма знака: по одному цвету статус читают не все. */
.notify-mark { font-weight: 700; }
.notify-mark--ok { color: var(--color-success-text); }
.notify-mark--fail { color: var(--color-error-text); }

/* Кнопки быстрой смены месяца над полями периода (2026-09-08). */
.month-shortcuts { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 0.5rem; }

.finance-summary {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem;
}
.finance-stat {
  border: 1px solid var(--color-border); border-radius: 8px; padding: 1rem; text-align: center;
}
.finance-stat__title { margin: 0 0 0.5rem; font-size: 0.85rem; color: var(--color-muted); font-weight: 400; }
.finance-stat__amount { margin: 0 0 0.75rem; font-size: 1.3rem; font-weight: 600; }
.finance-stat__amount--income { color: var(--color-success-text); }
.finance-stat__amount--expense { color: var(--color-error-text); }

.finance-donut {
  width: 88px; height: 88px; border-radius: 50%; margin: 0 auto 0.75rem;
  background: conic-gradient(var(--color-accent) 0% var(--cash-pct), #93c5fd var(--cash-pct) 100%);
  position: relative;
}
.finance-donut--empty { background: var(--color-border); }
.finance-donut__hole { position: absolute; inset: 20%; background: var(--color-bg); border-radius: 50%; }

.finance-legend { list-style: none; padding: 0; margin: 0; font-size: 0.8rem; color: var(--color-muted); text-align: left; }
.finance-legend li { display: flex; align-items: center; gap: 0.4rem; padding: 0.15rem 0; }
.finance-legend__dot { width: 0.6rem; height: 0.6rem; border-radius: 50%; flex-shrink: 0; }
.finance-legend__dot--cash { background: var(--color-accent); }
.finance-legend__dot--noncash { background: #93c5fd; }
/* Прогноз выручки (2026-09-14): «уже записано» — не факт, а ожидание.
   Отличается не только цветом, но и штриховкой: цвет один не должен
   быть единственным способом отличить факт от плана. */
.finance-legend__dot--plan {
  background: var(--color-accent-soft);
  border: 1px dashed var(--color-accent);
}

/* Разбивка «Расходы» по статьям, не по счёту (2026-08-06, по просьбе
   основателя) — горизонтальная полоса из сегментов вместо доната:
   «часть от целого» с длинными русскими названиями статей читается по
   полосе лучше, чем секторами круга (см. dataviz-скилл, «Part-to-whole» →
   stacked bar). 2px-зазор между сегментами — тот же приём разделения
   заливок, что рекомендован для любых стековых полос. */
.finance-stack {
  display: flex;
  height: 10px;
  border-radius: 5px;
  overflow: hidden;
  margin: 0 auto 0.75rem;
  background: var(--color-border);
}
.finance-stack__seg { height: 100%; }
.finance-stack__seg + .finance-stack__seg { border-left: 2px solid var(--color-bg); }
.finance-legend--categories li { justify-content: space-between; gap: 0.75rem; }
.finance-legend--categories .finance-legend__dot { border-radius: 3px; }

/* Столбчатый график выручки (2026-08-04) — чистый CSS, без библиотек,
   тот же приём, что у finance-donut выше: высота столбца задаётся
   переменной --bar-pct (процент от максимума за период).

   Горизонтальная разлиновка по суммам (2026-08-06, по просьбе
   основателя) — четыре линии-подсказки на 25/50/75/100% от максимума
   периода, background-image на самом же контейнере: раз нарисованная,
   не пересчитывается на каждый столбец, в отличие от рамок у каждого
   бара по отдельности. */
.finance-chart__bars {
  position: relative;
  display: flex; align-items: flex-end; gap: 0.4rem;
  height: 160px; overflow-x: auto; padding-top: 0.5rem;
  background-image: repeating-linear-gradient(
    to top,
    var(--color-border) 0, var(--color-border) 1px,
    transparent 1px, transparent 25%
  );
}
.finance-chart__bar-wrap {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  height: 100%; min-width: 1.5rem; flex: 1 1 0;
}
.finance-chart__bar {
  width: 100%; max-width: 1.75rem;
  height: calc(var(--bar-pct) * 1%);
  min-height: 2px;
  background: var(--color-accent);
  border-radius: 3px 3px 0 0;
  position: relative;
  z-index: 1;
}
.finance-chart__bar-label {
  font-size: 0.7rem; color: var(--color-muted); margin-top: 0.3rem; white-space: nowrap;
}

/* align-items: center — иначе цветные кнопки и ссылки-кнопки («Перемещение»,
   «Изменение остатков») тянутся на разную высоту и стоят вразнобой. */
.finance-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1.5rem;
  flex-wrap: wrap;
}

/* Аналитика — выручка по услугам/мастерам (2026-08-04): горизонтальные
   полосы, тот же приём с CSS-переменной --bar-pct, что и у finance-chart
   выше, только ширина, а не высота — список строк, не столбцов. */
.analytics-row {
  display: grid; grid-template-columns: 10rem 1fr 6rem;
  align-items: center; gap: 0.75rem; padding: 0.3rem 0;
}
.analytics-row__label { font-size: 0.85rem; }
.analytics-row__track { background: var(--color-surface-alt); border-radius: 4px; height: 0.6rem; overflow: hidden; }
.analytics-row__bar {
  height: 100%; width: calc(var(--bar-pct) * 1%); min-width: 2px;
  background: var(--color-accent); border-radius: 4px;
}
.analytics-row__amount { font-size: 0.85rem; text-align: right; white-space: nowrap; }

.finance-panel {
  border: 1px solid var(--color-border); border-radius: 8px; padding: 1rem 1.25rem;
  margin-bottom: 1.5rem; max-width: 420px;
}
.finance-panel h3 { margin-top: 0; }
.finance-panel label { display: block; margin-top: 0.75rem; font-size: 0.9rem; color: var(--color-muted); }
.finance-panel input, .finance-panel select {
  width: 100%; padding: 0.5rem 0.6rem; border: 1px solid var(--color-border); border-radius: 6px; font-size: 0.95rem;
}

.finance-table td, .finance-table th { white-space: nowrap; }
.finance-arrow { font-weight: 700; }
.finance-arrow--in { color: var(--color-success-text); }
.finance-arrow--out { color: var(--color-error-text); }

@media (max-width: 1000px) {
  .finance-summary { grid-template-columns: repeat(2, 1fr); }
}

/* --- Выделение клиентов, пагинация, индикатор поиска (2026-08-05) ------- */

/* Выпадающий список внутри таблицы не раздувает её (2026-09-14, замечание
   основателя по скриншоту «Что предложить после записи»: «справа наезд»).

   Браузер растягивает <select> по самому длинному варианту, а в салоне
   встречаются названия вроде «Комбо руки (маникюр + покрытие + укрепление,
   снятие бесплатно)» — шестьдесят знаков. Поле раздувалось до 450 пикселей
   и вылезало за край карточки вместе со всей таблицей.

   Название при этом не теряется: свёрнутый список обрезает его, а
   раскрытый показывает целиком. */
.client-table select { width: 100%; max-width: 100%; }

.client-table__check { width: 2.5rem; text-align: center; }
/* Галочка крупнее и с зоной нажатия во всю ячейку (2026-09-18, аудит
   мобильной версии: квадрат был 18×18 при норме касания 44). Ими
   отмечают клиентов для рассылки, и промах по соседней строке — это
   письмо не тому человеку.

   Сам квадрат 20 px, а недостающее добирает отступ ячейки: растянуть
   галочку до 44 значило бы поставить в таблицу огромный квадрат. */
.client-table__check input { cursor: pointer; }
/* Зона нажатия — вся ячейка: сам квадрат до нормы касания не растянуть,
   не превратив таблицу в поле квадратов, а ячейка добирает остальное. */
.client-table__check { padding: 0.75rem 0.5rem; }

/* Панель массового действия — появляется только когда что-то отмечено:
   постоянная кнопка «Удалить» на экране с клиентской базой слишком опасна. */
.bulk-bar {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.75rem 1rem;
  margin-bottom: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface-alt);
  font-size: 0.9rem;
}
.bulk-bar[hidden] { display: none; }
.bulk-bar .btn { margin-top: 0; }

.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  margin-top: 1rem;
}
.pagination__info { color: var(--color-muted); font-size: 0.9rem; }

/* Индикатор живого поиска: htmx сам вешает класс htmx-request на время
   запроса. Без указания opacity элемент был бы виден всегда. */
.htmx-indicator { opacity: 0; transition: opacity 0.2s; color: var(--color-muted); font-size: 0.85rem; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { opacity: 1; }

/* --- Просмотрщик фото работ (2026-08-05) --------------------------------
   Отдельный от общей модалки (#modal-overlay): та занята htmx-содержимым
   (карточка записи), и открытие фото затирало бы её. */

.photo-viewer {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(17, 24, 39, 0.85);
  cursor: zoom-out;
}
/* Тот же приём, что у .modal-overlay: авторский display:flex перебивает
   браузерное [hidden]{display:none}, поэтому скрытие задаём явно. */
.photo-viewer[hidden] { display: none; }
.photo-viewer img {
  max-width: 92vw;
  max-height: 92vh;
  border-radius: 8px;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
}
.photo-viewer .modal-overlay__close { color: #fff; font-size: 2rem; top: 1rem; right: 1.5rem; }

/* --- Обрезка фото сотрудника под круглую аватарку (2026-08-06) ---------
   Свой оверлей поверх модалки карточки сотрудника, тот же приём, что у
   .photo-viewer: отдельная область видимости, не занимает #modal-overlay,
   в котором лежит сама форма. */
.photo-cropper {
  position: fixed;
  inset: 0;
  z-index: 21;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(17, 24, 39, 0.85);
}
.photo-cropper[hidden] { display: none; }
.photo-cropper__panel {
  background: var(--color-surface);
  border-radius: 10px;
  padding: 1.5rem;
  max-width: 92vw;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.photo-cropper__header { display: flex; align-items: center; justify-content: space-between; gap: 2rem; }
.photo-cropper__header h2 { margin: 0; font-size: 1.2rem; }
.photo-cropper__stage {
  position: relative;
  /* line-height: 0 — иначе <img> как строчный элемент оставляет узкую
     щель под собой (обычное поведение inline-контента). */
  line-height: 0;
  background: var(--color-bg);
}
#photo-cropper-image { display: block; max-width: 80vw; max-height: 65vh; touch-action: none; }
.photo-cropper__box {
  position: absolute;
  border: 2px solid #fff;
  /* Раздутая тень без размытия закрашивает всё ВНЕ рамки, оставляя внутри
     прозрачное окно — тот же трюк, что у прицела кадрирования в DIKIDI. */
  box-shadow: 0 0 0 9999px rgba(17, 24, 39, 0.55);
  cursor: move;
  touch-action: none;
}
.photo-cropper__handle {
  position: absolute;
  width: 14px;
  height: 14px;
  background: #fff;
  border: 2px solid var(--color-accent);
  border-radius: 50%;
  touch-action: none;
}
.photo-cropper__handle--nw { top: -8px; left: -8px; cursor: nwse-resize; }
.photo-cropper__handle--se { bottom: -8px; right: -8px; cursor: nwse-resize; }
.photo-cropper__handle--ne { top: -8px; right: -8px; cursor: nesw-resize; }
.photo-cropper__handle--sw { bottom: -8px; left: -8px; cursor: nesw-resize; }
.photo-cropper__actions { display: flex; justify-content: flex-end; gap: 0.75rem; }

/* --- Импорт из CSV: список ошибок по строкам (2026-08-04) --------------- */

.import-errors {
  list-style: none; padding: 0; margin: 0; max-height: 20rem; overflow-y: auto;
  font-size: 0.85rem; color: var(--color-error-text);
}
.import-errors li { padding: 0.25rem 0; border-bottom: 1px solid var(--color-border); }
.import-errors li:last-child { border-bottom: none; }

/* --- Схема зарплаты: расшифровка базы процента (2026-08-27) ------------- */

.percent-base-hints {
  margin: 0.5rem 0 0; padding: 0.6rem 0.85rem; border-radius: 6px;
  background: var(--color-surface-alt); font-size: 0.85rem;
}
.percent-base-hints dt { font-weight: 600; margin-top: 0.5rem; }
.percent-base-hints dt:first-child { margin-top: 0; }
.percent-base-hints dd { margin: 0.1rem 0 0; color: var(--color-muted); }

/* Подходящее окно в листе ожидания (2026-08-27) — та же янтарная метка,
   что у минимальных остатков и пометки клиента: «обрати внимание», а не
   «ошибка». В узком сайдбаре это единственное цветное пятно, и его
   замечают. */
.journal-waitlist__slot {
  margin: 0.3rem 0 0;
  padding: 0.2rem 0.4rem;
  border-radius: 4px;
  background: rgba(251, 191, 36, 0.18);
  color: var(--color-warn-text);
  font-size: 0.78rem;
  font-weight: 500;
}
.journal-waitlist__slot-staff { display: block; font-weight: 400; opacity: 0.85; }
/* «Позвать» и отметка о приглашении (2026-09-17). Кнопка обычная, не
   акцентная: акцент в этой строке уже занят словом «Записать», а
   записать человека — действие важнее, чем позвать его. */
.journal-waitlist__invite { margin: 0.3rem 0 0; }
.journal-waitlist__invited {
  margin: 0.3rem 0 0;
  font-size: 0.78rem;
  color: var(--color-muted);
}

/* Поиск по салонам в переключателе (2026-08-27) — появляется, только
   когда салонов больше пяти. */
.sidebar__network-search {
  width: 100%;
  margin-bottom: 0.3rem;
  padding: 0.25rem 0.4rem;
  font-size: 0.85rem;
}

/* Выполнение плана в расчёте зарплаты (2026-08-27). Зелёный только когда
   план взят: остальное — обычная подпись, а не тревога. Человек, который
   идёт на 80%, ещё не отстаёт, и красить его красным несправедливо. */
.payroll-calc__plan {
  display: inline-block;
  padding: 0 0.3rem;
  border-radius: 4px;
  background: var(--color-surface-alt);
  font-size: 0.8rem;
}
.payroll-calc__plan--done { background: rgba(22, 163, 74, 0.15); color: #15803d; }

/* Две таблицы рядом в отчёте «кто наши клиенты» (2026-08-27): возраст и
   пол смотрят вместе, и прокручивать между ними незачем. */
.report-columns { display: grid; gap: 1.5rem; grid-template-columns: 1fr; }
@media (min-width: 48rem) {
  .report-columns { grid-template-columns: 1fr 1fr; }
}

/* --- Выбор свободного времени (2026-08-27) ----------------------------- */

.slot-picker { margin: 0.75rem 0; }
.slot-picker__controls {
  display: grid; gap: 0.4rem; margin-bottom: 0.75rem;
}
.slot-picker__days { display: flex; align-items: center; gap: 0.35rem; }
.slot-picker__days input { flex: 1 1 auto; }

/* Время — сеткой кнопок, а не выпадающим списком: администратор видит
   сразу всю картину дня и выбирает одним нажатием, а не открывает список
   и листает его. */
.slot-picker__slots {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));
  gap: 0.35rem;
  max-height: 16rem;
  overflow-y: auto;
}
.slot-picker__slot { width: 100%; text-align: center; }

/* Назначение роли (2026-08-27): выпадающий список и кнопка в одну
   строку — иначе таблица растягивается вдвое. */
.role-assign { display: flex; gap: 0.4rem; align-items: center; }

/* --- Вкладки карточки клиента (2026-08-27) ----------------------------- */

/* Раньше карточка шла одной колонкой на пятьсот строк: форма, деньги,
   абонементы, визиты, фото, приглашения. Чтобы посмотреть, когда человек
   был в последний раз, приходилось прокручивать мимо всего остального.

   Переключение — чистый CSS, без похода на сервер: данные уже в
   контексте. На radio-кнопках, а не на JS: работает при выключенном JS,
   доступно с клавиатуры и не ломается, когда htmx перерисовывает модалку.

   Переключатели — ПРЯМЫЕ дети .client-tabs, а не внутри ленты: комбинатор
   ~ работает только между соседями одного родителя, и из ленты панели
   были бы недостижимы. Поймано живой проверкой: вкладки отрисовались, но
   не показывали ничего. */
.client-tabs > input { position: absolute; opacity: 0; pointer-events: none; }

.client-tabs__strip { display: flex; flex-wrap: wrap; gap: 0.25rem; margin: 0.75rem 0; }
.client-tabs__strip label {
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  font-size: 0.9rem;
  cursor: pointer;
  white-space: nowrap;
}
.client-tabs__strip label:hover { background: var(--color-surface-alt); }

.client-tab { display: none; }

/* Порядок правил ОБЯЗАН совпадать с порядком <input> в разметке:
   nth-of-type считает их по позиции, а не по имени. Вкладка
   «Предпочтения» вставлена второй (2026-09-14) — вместе с ней
   сдвинулись все следующие номера. */
.client-tabs > input:nth-of-type(1):checked ~ .client-tabs__panels .client-tab[data-tab="main"],
.client-tabs > input:nth-of-type(2):checked ~ .client-tabs__panels .client-tab[data-tab="prefs"],
.client-tabs > input:nth-of-type(3):checked ~ .client-tabs__panels .client-tab[data-tab="consent"],
.client-tabs > input:nth-of-type(4):checked ~ .client-tabs__panels .client-tab[data-tab="docs"],
.client-tabs > input:nth-of-type(5):checked ~ .client-tabs__panels .client-tab[data-tab="stats"],
.client-tabs > input:nth-of-type(6):checked ~ .client-tabs__panels .client-tab[data-tab="packs"],
.client-tabs > input:nth-of-type(7):checked ~ .client-tabs__panels .client-tab[data-tab="visits"],
.client-tabs > input:nth-of-type(8):checked ~ .client-tabs__panels .client-tab[data-tab="photos"],
.client-tabs > input:nth-of-type(9):checked ~ .client-tabs__panels .client-tab[data-tab="referrals"] {
  display: block;
}

.client-tabs > input:nth-of-type(1):checked ~ .client-tabs__strip label:nth-of-type(1),
.client-tabs > input:nth-of-type(2):checked ~ .client-tabs__strip label:nth-of-type(2),
.client-tabs > input:nth-of-type(3):checked ~ .client-tabs__strip label:nth-of-type(3),
.client-tabs > input:nth-of-type(4):checked ~ .client-tabs__strip label:nth-of-type(4),
.client-tabs > input:nth-of-type(5):checked ~ .client-tabs__strip label:nth-of-type(5),
.client-tabs > input:nth-of-type(6):checked ~ .client-tabs__strip label:nth-of-type(6),
.client-tabs > input:nth-of-type(7):checked ~ .client-tabs__strip label:nth-of-type(7),
.client-tabs > input:nth-of-type(8):checked ~ .client-tabs__strip label:nth-of-type(8),
.client-tabs > input:nth-of-type(9):checked ~ .client-tabs__strip label:nth-of-type(9) {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: #fff;
}

/* Кнопка сохранения прилипает к низу — форма длиннее экрана, и мотать её
   вниз ради одного нажатия незачем (2026-08-27, по образцу Арники). */
.client-detail__save {
  position: sticky;
  bottom: 0;
  z-index: 3;
  margin-top: 1rem;
  padding: 0.6rem 0;
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
}

/* Аватар клиента в карточке (2026-08-27): в списке он был, а в карточке
   нет — по фото человека узнают быстрее, чем по имени, особенно когда в
   базе три Марии Ивановой. */
.client-detail__avatar { float: right; margin: 0 0 0.5rem 1rem; }
.client-detail__avatar-img,
.client-detail__avatar-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 4rem;
  height: 4rem;
  border-radius: 50%;
  object-fit: cover;
}
.client-detail__avatar-empty {
  background: var(--color-surface-alt);
  color: var(--color-muted);
  font-size: 1.5rem;
  font-weight: 600;
}

/* Текст согласия — то, что клиент подписывает. Показывается целиком, с
   прокруткой: по 152-ФЗ согласие должно быть информированным, то есть
   человек обязан видеть предмет, а не только галочку. */
.consent-doc {
  margin: 1rem 0;
  padding: 0.85rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface-alt);
  max-height: 18rem;
  overflow-y: auto;
  font-size: 0.9rem;
}
.consent-doc h3 { margin: 0 0 0.5rem; font-size: 1rem; }

/* Текст шаблона документа (2026-08-27) — моноширинным: он свёрстан
   пробелами (отступы под подписи, дата справа), и пропорциональный шрифт
   показывал бы одно, а на бумагу шло бы другое. */
.document-template__body,
.document-template__preview {
  width: 100%;
  font-family: "Cascadia Mono", Consolas, "Liberation Mono", monospace;
  font-size: 0.85rem;
  line-height: 1.45;
}
.document-template__preview {
  max-height: 24rem;
  overflow: auto;
  padding: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface-alt);
  white-space: pre-wrap;
}

/* --- Задвоенные карточки (2026-08-27) ---------------------------------- */

/* Две карточки рядом: решение принимается сравнением, и колонки должны
   быть одинаковой ширины, иначе глаз цепляется за размер, а не за цифры. */
.dupe__reason { margin: 0 0 0.6rem; font-weight: 500; }
.dupe__pair { display: grid; gap: 1rem; grid-template-columns: 1fr; }
@media (min-width: 48rem) {
  .dupe__pair { grid-template-columns: 1fr 1fr; }
}
.dupe__card {
  padding: 0.85rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
}
.dupe__name { margin: 0 0 0.5rem; font-weight: 600; }
.dupe__facts { margin: 0 0 0.75rem; font-size: 0.9rem; }
.dupe__facts dt { color: var(--color-muted); font-size: 0.8rem; margin-top: 0.4rem; }
.dupe__facts dd { margin: 0; }

/* --- Склад: минимумы, сроки годности, инвентаризация (2026-08-27) ------ */

/* «Пора заказать» и «скоро истечёт» — предупреждение, а не ошибка:
   янтарный, а не красный. Красным в этом разделе помечен отрицательный
   остаток, то есть настоящая поломка учёта, и путать их нельзя. */
.stock-low {
  display: inline-block;
  padding: 0 0.3rem;
  border-radius: 4px;
  background: rgba(251, 191, 36, 0.2);
  color: var(--color-warn-text);
  font-weight: 500;
}
/* Просроченное — уже не предупреждение. */
.stock-expired {
  display: inline-block;
  padding: 0 0.3rem;
  border-radius: 4px;
  background: rgba(239, 68, 68, 0.15);
  color: #b91c1c;
  font-weight: 500;
}

/* Поле сканера над листом пересчёта (2026-08-27) — курсор стоит в нём
   постоянно, поэтому оно заметное и отделено от таблицы. */
.stock-count__scan {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0.75rem 0;
  padding: 0.6rem 0.85rem;
  border-radius: 8px;
  background: var(--color-surface-alt);
}
.stock-count__scan input { max-width: 16rem; }

/* Найденная сканером строка — подсветка на пару секунд взглядом, а не
   таймером: человек смотрит на неё сразу после сканирования. */
.stock-count__row--found { background: rgba(251, 191, 36, 0.15); }

/* Поле ввода в листе пересчёта: узкое, потому что вводят числа, и с
   выравниванием по правому краю — так столбец читается сверху вниз. */
.stock-count__input {
  width: 6rem;
  text-align: right;
}
/* Взвешивание с тарой (2026-09-10, тема 958): вес и число ёмкостей
   стоят парой — это одно действие, а не два разных ввода. */
.stock-count__weigh { display: inline-flex; gap: 0.25rem; }
.stock-count__weigh .stock-count__gross { width: 4.5rem; }
.stock-count__weigh .stock-count__containers { width: 3rem; }
/* Примечание к строке (2026-09-10, тема 957) — последней графой, шире
   числовых полей: в него пишут словами. */
.stock-count__comment {
  width: 100%;
  min-width: 9rem;
}
/* Подсказки в листе пересчёта — своей строкой. Обычная .settings__hint
   строчная, и в узкой ячейке она прилипала к числу: «1 250гр»,
   «Краска 7.0Краски», а подсказка про тару разрывалась пополам. */
.stock-count-table .settings__hint { display: block; font-size: 0.8rem; }

/* --- Поддержка: справка, пожелания, обновления (2026-08-27) ------------- */

/* Две колонки на широком экране, одна на узком: контакты и «что нового»
   справа, содержание слева — как в окне поддержки Арники, но страницей,
   а не модалкой на всю высоту. */
.help-grid { display: grid; gap: 1rem; grid-template-columns: 1fr; }
@media (min-width: 62rem) {
  .help-grid { grid-template-columns: minmax(0, 2fr) minmax(18rem, 1fr); align-items: start; }
}

.help-section__title { font-size: 0.95rem; margin: 1rem 0 0.35rem; color: var(--color-muted); }
.help-list { list-style: none; padding: 0; margin: 0; }
.help-list li { padding: 0.3rem 0; border-bottom: 1px solid var(--color-border); }
.help-list li:last-child { border-bottom: none; }
.help-update__date { color: var(--color-muted); font-size: 0.85rem; margin-right: 0.4rem; }

.help-contacts { margin: 1rem 0 0.5rem; }
.help-contacts dt { color: var(--color-muted); font-size: 0.85rem; margin-top: 0.6rem; }
.help-contacts dd { margin: 0.1rem 0 0; }

.help-article p, .help-article ul, .help-article ol { margin: 0 0 0.85rem; }
.help-article li { margin: 0.2rem 0; }
.help-article code {
  padding: 0.1rem 0.35rem; border-radius: 4px;
  background: var(--color-surface-alt); font-size: 0.9em;
}

/* Пожелания: счётчик голосов слева, текст справа — так список читается
   сверху вниз по числам, а это главное, ради чего голоса и собираются. */
.wish-list { list-style: none; padding: 0; margin: 0; }
.wish {
  display: flex; gap: 0.85rem; align-items: flex-start;
  padding: 0.7rem 0; border-bottom: 1px solid var(--color-border);
}
.wish:last-child { border-bottom: none; }
.wish__vote { flex: 0 0 auto; }
.wish__vote-btn {
  display: flex; flex-direction: column; align-items: center; gap: 0.1rem;
  min-width: 5.5rem; padding: 0.35rem 0.5rem; line-height: 1.2;
}
.wish__vote-btn--on { border-color: var(--color-primary); color: var(--color-primary); }
.wish__count { font-size: 1.1rem; font-weight: 600; }
.wish__vote-label { font-size: 0.7rem; }
.wish__body { flex: 1 1 auto; min-width: 0; }
.wish__title { display: block; font-weight: 500; }
.wish__meta { font-size: 0.8rem; color: var(--color-muted); }
.wish__text { margin: 0.3rem 0 0; font-size: 0.9rem; }
.wish__answer {
  margin: 0.4rem 0 0; padding: 0.5rem 0.75rem; border-radius: 6px;
  background: var(--color-surface-alt); font-size: 0.9rem;
}

.release__date { color: var(--color-muted); font-size: 0.85rem; margin: 0 0 0.2rem; }
.release__title { margin: 0 0 0.6rem; font-size: 1.1rem; }

/* --- Загрузка справочников из Excel (2026-08-27) ------------------------ */

.import-hub__select { max-width: 22rem; }

/* Предупреждение про приход товара: он не дедуплицируется, и повторная
   загрузка удвоит остаток. Заметная плашка, а не серая подсказка — это
   единственное место в загрузках, где ошибка молча портит данные. */
.import-hub__warning {
  margin: 0.75rem 0; padding: 0.75rem 1rem; border-radius: 8px;
  border-left: 3px solid var(--color-warning, #d97706);
  background: var(--color-surface-alt); font-size: 0.9rem;
}

/* --- Уведомления: список видов и шаблон (2026-08-04) -------------------- */

.notify-group__title { font-size: 1rem; margin: 1.5rem 0 0.5rem; }
.notify-row__preview { font-size: 0.85rem; color: var(--color-muted); margin-top: 0.25rem; }
/* Сколько своих текстов у вида (2026-09-18). Мельче и тише самого
   текста: это подпись о возможности, а не содержание сообщения. */
.notify-row__variants {
  margin-top: 0.3rem;
  font-size: 0.8rem;
  color: var(--color-link);
}
.notify-row__state { text-align: right; white-space: nowrap; }
.notify-state { font-size: 0.85rem; color: var(--color-muted); }
.notify-state--on { color: var(--color-success-text); }
.notify-placeholders code {
  display: inline-block; padding: 0.15rem 0.5rem; margin: 0 0.25rem 0.25rem 0;
  border-radius: 4px; background: var(--color-surface-alt); font-size: 0.85rem;
}
.notify-preview {
  padding: 0.75rem 1rem; border-radius: 8px; background: var(--color-accent-soft);
  font-size: 0.95rem; max-width: 640px;
}

/* --- Настройки: своя ссылка (2026-08-03) -------------------------------- */

.settings__section {
  max-width: 640px;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 1.25rem 1.5rem;
  margin-top: 1rem;
}
.settings__section h2 { margin-top: 0; font-size: 1.1rem; }
.settings__hint { color: var(--color-muted); font-size: 0.9rem; }
/* Подзаголовок внутри длинной формы настроек (2026-09-09): делит её на
   смысловые части, не заводя вторую кнопку «Сохранить». */
.settings__subtitle { margin: 1.5rem 0 0.5rem; font-size: 1rem; }
/* Предупреждение о смене смысла настройки (2026-08-09): не ошибка и не
   запрет, но пропустить его нельзя — за ним стоят деньги сотрудников. */
.settings__hint--warning {
  padding: 0.6rem 0.85rem;
  border-left: 3px solid var(--color-warning, #b8860b);
  background: var(--color-surface-muted, rgba(184, 134, 11, 0.07));
  color: var(--color-text);
}
/* Что уже загружено в обложку онлайн-записи (2026-08-08) — показываем
   рядом с галочкой «убрать», чтобы владелец видел, что именно снимает. */
.settings__cover-preview {
  display: block; width: 100%; max-width: 420px; max-height: 180px;
  object-fit: cover; border-radius: 8px; margin-top: 0.75rem;
}
.settings__logo-preview {
  display: block; width: 72px; height: 72px; border-radius: 50%;
  object-fit: cover; margin-top: 0.75rem; border: 1px solid var(--color-border);
}
.settings__current-link { margin-bottom: 1.5rem; }
.settings__section label { display: block; margin-top: 1rem; font-size: 0.9rem; color: var(--color-muted); }
.settings__slug-field {
  display: flex;
  align-items: center;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  overflow: hidden;
}
.settings__slug-prefix { padding: 0.6rem 0.75rem; background: var(--color-surface-alt); color: var(--color-muted); font-size: 0.9rem; white-space: nowrap; }
/* min-width: 0 — иначе поле не сжимается ниже своей «ширины в символах»,
   и на узком экране адрес салона обрезался рамкой контейнера
   (2026-08-29, поймано на телефоне). */
.settings__slug-field input { flex: 1; min-width: 0; border: none; padding: 0.6rem 0.75rem; font-size: 1rem; }
.settings__slug-field input:focus { outline: none; }

/* --- Матрица доступа (2026-08-05, по просьбе основателя) ----------------
   Строки — функции, столбцы — роли. Первый столбец липкий: при прокрутке
   вбок вправо название функции должно оставаться на виду, иначе галочки
   в узком окне ставятся вслепую. */

.access-matrix__scroll { overflow-x: auto; }

/* Прокручиваемые блоки позиционированы (2026-09-12, найдено сплошной
   проверкой вёрстки на ширине телефона).

   Внутри широких таблиц стоят подписи для читалок экрана
   (.visually-hidden) — квадратик 1×1 пиксель, невидимый глазу. Он
   позиционирован абсолютно, а считается от ближайшего позиционированного
   предка. Если такого нет, отсчёт идёт от всей страницы — и подпись,
   которая должна прятаться внутри прокрутки таблицы, оказывается на
   координате 541 при экране 375.

   Видно её не было, а вот последствия были: страница получала
   горизонтальную прокрутку, и нижняя панель вкладок растягивалась вместе
   с документом — кнопка «Ещё» уезжала целиком за край экрана. На
   телефоне это отрезало половину разделов.

   position: relative у обёртки возвращает отсчёт внутрь прокрутки.
   Ширина страницы на «Правах доступа»: было 542 при экране 375, стало
   375. */
.table-scroll,
.access-matrix__scroll,
.schedule__grid-wrap,
.schedule__summary-wrap { position: relative; }

.access-matrix {
  width: 100%;
  border-collapse: collapse;
  background: var(--color-surface);
}
.access-matrix th,
.access-matrix td {
  border-bottom: 1px solid var(--color-border);
  padding: 0.6rem 0.75rem;
  text-align: left;
  vertical-align: top;
}
.access-matrix thead th {
  position: sticky;
  top: 0;
  background: var(--color-surface-alt);
  font-size: 0.9rem;
  z-index: 1;
}
.access-matrix__feature {
  position: sticky;
  left: 0;
  background: var(--color-surface);
  min-width: 16rem;
}
.access-matrix thead .access-matrix__feature { background: var(--color-surface-alt); z-index: 2; }
.access-matrix__role { text-align: center; white-space: nowrap; }
.access-matrix__group th {
  background: var(--color-surface-alt);
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-muted);
}
.access-matrix__label { display: block; }
.access-matrix__help {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.8rem;
  color: var(--color-muted);
}
.access-matrix__cell { text-align: center; }
.access-matrix__box { display: inline-flex; padding: 0.25rem; cursor: pointer; }
.access-matrix__box input { width: 1.15rem; height: 1.15rem; cursor: pointer; }
.access-matrix__owner-only,
.access-matrix__overrides { margin: 0.5rem 0 0; padding-left: 1.2rem; color: var(--color-muted); }
.access-matrix__overrides { color: inherit; }

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

/* --- Портфолио работ (2026-08-05, по просьбе основателя) ----------------
   Сетка одинаковых плиток: и внутри системы, и на публичной странице
   салона. Порядок задаётся стрелками — перетаскивание мышью потребовало
   бы JS-библиотеки (правило №7), а стрелки работают и с телефона. */

.portfolio__grid {
  list-style: none;
  padding: 0;
  margin: 1rem 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 1rem;
}
.portfolio__item {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--color-surface);
  display: flex;
  flex-direction: column;
}
.portfolio__item--hidden { opacity: 0.55; }
.portfolio__thumb {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  display: block;
}
.portfolio__meta {
  padding: 0.5rem 0.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  font-size: 0.85rem;
}
.portfolio__caption { font-weight: 600; }
.portfolio__author, .portfolio__service { color: var(--color-link); text-decoration: none; }
.portfolio__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.4rem 0.6rem 0.6rem;
  margin-top: auto;
}
.portfolio__actions form { margin: 0; }
/* Плитки-источники: снимок из «Фото работ», по нажатию уходит в витрину. */
.portfolio__grid--source { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }
.portfolio__pick {
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  display: block;
  width: 100%;
}
.portfolio__source { margin-top: 1.5rem; }

/* --- Вакансии (2026-08-05) --------------------------------------------- */

.vacancy__publish { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.75rem; }
.vacancy-card {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 1rem;
  margin-bottom: 1rem;
  background: var(--color-surface);
}
.vacancy-card h2 { margin: 0 0 0.25rem; font-size: 1.1rem; }
.vacancy-card__meta { color: var(--color-muted); font-size: 0.9rem; margin: 0 0 0.75rem; }
.vacancy-card__text { margin: 0 0 0.75rem; }
.badge--muted { background: var(--color-surface-alt); color: var(--color-muted); }

/* Буква типа дня в ячейке графика (2026-08-05, по просьбе основателя —
   «не пиши полностью Отпуск, напиши О как сверху»). Крупнее обычного
   текста ячейки: одна буква на белом фоне иначе теряется, а времени
   смены, которое задавало прежний размер, в таких ячейках нет. */
.schedule-cell__letter { font-size: 1.05rem; font-weight: 600; line-height: 1.6; }

/* --- Гарантия: переделка бесплатно (2026-08-05, по просьбе основателя) ---
   Свой цвет и в меню слота, и на карточке журнала: по виду должно быть
   сразу понятно, что это время не приносит денег, и что кнопка в меню —
   не обычная запись. Бирюзовый: в палитре проекта не занят ни статусами
   записи, ни типами дня в графике. */

.slot-menu__warranty {
  width: 100%;
  margin-top: 0.5rem;
  background: var(--color-warranty);
  color: #fff;
}
.slot-menu__warranty:hover { background: var(--color-warranty-hover); }

.badge--warranty { background: var(--color-warranty-chip-bg); color: var(--color-warranty-on-chip); }
.appointment-form__hint--warranty { color: var(--color-warranty-text); font-weight: 500; }

/* --- Телефон клиента и тумблер приватности в календаре (2026-08-05) ----- */

.journal-grid__appointment-phone {
  display: block;
  font-size: 0.7rem;
  color: var(--color-chip-text);
  opacity: 0.75;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Тумблер «Скрыть имена и телефоны». Класс на <html>, а не на сетке: сетка
   перерисовывается htmx-ом при отметке визита, и класс на ней слетал бы. */
html.journal-names-hidden .journal-grid__appointment-client,
html.journal-names-hidden .journal-grid__appointment-phone,
/* Комментарий прячется вместе с именем (2026-08-06): в нём тоже попадаются
   личные подробности — «после развода», «беременна», «не любит N». */
html.journal-names-hidden .journal-grid__appointment-comment,
/* Пометка клиента — по той же причине (2026-08-27): «аллергия», «после
   операции» — это здоровье, и журнал бывает виден посетителю через плечо
   администратора. */
html.journal-names-hidden .journal-grid__appointment-note {
  display: none;
}

.journal__privacy {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--color-muted);
  user-select: none;
  white-space: nowrap;
}
/* Подпись занимает всё свободное место, тумблер и «Вкл./Откл.» жмутся
   вправо — так у обеих строк меню переключатели стоят в одну колонку. */
.journal__privacy-label { flex: 1; }
.journal__privacy-state { min-width: 3.2rem; }
/* Сам чекбокс не показываем, но и не убираем из DOM: он остаётся тем, что
   реально хранит состояние и ловит клавиатуру. */
.switch__input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.switch {
  width: 2.4rem;
  height: 1.35rem;
  border-radius: 999px;
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border);
  display: inline-flex;
  align-items: center;
  padding: 2px;
  transition: background 0.15s ease;
  flex-shrink: 0;
}
.switch__knob {
  width: 1.05rem;
  height: 1.05rem;
  border-radius: 50%;
  background: var(--color-muted);
  transition: transform 0.15s ease, background 0.15s ease;
}
.switch__input:checked + .switch { background: var(--color-accent); border-color: var(--color-link); }
.switch__input:checked + .switch .switch__knob { transform: translateX(1.05rem); background: #fff; }
.switch__input:focus-visible + .switch { outline: 2px solid var(--color-accent); outline-offset: 2px; }

/* Тумблер как поле формы (2026-08-05) — тот же .switch, что и в журнале,
   но без margin-left: auto и текста состояния: здесь это просто строка
   формы «Используется», а не элемент тулбара. */
/* label.switch-row, а не просто .switch-row: внутри .appointment-form
   действует правило `.appointment-form label { display: block }` — оно
   специфичнее одного класса и распрямило бы тумблер в блок, разложив
   подпись и переключатель по разным строкам. */
label.switch-row {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0.75rem 0;
  cursor: pointer;
  user-select: none;
}
/* Тот же тумблер в строке поиска списка сотрудников (2026-08-06) — margin
   формы-столбика (0.75rem сверху/снизу) здесь ни к чему, строка и так
   выровнена по высоте с полем поиска и кнопкой «Найти». */
.client-list__staff-toggle {
  margin: 0;
  font-size: 0.9rem;
  color: var(--color-muted);
}

/* Две таблицы выручки под лентой «Доходов и расходов» (2026-08-05) —
   рядом на широком экране, друг под другом на узком. */
.finance-breakdown {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 1.5rem;
  margin-top: 2rem;
}
.finance-breakdown__block h2 { font-size: 1rem; margin: 0 0 0.75rem; }

/* --- Линия текущего времени в календаре (2026-08-05, по образцу
   присланного скриншота) -------------------------------------------------
   Тянется через все колонки мастеров. Позицию по вертикали задаёт JS в
   пикселях (см. journal.html), горизонталь — на всю ширину сетки.

   pointer-events: none — линия лежит поверх слотов, и без этого она
   перехватывала бы клики по времени под собой; заодно её нельзя выделить
   мышью, как и просил основатель. */

.journal-grid { position: relative; }
.journal-grid__now {
  position: absolute;
  left: 0;
  /* Ширину задаёт скрипт по всей прокручиваемой ширине сетки (2026-08-10,
     по скриншоту основателя: «линия закончилась»). right: 0 привязывал её
     к видимой области, и на прокрутке линия обрывалась ровно там, где
     кончался экран в момент отрисовки. */
  height: 0;
  border-top: 2px solid #dc2626;
  pointer-events: none;
  user-select: none;
  z-index: 3;
}
html.journal-nowline-hidden .journal-grid__now { display: none; }

/* Скидка в карточке записи (2026-08-05) — строка между услугами и итогом. */
.sale-summary__discount { color: var(--color-error-text); }
.sale-summary__reason {
  display: block;
  font-size: 0.8rem;
  color: var(--color-muted);
}

/* --- Переключатель темы тремя иконками (2026-08-06, по образцу
   присланного скриншота) --------------------------------------------------
   Сегментированный контрол: монитор — как в системе, солнце — светлая,
   луна — тёмная. Выбранная подсвечивается заливкой. */

.theme-switch {
  display: inline-flex;
  gap: 0.25rem;
  margin-top: 0.75rem;
  padding: 0.25rem;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  background: var(--color-surface-alt);
}
.theme-switch__option {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.4rem;
  height: 2.1rem;
  padding: 0;
  border: none;
  border-radius: 8px;
  background: none;
  color: var(--color-muted);
  cursor: pointer;
}
.theme-switch__option:hover { color: var(--color-text); }
.theme-switch__option--active {
  background: var(--color-surface);
  color: var(--color-text);
  /* Тонкая обводка вместо тени: на тёмной теме тень не видна вовсе, а
     выбранную иконку отличать надо в обеих. */
  box-shadow: inset 0 0 0 1px var(--color-border);
}
.theme-switch__option:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}
.theme-switch__icon {
  width: 18px;
  height: 18px;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- Кнопка настроек календаря (2026-08-06, по просьбе основателя) ------
   Тумблеры «Линия текущего времени» и «Имена и телефоны» убраны из
   тулбара под шестерёнку: трогают их редко, а места занимали много.
   Нативный <details>, без JS — тот же приём, что у групп в сайдбаре. */

.journal-settings {
  position: relative;
  margin-left: auto;
}
.journal-settings__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.2rem;
  height: 2.2rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  color: var(--color-muted);
  cursor: pointer;
  list-style: none;
}
/* Стандартный треугольник <summary> убираем в обоих движках. */
.journal-settings__button::-webkit-details-marker { display: none; }
.journal-settings__button::marker { content: ""; }
.journal-settings__button:hover { color: var(--color-text); background: var(--color-surface-alt); }
.journal-settings[open] .journal-settings__button {
  color: var(--color-text);
  background: var(--color-surface-alt);
}
.journal-settings__icon {
  width: 18px;
  height: 18px;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.journal-settings__panel {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 6;
  min-width: 17rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 0.85rem;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  background: var(--color-surface);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}

/* «На сегодня» (2026-08-06) — над календарём, во всю ширину сайдбара,
   видна только когда открыт не сегодняшний день. Строка под неё есть
   всегда и держит свою высоту (2026-08-07, заметил основатель): иначе
   появление кнопки сдвигало календарь вниз, и число под курсором уезжало
   из-под мыши. */
.journal-calendar__today-row {
  min-height: 1.9rem;
  margin-bottom: 0.5rem;
}
.journal-calendar__today {
  display: block;
  text-align: center;
  padding: 0.35rem 0;
  font-size: 0.85rem;
}

/* Второй месяц в сайдбаре (2026-08-06) — свой заголовок без стрелок:
   стрелки листают пару целиком. */
.journal-calendar__subheader {
  margin-top: 0.75rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--color-border);
  text-align: center;
  font-size: 0.85rem;
  text-transform: capitalize;
  color: var(--color-muted);
}

/* Зарплата: три шага привычного порядка — рассчитать, проверить,
   выплатить (2026-08-06, по просьбе основателя, по образцу DIKIDI).
   Раньше разделы были просто ссылками, и владелец не понимал, что после
   расчёта нужно ещё куда-то идти. */
.payroll-steps {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  list-style: none;
  margin: 0 0 1.5rem;
  padding: 0;
}
.payroll-steps__item {
  padding: 0.35rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  font-size: 0.85rem;
  color: var(--color-muted);
}
.payroll-steps__item a { color: inherit; text-decoration: none; }
.payroll-steps__item a:hover { color: var(--color-text); }
.payroll-steps__item--current {
  border-color: var(--color-link);
  color: var(--color-link);
  font-weight: 600;
}
.payroll-period { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 1rem; margin-bottom: 1.5rem; }
.payroll-period__field { display: flex; flex-direction: column; gap: 0.3rem; }
.payroll-period__field label { font-size: 0.8rem; color: var(--color-muted); }
.payroll-period button { margin-top: 0; }
.payroll-calc__hint { color: var(--color-muted); font-size: 0.8rem; }
/* Причина, по которой сотруднику не начислили, — не ошибка, а обычное
   состояние («уже начисляли»), поэтому приглушённо, а не красным. */
.payroll-calc__blocked { color: var(--color-muted); font-size: 0.85rem; }
.payroll-calc__confirm { margin-top: 1.5rem; }
.payroll-calc__total { margin: 0 0 0.5rem; }
/* Экран «Выдать зарплату» (2026-08-09). Готовые периоды — крупными
   кнопками-переключателями, а не выпадающим списком: это главный выбор
   экрана, и он должен читаться с одного взгляда. */
.payroll-payout__lead { color: var(--color-muted); margin: 0 0 1rem; }
/* Изменение прайса (2026-08-09). Поля цены и даты — узкие: это клетки
   таблицы, а не форма, и растянутые на всю ширину они разъезжались бы. */
.price-change__bulk { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin-bottom: 1rem; }
.price-change__bulk label { font-size: 0.8rem; color: var(--color-muted); }
.price-change__bulk button { margin-top: 0; }
.price-change__table input[type="number"] { width: 7rem; }
.price-change__bulk input[type="number"] { width: 5rem; }
/* «%» вплотную к полю процента: подпись единицы, а не отдельный элемент
   формы, поэтому без отступа слева. */
.price-change__unit { margin-left: -0.25rem; color: var(--color-muted); }
.price-change__table input[type="date"] { width: 10rem; }
/* Строка с уже назначенным повышением — подсветкой слева, а не фоном:
   строк много, сплошная заливка превратила бы таблицу в полосатую. */
.price-change__row--planned td:first-child { border-left: 3px solid var(--color-accent); }
/* Снижение цены (2026-08-09, вместе с переименованием в «Изменение
   прайса»): «−500 ₽» бегло читается как «500 ₽», а перепутать
   подорожание с удешевлением дорого — поэтому ещё и цветом. */
.price-change__diff--down strong { color: var(--color-danger, #b91c1c); }
.payroll-payout__period { margin-bottom: 1.5rem; }
.payroll-payout__presets { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 0.75rem; }
/* Период выбирается кнопками (2026-08-09, просьба основателя). Нажатая
   остаётся выделенной: иначе, посмотрев вчерашний день и вернувшись
   глазами наверх, не поймёшь, что сейчас на экране. */
.payroll-payout__preset {
  margin-top: 0;
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
}
.payroll-payout__preset--on {
  border-color: var(--color-link);
  background: var(--color-accent);
  color: var(--color-on-accent);
}
.payroll-payout__dates { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.payroll-payout__dates-label { color: var(--color-muted); font-size: 0.9rem; }
.payroll-payout__dates label { font-size: 0.8rem; color: var(--color-muted); }
.payroll-payout__dates button { margin-top: 0; }
/* Кнопки бюджета в один ряд (2026-09-14, замечание основателя: «наехали
   кнопки»). align-items: flex-start — иначе раскрытая форма «Строки
   бюджета» растянула бы соседнюю кнопку на всю свою высоту. */
.budget-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: flex-start;
  margin-bottom: 1.5rem;
}
.budget-actions form { margin: 0; }

.payroll-scheme-form { margin-bottom: 1.5rem; }
.payroll-scheme-form summary { list-style: none; cursor: pointer; display: inline-block; }
.payroll-scheme-form summary::-webkit-details-marker { display: none; }
.payroll-scheme-form form { margin-top: 1rem; max-width: 480px; }
.payroll-payout { display: inline-block; margin: 0 0 0 0.75rem; }
.payroll-payout button { margin-top: 0; }

/* «нет мастера» рядом с галочкой онлайн-записи (2026-08-06, по жалобе
   основателя «ссылка не работает»): услуга помечена для виджета, но взять
   её через виджет некому. Ссылкой, а не просто плашкой — ведёт туда, где
   связку заводят. */
/* Кружок вместо широкой плашки (2026-08-30, замечание основателя:
   «некрасивая большая кнопка»). В столбце из полутора сотен услуг плашка
   «нет мастера» повторялась у каждой строки и перетягивала на себя весь
   экран; сам текст никуда не делся — он во всплывающей подсказке. */
.service-online-warning {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.15rem;
  height: 1.15rem;
  margin-left: 0.4rem;
  border-radius: 50%;
  background: var(--color-error-bg, #ffe8e8);
  color: var(--color-error-text);
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1;
  text-decoration: none;
  vertical-align: middle;
  cursor: help;
}
/* Цвет текста задаём явно (2026-08-10, заметил основатель: «пустая
   розовая плашка, не могу понять, что это»). Плашка — ссылка, а общее
   правило `.client-table a { color: var(--color-text) }` специфичнее, чем
   `.badge--danger`, и перебивало её цвет: в тёмной теме текст светлеет и
   пропадает на фиксированном светло-розовом фоне. Ровно та же ловушка,
   что уже описана выше у строк платёжного календаря. */
.client-table a.service-online-warning { color: var(--color-error-text); }

/* Предупреждение под ссылкой на онлайн-запись (2026-08-06): виджет
   исправен, но клиенту нечего выбрать. Не красным «всё сломалось», а
   заметной подсказкой: это состояние настройки, а не авария. */
.settings__warning {
  margin: 0.5rem 0 0;
  padding: 0.6rem 0.75rem;
  border: 1px solid #f59e0b;
  border-radius: 8px;
  background: rgba(245, 158, 11, 0.08);
  font-size: 0.9rem;
}

/* Панель платформы (2026-08-06) — раздел владельца сервиса, не салона.
   Оформление нарочно спокойное: сюда заходят раз в месяц свериться по
   оплатам, а не работать целый день. */
.platform-totals {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: 1.5rem;
}
.platform-totals__item {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 0.6rem 1rem;
  min-width: 8rem;
}
.platform-totals__item--warn { border-color: #f59e0b; }

/* Плашка «смотрю кабинет глазами роли» (2026-08-14). Видна на каждом
   экране и намеренно бросается в глаза: владелец, забывший про режим,
   решит, что половина кабинета пропала сама собой. */
.preview-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin: 0 0 1rem;
  padding: 0.7rem 1rem;
  border: 1px solid var(--color-accent);
  border-left-width: 4px;
  border-radius: 10px;
  background: var(--color-accent-soft);
}
.preview-switch__form { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; }
.preview-switch__form select {
  min-height: 44px;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface);
  color: var(--color-text);
}

/* Подписка салона (2026-08-14): что салон платит за саму систему. Цена
   крупно — за ней на страницу и приходят. */
.subscription__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.subscription__price { margin: 0; font-size: 2rem; font-weight: 600; line-height: 1.1; }
.subscription__price span { font-size: 1rem; font-weight: 400; color: var(--color-muted); }
.subscription__status {
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
  border: 1px solid var(--color-border);
  white-space: nowrap;
}
.subscription__status--trial,
.subscription__status--active {
  border-color: var(--color-link);
  background: var(--color-accent-soft);
  color: var(--color-link);
}
.subscription__status--overdue,
.subscription__status--blocked {
  border-color: var(--color-error-text);
  color: var(--color-error-text);
}
.subscription__until { margin: 1rem 0 0.25rem; font-size: 1.1rem; font-weight: 600; }
.subscription__until--warn { color: var(--color-error-text); }

/* Поломка платформы (2026-08-14): остановившиеся фоновые задачи. Красная
   рамка на месте, где обычно ничего нет, — единственный способ заметить
   это раньше, чем через четыре дня, как вышло 10–13 августа. */
.platform-alert {
  margin: 0 0 1.25rem;
  padding: 1rem 1.15rem;
  border: 1px solid var(--color-error);
  border-left-width: 4px;
  border-radius: 10px;
  background: var(--color-surface);
}
.platform-alert strong { color: var(--color-error-text); }
.platform-alert p { margin: 0.4rem 0 0; }
.platform-alert code {
  padding: 0.1rem 0.35rem;
  border-radius: 4px;
  background: var(--color-surface-alt);
  font-size: 0.9em;
}
.platform-totals__value { display: block; font-size: 1.3rem; font-weight: 600; }
.platform-totals__label { display: block; font-size: 0.8rem; color: var(--color-muted); }
.platform-callout {
  margin-bottom: 1rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  font-size: 0.9rem;
}
.platform-callout--warn { border-color: #f59e0b; background: rgba(245, 158, 11, 0.08); }
.platform-status { font-size: 0.85rem; }
.platform-status--overdue { color: #dc2626; font-weight: 600; }
.platform-status--blocked { color: var(--color-muted); }
.platform-status--trial { color: var(--color-warranty-text); }
.platform-overdue { color: #dc2626; font-size: 0.8rem; }
.platform-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 1rem;
  margin-bottom: 2rem;
}
.platform-form__field { display: flex; flex-direction: column; gap: 0.3rem; }
.platform-form__field--wide { flex: 1; min-width: 14rem; }
.platform-form__field label { font-size: 0.8rem; color: var(--color-muted); }
.platform-form__field input { padding: 0.4rem 0.5rem; }
.platform-form__hint { font-size: 0.75rem; color: var(--color-muted); }
.platform-form button { margin-top: 0; }
.platform-body { margin: 0; background: var(--color-bg); color: var(--color-text); }
.platform-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1.5rem;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface);
}
.platform-header__brand { font-weight: 600; text-decoration: none; color: inherit; }
.platform-header__user { font-size: 0.85rem; color: var(--color-muted); }
.platform-main { padding: 1.5rem; max-width: 1400px; }
.platform-header__user { display: inline-flex; align-items: center; gap: 0.75rem; }
.platform-header__logout { margin: 0; }
.platform-header__logout button { margin-top: 0; }
/* Колонка выбора в расчёте зарплаты (2026-08-06) — узкая, галочка по
   центру: это не данные, а способ отметить строку. */
.payroll-calc__pick { width: 2.5rem; text-align: center; }

/* Теги работ (2026-08-06, по просьбе основателя). Метка-ссылка: клик по
   ней отбирает работы с этим тегом — тот же элемент и показывает, и
   фильтрует, отдельная кнопка «искать по тегу» не нужна. */
.photo-tag {
  display: inline-block;
  margin: 0 0.2rem 0.2rem 0;
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  font-size: 0.75rem;
  color: var(--color-muted);
  text-decoration: none;
  white-space: nowrap;
}
.photo-tag:hover { color: var(--color-text); border-color: var(--color-link); }
.photo-tag--active {
  background: var(--color-accent);
  border-color: var(--color-link);
  color: var(--color-on-accent);
}
.photo-tags__filter { margin-bottom: 1rem; }
.photo-tags__editor { display: inline-block; }
.photo-tags__editor summary { list-style: none; cursor: pointer; font-size: 0.75rem; }
.photo-tags__editor summary::-webkit-details-marker { display: none; }
.photo-tags__editor form {
  margin-top: 0.4rem;
  padding: 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  min-width: 12rem;
}
.photo-tags__option { display: block; font-size: 0.85rem; margin-bottom: 0.2rem; }
.photo-tags__new { width: 100%; margin: 0.3rem 0; padding: 0.3rem 0.4rem; font-size: 0.85rem; }
.photo-tags__editor button { margin-top: 0.3rem; }

/* Галочка выполнения задачи (2026-08-06) — колонка узкая, отметка по
   центру: это действие, а не данные. Форма без отступов, иначе строка
   таблицы разъезжается по высоте. */
.reminder-check { width: 2.5rem; text-align: center; }
.reminder-check form { margin: 0; }
.reminder-check input[type="checkbox"] { cursor: pointer; }

/* Список активных регулярных расходов над лентой (2026-08-06) — компактные
   строки, а не полноценная таблица: полей мало (статья/сумма/периодичность/
   когда), а «Остановить» должна быть под рукой на каждой строке. */
.recurring-expenses {
  border: 1px solid var(--color-border); border-radius: 8px;
  padding: 0.75rem 1rem; margin-bottom: 1.5rem;
}
.recurring-expenses h3 { margin: 0 0 0.5rem; font-size: 0.9rem; color: var(--color-muted); font-weight: 400; }
.recurring-expenses__row {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.35rem 0; border-top: 1px solid var(--color-border);
  font-size: 0.9rem;
}
.recurring-expenses__row:first-of-type { border-top: none; }
.recurring-expenses__category { flex: 1; min-width: 0; }
.recurring-expenses__amount { white-space: nowrap; font-weight: 600; }
.recurring-expenses__interval { color: var(--color-muted); white-space: nowrap; }
.recurring-expenses__next { color: var(--color-muted); white-space: nowrap; }
.recurring-expenses__stop { margin: 0; }
.recurring-expenses__stop button { margin-top: 0; }

/* Экран «Статьи расходов»: бюджет + удаление в одной ячейке действий
   (2026-08-06) — обе формы инлайновые, без своих отступов сверху. */
.category-rename-form { margin: 0; }
.category-rename-form__budget { width: 8rem; }
.category-rename-form__delete { display: inline-block; margin: 0 0 0 0.75rem; }
.category-rename-form__delete button { margin-top: 0; }

/* Бюджет по статьям (2026-08-06) — перерасход красным и с плюсом, чтобы
   был виден с первого взгляда; уложились — приглушённым цветом текста,
   как обычные цифры, без лишнего «зелёного успеха» на каждой строке. */
.budget-status__over { color: var(--color-error-text); font-weight: 600; }
.budget-status__under { color: var(--color-muted); }

/* Всплывающие уведомления о записях (2026-08-06) — справа внизу. z-index
   ниже модального окна (10): окно не должно перекрывать открытую
   карточку записи, ради которой администратор и открыл экран. */
.toast-container {
  position: fixed; right: 1rem; bottom: 1rem; z-index: 9;
  display: flex; flex-direction: column; gap: 0.5rem;
  max-width: min(22rem, calc(100vw - 2rem));
  pointer-events: none;
}
.toast {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--color-accent);
  border-radius: 6px;
  padding: 0.6rem 0.85rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
  animation: toast-in 0.2s ease-out;
}
.toast--cancelled { border-left-color: var(--color-error-text); }
.toast__title { margin: 0; font-weight: 600; font-size: 0.9rem; }
.toast__text { margin: 0.15rem 0 0; font-size: 0.85rem; color: var(--color-muted); }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(0.5rem); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
  /* Кто выключил анимации в системе, тот выключил их и здесь: подсветка
     остаётся, движение — нет. */
  .widget-page * { transition: none !important; }
  .widget-slots__item:active,
  .widget-chip:active,
  .widget-list__item:active,
  .widget-shortcut:active { transform: none; }
}

/* Акции на главной виджета (2026-08-08, по просьбе основателя). Карточка
   с необязательной картинкой слева: у салона не всегда есть под рукой
   макет, и объявление без картинки должно выглядеть так же опрятно. */
.widget-promos { margin: 1rem 0 1.5rem; }
.widget-promo {
  display: flex; gap: 0.75rem; align-items: flex-start;
  padding: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  margin-bottom: 0.6rem;
}
.widget-promo__image {
  width: 72px; height: 72px; object-fit: cover; border-radius: 6px; flex-shrink: 0;
}
.widget-promo__body { min-width: 0; }
.widget-promo__title { margin: 0 0 0.2rem; font-size: 1rem; }
.widget-promo__text { margin: 0; font-size: 0.9rem; }
.widget-promo__until {
  margin: 0.3rem 0 0; font-size: 0.85rem; color: var(--color-muted);
}

/* Согласие на обработку данных под формой записи (2026-08-08) — мелким,
   но читаемым: это не примечание, а то, чем человек соглашается. */
.widget-consent {
  margin: 0.75rem 0 0.5rem;
  font-size: 0.82rem;
  color: var(--color-muted);
}

/* Заголовок категории на экране «Услуги → Онлайн-запись» (2026-08-08) —
   мельче обычного h3 и без отбивки сверху у первого, чтобы список из
   пяти категорий не превращался в лестницу из заголовков. */
.widget-order__category {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 1.2rem 0 0.4rem;
  font-size: 0.95rem;
  color: var(--color-muted);
}
/* Категорию тянут за ручку у названия (2026-08-09). Ручка узкая и с
   курсором «схватить» — иначе непонятно, что заголовок вообще двигается,
   а тянуть за сам текст мешало бы выделять его мышью. */
.widget-order__category .draggable-row__handle {
  width: auto;
  cursor: grab;
  user-select: none;
}
.widget-order__group.draggable-row--dragging { opacity: 0.5; }

/* Блок «Может быть интересно» на экране успеха (2026-08-08) — отделён
   линией сверху: это уже не про сделанную запись, а следующий шаг, и
   сливаться с деталями визита он не должен. */
.widget-recommend {
  margin: 1.25rem 0;
  padding-top: 1rem;
  border-top: 1px solid var(--color-border);
}
/* Запись в один клик (2026-08-10). Сначала описание услуги, потом
   настоящая кнопка: первым заходом кнопкой был весь блок, и он читался
   как строка списка — основатель так её и не заметил. */
.widget-recommend__form { margin: 0; }
.widget-recommend__offer {
  display: block;
  padding: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  margin-bottom: 0.75rem;
}
.widget-recommend__button {
  width: 100%;
  margin-top: 0;
  font-size: 1rem;
  padding: 0.85rem 1rem;
}
/* Сводка по нескольким записям (2026-08-10) — список того, на что человек
   записался за один заход, вместо одной карточки последней записи. */
.widget-success__list {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.widget-success__item {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 0.25rem 0.75rem;
  padding: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
}
.widget-success__item-time { font-weight: 600; }
.widget-success__item-service { font-weight: 600; }
.widget-success__item-price { text-align: right; white-space: nowrap; }
/* Мастер и ссылка — второй строкой под основными данными. */
.widget-success__item-staff { grid-column: 2; color: var(--color-muted); font-size: 0.9rem; }
.widget-success__item-ics { grid-column: 3; text-align: right; font-size: 0.85rem; }

.widget-recommend__title { margin: 0 0 0.5rem; font-size: 1rem; }

/* --- Массовая правка свойств услуг (2026-08-11) --------------------------

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

.bulk-bar {
  background: var(--color-surface-alt);
  border-radius: 8px;
  padding: 0.9rem 1rem;
  margin-bottom: 1rem;
}
.bulk-bar__title { margin: 0 0 0.75rem; }
.bulk-bar__fields {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
  margin-bottom: 0.5rem;
}
.bulk-bar__fields label {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  font-size: 0.85rem;
  color: var(--color-muted);
}
.bulk-bar__fields input,
.bulk-bar__fields select { min-width: 11rem; }

/* --- Услуги мастера по категориям (2026-08-11) ---------------------------

   Категория — <details>: свёрнутая группа занимает одну строку, и
   вкладка с полутора сотнями услуг снова помещается на экран. */

.staff-services-group {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  margin-bottom: 0.6rem;
  padding: 0.5rem 0.75rem;
}
.staff-services-group > summary {
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
}
.staff-services-group__name { font-weight: 600; }
.staff-services-group__count { font-size: 0.8rem; color: var(--color-muted); }
.staff-services-group__all {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0.6rem 0 0.2rem;
  font-size: 0.85rem;
}

/* Ячейка, которую нельзя рвать переносом (2026-08-12, по замечанию
   основателя: «сделай цену в одну строчку всегда»). В узкой колонке
   «3300 ₽» разваливалось на число и знак рубля отдельной строкой — цена
   читается как единое целое, а таблица от одной неразрывной ячейки
   расползётся не сильнее, чем от лишней строки в каждой строке. */
.cell-nowrap { white-space: nowrap; }

/* Таблица со своей горизонтальной прокруткой (2026-08-12). Без неё
   широкий каталог услуг уносил за край экрана всю страницу — вместе с
   шапкой и левым меню, — вместо того чтобы ехать самому. */
.table-scroll { overflow-x: auto; }

/* --- Страница отзыва о визите (2026-08-12) -------------------------------

   Звёзды — обычные radio, спрятанные под крупные подписи: без JS, чтобы
   страница работала сразу на телефоне со слабым интернетом. Сама radio
   не скрыта через display:none — так её не поймал бы Tab и не прочитал
   бы экранный диктор; уводим за границы экрана. */

.review-form { max-width: 32rem; }
.review-stars { border: 0; padding: 0; margin: 0 0 1rem; }
.review-stars legend { font-size: 0.85rem; color: var(--color-muted); padding: 0; }
.review-stars__row { display: flex; gap: 0.4rem; margin-top: 0.4rem; }
.review-stars__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
  cursor: pointer;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  line-height: 1;
}
.review-stars__item input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}
.review-stars__mark { font-size: 1.5rem; color: var(--color-muted); }
.review-stars__value { font-size: 0.75rem; color: var(--color-muted); }
/* Выбранная и та, что под курсором/фокусом: цвет темы, не жёлтый —
   страница оформлена под салон, а не под универсальный «рейтинг». */
.review-stars__item:has(input:checked) { border-color: var(--color-primary); }
.review-stars__item:has(input:checked) .review-stars__mark { color: var(--color-primary); }
.review-stars__item:has(input:focus-visible) { outline: 2px solid var(--color-primary); }
.review-stars__item:hover .review-stars__mark { color: var(--color-primary); }

.review-tips {
  margin-top: 2rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--color-border);
}
.review-thanks { margin-bottom: 1.5rem; }

/* Предложение повторить отзыв на картах (2026-09-18). Отдельной карточкой
   на песочном: это не продолжение благодарности, а просьба сделать ещё
   одно действие, и она должна читаться как отдельный шаг.

   Сам отзыв показан цитатой — человек видит, что копирует, и не думает,
   что его просят написать всё заново. */
.review-maps {
  margin-top: 1.5rem;
  padding: 1.5rem;
  border-radius: var(--ok-r-xl);
  background: var(--color-surface-alt);
}
.review-maps__title { margin: 0 0 0.5rem; font-size: 1.15rem; }
.review-maps__lead { margin: 0 0 1rem; color: var(--color-muted); }
.review-maps__text {
  margin: 0 0 0.75rem;
  padding: 0.75rem 1rem;
  border-left: 2px solid var(--color-border);
  background: var(--color-surface);
  border-radius: 8px;
  color: var(--color-text);
}
.review-maps__go { margin-top: 0.75rem; display: inline-flex; width: auto; }

/* Готовые сегменты (2026-08-12) — карточка с описанием слева и кнопкой
   справа: без описания непонятно, кого именно соберёт заготовка. */
.segment-presets { list-style: none; padding: 0; margin: 0.5rem 0 0; }
.segment-presets__item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 0.75rem 0;
  border-top: 1px solid var(--color-border);
}
.segment-presets__item p { margin: 0.2rem 0 0; max-width: 40rem; }

/* Сегмент повтора по конкретной услуге (2026-08-31): выбор услуги и две
   кнопки — «пора» и «забыли». На узком экране кнопки уходят под селект,
   чтобы не сжимать название услуги до нечитаемого. */
.segment-repeat {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
  margin-top: 0.5rem;
}
.segment-repeat__field { display: flex; flex-direction: column; gap: 0.25rem; min-width: 18rem; }
.segment-repeat__field > span { font-size: 0.85rem; color: var(--color-muted); }
.segment-repeat__field select { width: 100%; }
.segment-repeat__buttons { display: flex; flex-wrap: wrap; gap: 0.5rem; }

/* Метка «как связаться» в списке обзвона (2026-08-31). Приглушённая:
   это подсказка администратору, а не статус, ради которого стоит
   раскрашивать таблицу. Выделен только случай «звонить придётся» —
   он и означает работу. */
.outreach { font-size: 0.85rem; color: var(--color-muted); white-space: nowrap; }
.outreach--sent { color: var(--color-muted); }
.outreach--call { color: var(--color-text); font-weight: 500; }

/* Отчёт «После кого не возвращаются» (2026-08-31). Оговорки — свёрнутым
   блоком под заголовком: развернуть их должно быть легко, но занимать
   пол-экрана каждый раз они не должны. Два списка выбора рядом, на
   узком экране — друг под другом. */
.churn__note {
  margin: 0.75rem 0 1.25rem;
  padding: 0.75rem 1rem;
  background: var(--color-surface-alt);
  border-radius: 10px;
}
.churn__note summary { cursor: pointer; font-weight: 500; }
.churn__note ul { margin: 0.75rem 0 0; padding-left: 1.1rem; }
.churn__note li { margin-bottom: 0.5rem; max-width: 46rem; }
.churn__filters { margin-bottom: 1.5rem; }
.churn__multiselects { display: flex; flex-wrap: wrap; gap: 1.5rem; margin-top: 1rem; }
.churn__multiselects > div { min-width: 16rem; }
.churn__multiselects select { min-width: 16rem; max-width: 22rem; }
.churn__actions { display: flex; align-items: center; gap: 0.75rem; margin-top: 1rem; }

/* Плашка о незакрытых записях над журналом (2026-08-31). Заметная, но не
   тревожная: это не поломка, а забытое дело, и красный цвет здесь
   приучал бы не замечать настоящие ошибки. */
.journal__unclosed {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
  padding: 0.7rem 1rem;
  background: var(--color-surface-alt);
  border-left: 3px solid var(--color-accent);
  border-radius: 8px;
}
.unclosed__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }

/* --- Значки с числами над сеткой журнала (2026-09-10, тема 138) --------
   Ряд таблеток «иконка + число + слово». Нулевых значков не бывает
   вовсе (см. journal_badges), поэтому пустого состояния у полосы нет.

   Тревожные — заливкой, спокойные — контуром: если выделить все, не
   выделен никто. */
.journal-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}
.journal-badges__item {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.25rem 0.6rem 0.25rem 0.45rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface-alt);
  color: var(--color-text);
  font-size: 0.8rem;
  line-height: 1.4;
  text-decoration: none;
  white-space: nowrap;
}
.journal-badges__item:hover { border-color: var(--color-accent); }
.journal-badges__icon {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.journal-badges__count { font-weight: 700; }
.journal-badges__label { color: var(--color-muted); }
.journal-badges__item--danger {
  border-color: transparent;
  background: var(--color-error);
  color: #fff;
}
.journal-badges__item--danger:hover { border-color: transparent; opacity: 0.9; }
.journal-badges__item--danger .journal-badges__label { color: #fff; }
.journal-badges__item--warn {
  border-color: rgba(245, 158, 11, 0.5);
  background: rgba(245, 158, 11, 0.14);
  color: var(--color-warn-text);
}
.journal-badges__item--warn .journal-badges__label { color: var(--color-warn-text); }
/* На узком экране подпись уходит: иконка и число несут то же самое, а
   тулбар с датой и стрелками важнее. */
@media (max-width: 700px) {
  .journal-badges__label { display: none; }
}

/* --- Сводка владельца (2026-08-12) ---------------------------------------

   Карточки цифр в ряд с переносом; блок «требует внимания» — списком с
   кнопкой действия справа, чтобы от проблемы до экрана, где она чинится,
   было одно нажатие. */

.dashboard__section { margin-bottom: 2rem; }
.dashboard__cards { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.dashboard__card {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 12rem;
  padding: 0.9rem 1.1rem;
  background: var(--color-surface-alt);
  border-radius: 10px;
}
.dashboard__card-label { font-size: 0.8rem; color: var(--color-muted); }
.dashboard__card-value { font-size: 1.6rem; font-weight: 600; }
.dashboard__card-note { font-size: 0.8rem; color: var(--color-muted); }

.dashboard__attention { list-style: none; padding: 0; margin: 0; }
.dashboard__attention-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 0.85rem 1rem;
  border-radius: 10px;
  background: var(--color-surface-alt);
  margin-bottom: 0.5rem;
}
.dashboard__attention-item p { margin: 0.2rem 0 0; }
/* Срочное — полосой слева, а не красным фоном: строка должна читаться,
   а не кричать; красным фоном половина списка выглядела бы как авария. */
.dashboard__attention-item--urgent { border-left: 3px solid var(--color-error); }
.dashboard__all-good { color: var(--color-muted); }

/* Отчёт «Расходы по статьям» (2026-08-12). Рост расходов — красным:
   снижение не всегда хорошо (перестали закупать материалы — это не
   экономия), а рост заметить нужно всегда. */
.expense-report__up { color: var(--color-error-text); }
/* Полоска доли рядом с процентом — чтобы крупные статьи видеть, не
   вчитываясь в числа. */
.expense-report__bar {
  display: inline-block;
  height: 6px;
  max-width: 6rem;
  margin-left: 0.4rem;
  border-radius: 3px;
  background: var(--color-primary);
  vertical-align: middle;
}

/* --- Виджет: категории и поиск (2026-08-12) ------------------------------

   По итогам мобильной проверки: все услуги салона шли одним списком — в
   демо 148 карточек и 113 экранов прокрутки на телефоне. Свёрнутые
   категории дают 11 строк вместо 148, поиск — путь для тех, кто знает
   название. */

/* Что и когда — карточка над формой подтверждения (2026-08-14). Время
   крупно: это последнее, что клиент проверяет перед нажатием кнопки. */
.widget-summary {
  padding: 1rem 1.15rem;
  margin: 1rem 0 1.5rem;
  border-radius: 12px;
  background: var(--color-surface-alt);
}
.widget-summary__when {
  margin: 0;
  font-size: 1.3rem;
  font-weight: 600;
  color: var(--color-link);
  font-variant-numeric: tabular-nums;
}
.widget-summary__what { margin: 0.35rem 0 0; font-weight: 600; }
.widget-summary__who { margin: 0.2rem 0 0; color: var(--color-muted); font-size: 0.9rem; }

/* Части дня на экране выбора времени (2026-08-14). Заголовок тихий: он
   делит сетку на куски, но сам по себе не важнее кнопок под ним. */
.widget-slot-group { margin-top: 1.25rem; }
.widget-slot-group__title {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0;
  font-size: 1rem;
}
.widget-slot-group__hint { font-weight: 400; font-size: 0.85rem; color: var(--color-muted); }

/* Второй путь записи — «мне удобно в субботу с утра» (2026-08-14).
   Отдельная карточка со стрелкой: раньше это была строка в списке услуг
   и читалась как ещё одна услуга. */
.widget-shortcut {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1rem;
  margin: 1.25rem 0;
  border: 1px solid var(--color-border);
  border-radius: 12px;
  background: var(--color-surface);
  text-decoration: none;
  color: var(--color-text);
  transition: border-color 0.15s, background-color 0.15s;
}
.widget-shortcut__body { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.widget-shortcut__title { font-weight: 600; }
.widget-shortcut__hint { color: var(--color-muted); font-size: 0.85rem; }
/* Стрелка справа — та же, что у категорий, повёрнутая вбок. */
.widget-shortcut::after {
  content: "";
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  margin-left: auto;
  border-right: 2px solid var(--color-muted);
  border-bottom: 2px solid var(--color-muted);
  transform: rotate(-45deg);
}

/* Заголовки разделов страницы записи: чуть крупнее обычного h2 и с
   воздухом сверху — они делят длинную страницу на понятные куски. */
.widget-content .widget-section-title {
  margin: 1.75rem 0 0.85rem;
  font-size: 1.15rem;
  letter-spacing: -0.01em;
}
/* Ссылка на витрину салона под обложкой — спокойная строка, а не кнопка:
   главное действие страницы это запись, а не просмотр работ. */
.widget-cover__link { margin: -0.75rem 0 1.25rem; font-size: 0.9rem; }

.widget-search {
  width: 100%;
  padding: 0.8rem 1rem;
  margin-bottom: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  /* 16px обязательны: при меньшем размере Safari на iPhone зумит всю
     страницу, как только палец попадает в поле ввода. */
  font-size: 16px;
  background: var(--color-surface);
  color: var(--color-text);
}
.widget-search__empty { color: var(--color-muted); }

.widget-category {
  border: 1px solid var(--color-border);
  border-radius: 12px;
  margin-bottom: 0.5rem;
  overflow: hidden;
  background: var(--color-surface);
}
.widget-category__summary {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  /* Крупная цель: строка целиком нажимается пальцем, а не одна стрелка. */
  padding: 1rem;
  cursor: pointer;
  font-weight: 600;
  /* Своя стрелка вместо системного треугольника — он в разных браузерах
     выглядит по-разному и всегда чужеродно. */
  list-style: none;
}
.widget-category__summary::-webkit-details-marker { display: none; }
.widget-category__name { flex: 1; min-width: 0; }
.widget-category__count {
  font-weight: 400;
  font-size: 0.85rem;
  color: var(--color-muted);
}
/* Стрелка-указатель: повёрнута вниз, когда категория раскрыта. Без неё
   свёрнутый список не выглядит нажимаемым — люди искали, куда нажать. */
.widget-category__summary::after {
  content: "";
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  margin-left: 0.15rem;
  border-right: 2px solid var(--color-muted);
  border-bottom: 2px solid var(--color-muted);
  transform: rotate(-45deg) translate(-2px, -2px);
  transition: transform 0.2s;
}
.widget-category[open] > .widget-category__summary::after {
  transform: rotate(45deg) translate(-1px, -1px);
}
.widget-category .widget-list { padding: 0 0.6rem 0.6rem; margin: 0; }
/* Внутри раскрытой категории карточки услуг уже обведены рамкой самой
   категории — вторая рамка вокруг каждой строки делает список рябым. */
.widget-category .widget-list__item {
  border-color: transparent;
  background: var(--color-surface-alt);
}

/* --- Виджет на телефоне (2026-08-12, по итогам мобильной проверки) -------

   Пальцу нужна цель крупнее курсора. Кнопка подтверждения записи была
   38px высотой, галочки согласия — 13px: промахнуться мимо них с
   телефона проще, чем попасть, а это последний шаг перед записью. */

.widget-page .btn { min-height: 44px; }

/* Отклик на нажатие (2026-08-14). На телефоне нет наведения курсором:
   палец жмёт кнопку времени, страница думает полсекунды, и человек не
   понимает, засчиталось ли — жмёт второй раз. Лёгкое «вдавливание»
   отвечает мгновенно, до всякого запроса к серверу.

   Для тех, кто выключил анимации в системе, движение снимается в блоке
   prefers-reduced-motion выше — остаётся только подсветка. */
.widget-slots__item:active,
.widget-chip:active,
.widget-list__item:active,
.widget-shortcut:active {
  transform: scale(0.98);
  background: var(--color-accent-soft);
}
/* Полоса дней прокручивается пальцем, и «вдавливание» при перетаскивании
   выглядит как случайное нажатие. */
.widget-chips--days .widget-chip:active { transform: none; }
.widget-page input[type="checkbox"] {
  width: 20px;
  height: 20px;
  /* Своя цель у галочки, даже когда подпись рядом короткая. */
  flex-shrink: 0;
}
.widget-page label { line-height: 1.4; }

/* --- «С чего начать» (2026-08-12) ----------------------------------------

   Шаги списком с крупной отметкой слева и кнопкой действия справа.
   Выполненные не прячутся, а приглушаются: видеть, что уже сделано,
   так же важно, как и что осталось. */

.onboarding { list-style: none; padding: 0; margin: 1rem 0 0; counter-reset: step; }
.onboarding__step {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 1rem;
  border-radius: 10px;
  background: var(--color-surface-alt);
  margin-bottom: 0.5rem;
}
.onboarding__body { display: flex; align-items: flex-start; gap: 0.9rem; }
.onboarding__body p { margin: 0.2rem 0 0; }
.onboarding__mark {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  font-weight: 600;
}
.onboarding__step--done { opacity: 0.65; }
.onboarding__step--done .onboarding__mark {
  background: var(--color-accent-soft);
  border-color: var(--color-link);
  color: var(--color-link);
}
/* Игры и награды (2026-09-23). Поля механики — в строку на широком
   экране и столбцом на телефоне; выключатель — отдельной строкой сверху,
   чтобы его нельзя было не заметить. */
.settings__check {
  display: flex; align-items: center; gap: 0.5rem;
  margin: 0.5rem 0; font-weight: 600;
}
.game-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 0.6rem 1rem;
  margin: 0.4rem 0;
}
/* С префиксом формы: общее правило подписей в настройках делает их
   блоками и побеждало по весу — поле «что даёт статус» сжималось до
   ширины слова. */
.settings__form .game-fields label, .settings__form .game-fields__wide {
  display: flex; flex-direction: column; gap: 0.25rem;
  font-size: 0.85rem;
}
.settings__form .game-fields__wide { margin-top: 0.4rem; }
.settings__form .game-fields__wide input,
.settings__form .game-fields__wide textarea { width: 100%; }
.game-achievements input[type="text"] { width: 100%; }
/* Дни недели — в строку, как в календаре. */
.offpeak-days { border: 0; padding: 0; margin: 0.6rem 0; }
.offpeak-days > div { display: flex; flex-wrap: wrap; gap: 0.4rem 0.9rem; }
.offpeak-days label { display: inline-flex; align-items: center; gap: 0.3rem; }
.game-achievements input[type="number"] { width: 6rem; text-align: right; }
.game-achievements select { max-width: 12rem; }
.game-achievements__new td { background: var(--color-surface-alt); }

/* Блок игр в кабинете клиента и в карточке. Полученное — ярко,
   предстоящее — приглушённо с подсказкой «как получить»: иначе список
   будущих наград читается как упрёк, а не как приглашение. */
.game-badges { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0.4rem 0; }
.game-badge {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--color-accent);
  font-size: 0.85rem;
}
.game-badge--ahead { border-style: dashed; border-color: var(--color-border); color: var(--color-muted); }
.game-badge__bonus { font-size: 0.75rem; color: var(--color-muted); }
.game-task {
  padding: 0.6rem 0;
  border-top: 1px solid var(--color-border);
}
.game-task__head { display: flex; justify-content: space-between; gap: 0.5rem; }
.game-task .reach-bar { margin: 0.35rem 0; }
.game-task--done { opacity: 0.7; }
/* Колесо фортуны (2026-09-23). Диск — conic-gradient с долями по
   шансам; вращение — CSS-переход с замедлением к концу, как у настоящего
   колеса. Для тех, кто просил систему не двигать лишнего, поворот
   мгновенный. */
.wheel { position: relative; width: 240px; height: 240px; margin: 0.8rem auto; }
.wheel__disc {
  width: 100%; height: 100%;
  border-radius: 50%;
  border: 4px solid var(--color-surface-alt);
  transition: transform 4s cubic-bezier(0.17, 0.67, 0.12, 1);
}
.wheel__pointer {
  position: absolute; top: -0.9rem; left: 50%;
  transform: translateX(-50%);
  font-size: 1.4rem; line-height: 1; z-index: 1;
  color: var(--color-text);
}
.wheel__legend {
  list-style: none; padding: 0; margin: 0.5rem 0;
  display: flex; flex-wrap: wrap; gap: 0.3rem 1rem;
  font-size: 0.85rem;
}
.wheel__result { font-weight: 600; margin-top: 0.6rem; }
.wheel__rules { font-size: 0.8rem; color: var(--color-muted); }
@media (prefers-reduced-motion: reduce) {
  .wheel__disc { transition: none; }
}
.game-status {
  padding: 0.7rem 0.9rem;
  border-radius: 10px;
  border: 1px solid var(--color-accent);
  margin: 0.5rem 0;
}

/* Аналитика салона — один экран (2026-09-21, уточнение основателя:
   «очень важно, чтобы это был один экран, а не длинная страница»).

   Сетка плиток на всю высоту окна. Прокручивается не страница, а
   содержимое плитки: соседние блоки остаются на месте, и картина целиком
   видна всегда. Плитки разной ширины и высоты — «Мастера» и графики
   требуют места, а счётчики обходятся четвертью. */
.board-top {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  margin-bottom: 0.75rem;
}
.board-top__title { margin: 0; font-size: 1.4rem; }

.board-nav { display: flex; align-items: center; gap: 0.4rem; flex-wrap: nowrap; }
.board-nav__arrow {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.9rem; height: 1.9rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  text-decoration: none;
  color: var(--color-text);
  line-height: 1;
}
.board-nav__arrow:hover { border-color: var(--color-link); }
/* Вперёд из текущего периода идти некуда — место остаётся, чтобы
   подпись не прыгала вбок при каждой перемотке. */
.board-nav__arrow--off { opacity: 0.25; }
.board-nav__label {
  flex: 1 1 auto; min-width: 0;
  font-size: 0.9rem; color: var(--color-muted);
}

.board-presets { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.board-presets__item {
  padding: 0.25rem 0.7rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  font-size: 0.8rem;
  text-decoration: none;
  color: var(--color-text);
}
.board-presets__item--on {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-accent-contrast, #1b1b1b);
}

.board-own { display: flex; align-items: center; gap: 0.3rem; flex-wrap: wrap; }
.board-own__field { font-size: 0.8rem; padding: 0.2rem 0.4rem; }
.board-own__go {
  font-size: 0.8rem; padding: 0.25rem 0.7rem;
  border: 1px solid var(--color-border); border-radius: 8px;
  background: var(--color-surface-alt); color: var(--color-text);
  cursor: pointer;
}

/* Сама сетка. Высота — остаток окна после шапки и строки фильтров;
   плитки делят её между собой, поэтому страница не прокручивается. */
.board-grid {
  display: grid;
  /* Пять колонок и ровно три ряда: десять плиток раскладываются без
     остатка (2+3, 1+1+3, 1+1+1+1+1). При четырёх колонках выходило
     четыре ряда, и на плитку оставалось по сто пикселей — числа
     обрезались, а прокрутка возвращалась, только уже внутри плиток. */
  grid-template-columns: repeat(5, minmax(0, 1fr));
  /* Ряды делят высоту поровну (1fr, а не minmax): иначе плитки берут
     столько, сколько просит содержимое, и сетка вылезает за окно — то
     самое проматывание, от которого уходили. */
  grid-auto-rows: 1fr;
  gap: 0.6rem;
  height: calc(100vh - 9.5rem);
  min-height: 34rem;
}
.board-tile {
  display: flex; flex-direction: column;
  min-height: 0; min-width: 0;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  background: var(--color-surface-alt);
  padding: 0.6rem 0.75rem;
}
.board-tile--wide { grid-column: span 2; }
.board-tile--widest { grid-column: span 3; }
.board-tile__title {
  margin: 0 0 0.4rem;
  font-size: 0.8rem; font-weight: 600;
  color: var(--color-muted);
  text-transform: uppercase; letter-spacing: 0.03em;
  display: flex; justify-content: space-between; gap: 0.5rem;
}
.board-tile__sum { color: var(--color-text); text-transform: none; letter-spacing: 0; }
.board-tile__body { flex: 1 1 auto; min-height: 0; }
/* Длинный список листается внутри плитки, а не растягивает страницу. */
.board-tile__body--scroll { overflow-y: auto; }

/* Крупные числа внутри плитки. */
.board-nums {
  display: grid;
  /* 6.5rem — ширина, при которой «139600 ₽» ещё помещается в строку, а
     пять счётчиков встают в один ряд на плитке в половину экрана. */
  grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr));
  gap: 0.4rem 0.6rem;
}
/* В узкой плитке числа встают списком «метка — значение»: сеткой они
   расползались на три ряда и не помещались. Строкой читается быстрее —
   глаз идёт сверху вниз по одной колонке. */
.board-nums--list { display: flex; flex-direction: column; gap: 0.1rem; }
.board-nums--list .board-num {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline; gap: 0.4rem;
}
.board-nums--list .board-num__label { font-size: 0.78rem; color: var(--color-text); }
.board-nums--list .board-num__value { font-size: 0.95rem; }
.board-nums--list .board-num__note {
  grid-column: 1 / -1;
  margin-top: -0.2rem;
  text-align: right;
}
.board-num { display: flex; flex-direction: column; gap: 0.1rem; }
.board-num__label { font-size: 0.72rem; color: var(--color-muted); }
/* Моноширинные цифры: суммы в соседних плитках не должны плясать. */
.board-num__value { font-size: 1.1rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.board-num__note { font-size: 0.68rem; color: var(--color-muted); line-height: 1.25; }
.board-num--loss .board-num__value { color: var(--color-danger, #d05353); }

.board-warning {
  margin: 0.35rem 0 0;
  font-size: 0.7rem; line-height: 1.3;
  color: var(--color-muted);
}

/* Строки-полосы: название, полоса, число. */
.board-rows { display: flex; flex-direction: column; gap: 0.15rem; }
.board-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; gap: 0.4rem;
  font-size: 0.78rem; line-height: 1.35;
}
.board-row:has(.board-row__track) {
  grid-template-columns: minmax(0, 1fr) 3.5rem auto;
}
.board-row:has(.board-row__extra) {
  grid-template-columns: minmax(0, 1fr) 3.5rem auto 2.2rem;
}
.board-row__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.board-row__track {
  height: 0.4rem; border-radius: 4px;
  background: var(--color-surface); overflow: hidden;
}
.board-row__bar {
  display: block; height: 100%;
  width: calc(var(--bar-pct) * 1%); min-width: 2px;
  background: var(--color-accent); border-radius: 4px;
}
.board-row__value { font-variant-numeric: tabular-nums; }
.board-row__extra { font-size: 0.7rem; color: var(--color-muted); text-align: right; }

/* Таблица мастеров: плотная, с липкой шапкой — при прокрутке внутри
   плитки колонки должны оставаться подписанными. */
.board-table { width: 100%; border-collapse: collapse; font-size: 0.78rem; }
.board-table th, .board-table td {
  padding: 0.25rem 0.3rem;
  border-bottom: 1px solid var(--color-border);
  white-space: nowrap;
}
.board-table thead th {
  position: sticky; top: 0;
  background: var(--color-surface-alt);
  font-weight: 600; color: var(--color-muted);
  text-align: left;
}
.board-table__num { text-align: right; font-variant-numeric: tabular-nums; }
.board-table__num--muted { color: var(--color-muted); }

.board-hint { margin: 0.4rem 0 0; font-size: 0.7rem; color: var(--color-muted); }
.board-chart { height: 100%; min-height: 5rem; }

.board-links {
  display: flex; gap: 1rem; flex-wrap: wrap;
  margin: 0.6rem 0 0; font-size: 0.8rem;
}

@media (max-width: 1400px) and (min-width: 1101px) {
  /* Между ноутбуком и большим монитором — четыре колонки: пятая делала
     плитки уже читаемого. Рядов становится четыре, высота отпускается. */
  .board-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); height: auto; }
  .board-tile { min-height: 10rem; }
}
@media (max-width: 1100px) {
  /* Пять колонок на ноутбуке превращаются в две, а высота отпускается:
     на маленьком экране честнее дать прокрутку, чем сплющить плитки до
     нечитаемых. */
  .board-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); height: auto; }
  .board-tile { min-height: 11rem; }
}
@media (max-width: 700px) {
  .board-grid { grid-template-columns: minmax(0, 1fr); }
  .board-tile--wide, .board-tile--widest { grid-column: auto; }
}

/* День без интернета (2026-09-20). Строки крупнее обычного: лист
   читают в спешке, часто с телефона в вытянутой руке, и мелкий шрифт
   здесь стоил бы дороже, чем сэкономленное место. */
.offline-day__branch { margin-top: 1.5rem; }
.offline-day__table td { padding-top: 0.7rem; padding-bottom: 0.7rem; }
.offline-day__time { white-space: nowrap; }
/* Клиент уже пришёл — строка гаснет: администратор ищет глазами тех,
   кого ещё ждут. */
.offline-day__row--done { opacity: 0.55; }
.offline-day__stale {
  padding: 0.8rem 1rem;
  border-radius: 10px;
  background: var(--color-warning-soft, var(--color-surface-alt));
  border: 1px solid var(--color-border);
}

/* Заполнить кабинет примером (2026-09-20). Рамка, а не заливка: это
   предложение рядом со списком шагов, а не ещё один шаг, и оно не должно
   спорить за внимание с самими шагами. */
.sample-box {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  margin-top: 1.25rem;
  padding: 1rem;
  border: 1px solid var(--color-border);
  border-radius: 10px;
}
.sample-box p { margin: 0.2rem 0 0; }
.sample-box form { flex-shrink: 0; margin: 0; }
@media (max-width: 640px) {
  /* На телефоне кнопка уезжала за край: колонкой она остаётся нажимаемой. */
  .sample-box { flex-direction: column; align-items: stretch; }
}

/* Пункт меню, пока салон не настроен: подсвечен, но не кричит. */
.sidebar__nav-link--accent .sidebar__label { color: var(--color-link); font-weight: 600; }

/* Фото услуги в виджете (2026-08-13). Небольшое и слева: клиент узнаёт
   услугу глазами быстрее, чем читает, но список должен оставаться
   списком, а не витриной с крупными картинками.

   2026-08-14: было float внутри flex-контейнера — то есть не работало
   вовсе (флекс отменяет обтекание), картинка просто вставала первой
   колонкой и отнимала ширину у названия. Теперь это ячейка сетки. */
.widget-list__photo {
  grid-area: photo;
  align-self: start;
  width: 52px;
  height: 52px;
  margin-right: 0.8rem;
  object-fit: cover;
  border-radius: 8px;
}

/* --- Личный кабинет клиента (2026-08-13) --------------------------------- */

.cabinet__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
}
.cabinet__section { margin-bottom: 1.75rem; }
/* Фото работ сеткой: на телефоне два столбца, шире — три. */
.cabinet__photos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 0.5rem;
}
.cabinet__photo {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 10px;
}

/* Вход в кабинет в шапке виджета (2026-08-13): заметный, но не спорящий
   с названием салона — это не главное действие страницы записи. */
.widget-header__cabinet {
  margin-left: auto;
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 500;
  white-space: nowrap;
  text-decoration: none;
}

/* --- Чат с клиентом (2026-08-13) ----------------------------------------

   Лента как в мессенджере: свои сообщения справа, клиента — слева. Не
   ради красоты: в сплошном списке без выравнивания невозможно с одного
   взгляда понять, кто последним написал и ждут ли ответа. */

.chat { display: flex; flex-direction: column; gap: 0.6rem; margin: 1rem 0; }
.chat__message { max-width: min(80%, 40rem); }
.chat__message--out { align-self: flex-end; text-align: right; }
.chat__bubble {
  display: inline-block;
  padding: 0.6rem 0.9rem;
  border-radius: 12px;
  background: var(--color-surface-alt);
  text-align: left;
  white-space: pre-wrap;
  word-break: break-word;
}
.chat__message--out .chat__bubble {
  background: var(--color-accent-soft);
}
.chat__meta { font-size: 0.75rem; color: var(--color-muted); margin-top: 0.2rem; }
.chat__form { max-width: 40rem; }
.message-preview { color: var(--color-muted); }

/* Сопоставление колонок при импорте (2026-08-13): подпись слева, выбор
   справа — так видно все поля сразу, а не по одному в столбик. */
.import-map { display: flex; flex-direction: column; gap: 0.6rem; margin-bottom: 1rem; }
.import-map__row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.import-map__label { display: flex; flex-direction: column; }
.import-map__row select { min-width: 16rem; }

/* Пример подходящего файла на экране загрузки (2026-09-20). Первый
   столбец — номер строки, как в Excel: рядом стоит поле «строка с
   названиями колонок», и человек должен видеть, что за число от него
   хотят. Колонка узкая и приглушённая — это подпись, а не данные. */
.import-example__num {
  width: 2rem;
  text-align: center;
  color: var(--color-muted);
  background: var(--color-surface-alt);
}

/* Подсказка имён в форме записи (2026-08-13). Обёртка нужна, чтобы
   список лёг ровно под полем: нативный <datalist> внутри модалки браузер
   уводил за край экрана, поэтому подсказка своя — обычный блок в потоке,
   как у выбора услуг и клиента в этой же форме. */
.name-picker { position: relative; }
.name-picker .client-search-results {
  position: absolute;
  left: 0;
  right: 0;
  z-index: 5;
}

/* Переименование варианта прямо в строке (2026-08-13): поле и кнопка в
   одну линию, иначе таблица разъезжается по высоте. */
.referral-row__form { display: flex; gap: 0.5rem; align-items: center; }
.referral-row__form input[type="text"] { flex: 1; min-width: 12rem; }

/* Итоги месяца в карточке зарплаты (2026-08-13) — отступ снизу, чтобы
   карточки не липли к списку операций под ними. */
.payroll-months { margin-bottom: 1.25rem; }

/* Сегодняшняя строка в «Моём графике» (2026-08-13): мастер открывает
   экран, чтобы понять «а сегодня я до скольки» — эта строка должна
   находиться взглядом сразу. */
.my-schedule__today { background: var(--color-surface-alt); font-weight: 600; }

/* Кнопка установки кабинета на телефон (2026-08-13). Внизу справа, над
   содержимым: она нужна один раз, и место в общем потоке ей ни к чему —
   а прятать её в меню значит, что её не найдут. */
.install-app {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 40;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
}
/* Подсказка для iPhone (2026-08-29): полоса снизу, а не всплывающее
   окно. Окно посреди экрана человек закрывает не читая — а здесь и
   читать-то нужно одну строку. */
.install-ios {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 41;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.9rem 1rem calc(0.9rem + env(safe-area-inset-bottom));
  background: var(--color-sidebar);
  color: var(--ok-on-dark);
  font-size: 0.9rem;
}
.install-ios p { margin: 0; }
.install-ios .btn--link { color: var(--ok-sand); flex-shrink: 0; }

@media (max-width: 768px) {
  /* Обе подсказки поднимаются над нижней панелью разделов: иначе кнопка
     установки ложится ровно на «Календарь» и «Клиентов» (2026-08-29). */
  .install-app { bottom: calc(4.5rem + env(safe-area-inset-bottom)); }
  .install-ios { bottom: calc(3.25rem + env(safe-area-inset-bottom)); }
}

/* --- Подключение салона и приглашение (2026-08-14) -----------------------

   До этого экрана способа завести салон не существовало вовсе: он
   создавался руками в консоли. Ссылка показывается один раз сразу после
   создания — чтобы владелец сервиса скопировал её, пока помнит, зачем
   заводил салон. */
.invite-result { border: 1px solid var(--color-accent); border-radius: 12px; }
.invite-result__link { display: flex; gap: 0.6rem; flex-wrap: wrap; margin: 0.75rem 0; }
.invite-result__link input {
  flex: 1 1 20rem;
  min-height: 44px;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface-alt);
  color: var(--color-text);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9rem;
}
.platform-reissue { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin: 0.5rem 0 1rem; }
/* Узкая колонка для страниц, куда человек приходит по ссылке из
   сообщения: там одно действие и незачем растягивать его на весь экран. */
.settings--narrow { max-width: 26rem; }

/* «Забыли пароль?» под формой входа (2026-08-14) — тихой ссылкой: тем,
   кто помнит пароль, она не нужна. */
.login__forgot { margin-top: 1rem; font-size: 0.9rem; }

/* Сообщения об ошибках от салонов (2026-08-14). Новые выделены рамкой:
   в списке из полусотни разобранных одно новое иначе теряется. */
.bug-report {
  padding: 1rem 1.15rem;
  margin-bottom: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 12px;
  background: var(--color-surface);
}
.bug-report--new { border-color: var(--color-link); border-left-width: 4px; }
.bug-report--done { opacity: 0.6; }
.bug-report__head { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; align-items: baseline; }
.bug-report__text { margin: 0.6rem 0; white-space: pre-wrap; word-break: break-word; }
/* Технический след мелким шрифтом и с переносом: длинный User-Agent
   иначе растягивает карточку за край экрана. */
.bug-report__tech { font-size: 0.8rem; word-break: break-all; }
.bug-report__form { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.75rem; }
.bug-report__form input { flex: 1 1 14rem; }
.bug-report__form select,
.bug-report__form input {
  min-height: 40px;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface-alt);
  color: var(--color-text);
}

/* Переписка с поддержкой (2026-08-14): чат стал настоящим. Значок
   непрочитанного в меню — чат открывают редко, и без отметки ответ
   пролежал бы неделю. */
.sidebar__badge {
  margin-left: auto;
  min-width: 20px;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: var(--color-error);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 600;
  text-align: center;
  line-height: 20px;
}
.support-chat__empty { color: var(--color-muted); }
.support-chat__time { display: block; margin-top: 0.2rem; font-size: 0.75rem; opacity: 0.7; }
/* Ответ поддержки — слева и другим фоном, как чужая реплика в мессенджере. */
.support-chat__message--support { background: var(--color-surface-alt); align-self: flex-start; }
.support-dialog {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  max-height: 60vh;
  overflow-y: auto;
  padding: 1rem;
  margin: 1rem 0;
  border: 1px solid var(--color-border);
  border-radius: 12px;
}
.support-dialog__reply textarea { width: 100%; }

/* --- Публичная страница сервиса (2026-08-15) -----------------------------

   Раньше корень сайта вёл прямо на форму входа: салон, которому
   рассказали про «Фенхель», открывал адрес, видел поле «телефон и пароль»
   и закрывал вкладку.

   Страница светлая всегда: тёмная тема — настройка рабочего кабинета, а
   сюда человек попадает впервые и ничего не настраивал. */
.landing {
  max-width: 60rem;
  margin: 0 auto;
  padding: 0 1.25rem 4rem;
  line-height: 1.6;
}
.landing__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.25rem 0;
}
/* Логотип: знак и слово в одну строку (2026-08-29). Вес 500, а не 700 —
   так задан логотип в дизайн-пакете, жирнее его набирать нельзя. Цвет —
   основной текстовый, а не ссылочный: это заголовок страницы, а не ссылка
   (на лендинге он никуда не ведёт — мы уже здесь). */
.landing__logo {
  display: inline-flex;
  align-items: center;
  /* Расстояние между знаком и словом = 1/4 высоты знака, правило пакета:
     30 px знак → 7-8 px промежуток. */
  gap: 0.5rem;
  font-size: 1.35rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--color-text);
  text-decoration: none;
}


/* Отклик на нажатие и наведение (2026-09-18, навык Emil Kowalski).

   ГЛАВНОЕ, ЧЕГО НЕ ХВАТАЛО. Кнопка на первом экране не менялась ни от
   курсора, ни от нажатия: человек жал — и до перехода страницы не
   получал ни одного признака, что нажатие услышано. На медленной сети
   это секунда тишины, и за неё успевают нажать второй раз.

   Масштаб 0.97, а не 0.9: нажатие должно ощущаться, а не разыгрываться.
   160 мс — на грани, за которой отклик перестаёт быть мгновенным.

   Наведение — отдельно и только для мыши: на телефоне hover срабатывает
   от касания и остаётся висеть после него, так что кнопка выглядит
   нажатой, пока человек не ткнёт в другое место. */
.landing .btn,
/* Тень при наведении живёт на отдельном слое и проявляется
   прозрачностью, а не растёт из нуля: анимация самой box-shadow
   заставляет перерисовывать кнопку на каждом кадре. */
.landing .btn--primary { position: relative; }
.landing .btn--primary::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 10px 24px -12px rgba(78, 70, 54, 0.55);
  opacity: 0;
  transition: opacity 160ms var(--ok-ease-out);
  pointer-events: none;
}
.landing .btn:active { transform: scale(0.97); }
/* Обводка при переходе с клавиатуры: до сих пор здесь работала
   браузерная, а она на песочном фоне почти не видна. Цветом текста, а не
   акцентом: акцент и есть цвет самой кнопки. */
.landing .btn:focus-visible {
  outline: 2px solid var(--color-text);
  outline-offset: 3px;
}

@media (hover: hover) and (pointer: fine) {
  .landing .btn--primary:hover { transform: translateY(-1px); }
  .landing .btn--primary:hover::after { opacity: 1; }
  /* Нажатие перебивает наведение: кнопка идёт вниз и тень гаснет —
     иначе она «подпрыгивает» и вдавливается одновременно. */
  .landing .btn--primary:active { transform: scale(0.97); }
  .landing .btn--primary:active::after { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  /* Меньше движения — значит без сдвигов и масштаба. Тень и цвет
     остаются: они говорят то же самое, ничего не перемещая. */
  .landing .btn,
  .landing .btn:active,
  .landing .btn--primary:hover { transform: none; }
}


/* Первый экран страницы сравнения (2026-08-29, прежний вид главной).
   Главная переехала на новую композицию 2026-09-18, а сравнение осталось
   текстовой страницей: там нет продукта, который можно показать, — там
   разбор чужих тарифов. Классы прежние, потому что разметка той страницы
   не менялась. */
.landing__hero {
  padding: 2.5rem 2rem 3rem;
  margin: 1rem 0 2rem;
  background: var(--color-surface-alt);
  border-radius: var(--ok-r-xl);
}
.landing__hero h1 {
  margin: 0 0 1rem;
  font-size: clamp(1.9rem, 5vw, 3rem);
  line-height: 1.15;
  letter-spacing: -0.02em;
}
.landing__lead { max-width: 38rem; font-size: 1.1rem; color: var(--color-muted); }
.landing__price {
  margin: 1.75rem 0 0.25rem;
  font-size: 1.5rem;
  font-weight: 700;
}
.landing__price span { display: block; font-size: 1rem; font-weight: 400; color: var(--color-muted); }
.landing__trial { margin: 0.25rem 0 1.5rem; color: var(--color-muted); }
.landing__cta {
  display: inline-flex;
  align-items: center;
  width: auto;
  min-height: 52px;
  padding: 0 2rem;
  font-size: 1.05rem;
  text-decoration: none;
}

/* Раздел заявки на странице сравнения: воздух вокруг и линия сверху.
   Главная переехала на свою композицию (.lp-*) 2026-09-19, здесь остался
   прежний вид. */
.landing__form-section {
  padding: clamp(3.5rem, 7vw, 6rem) 0;
  border-top: 1px solid var(--color-border);
}

/* Заголовки разделов одного кегля (2026-09-18): было 24 px у двух и
   33.6 у трёх — разнобой читается как несобранная страница, даже когда
   человек не может сказать, что именно не так. */
.landing__form-section h2 {
  margin: 0 0 1.5rem;
  font-size: clamp(1.5rem, 2.6vw, 2.1rem);
  letter-spacing: -0.02em;
}


.landing__form { max-width: 26rem; }
.landing__form .sale-box__field { margin-bottom: 0.85rem; }
.landing__form input,
.landing__form textarea {
  width: 100%;
  min-height: 44px;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  /* 16px обязательны: при меньшем размере Safari на iPhone зумит всю
     страницу, как только палец попадает в поле. */
  font-size: 16px;
}
.landing__consent { display: flex; gap: 0.5rem; align-items: flex-start; margin: 1rem 0; font-size: 0.9rem; }
.landing__consent input { width: 20px; height: 20px; flex-shrink: 0; }
.landing__sent {
  max-width: 26rem;
  padding: 1.25rem;
  border: 1px solid var(--color-accent);
  border-radius: 12px;
  background: var(--color-accent-soft);
}

.landing__legal { padding: 1rem 0 3rem; max-width: 45rem; }
.landing__legal h2 { margin-top: 2rem; font-size: 1.15rem; }
/* Черновик помечен явно: незаполненный договор не должен выглядеть
   готовым. */
.landing__draft {
  padding: 0.75rem 1rem;
  border-left: 4px solid var(--color-error);
  background: var(--color-surface-alt);
  color: var(--color-error-text);
}

.landing__footer {
  padding-top: 2rem;
  border-top: 1px solid var(--color-border);
  font-size: 0.9rem;
}
.landing__copyright { color: var(--color-muted); }

/* --- Счёт на оплату (2026-08-15) -----------------------------------------

   Печатная страница, а не PDF-файл: генератор PDF — новая зависимость
   (правило №7), а браузер сохраняет страницу в PDF по Ctrl+P сам, и
   результат для банка тот же. */
.invoice-page { max-width: 48rem; margin: 0 auto; padding: 1.5rem 1.25rem 4rem; }
.invoice-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.5rem;
}
.invoice-warning {
  padding: 0.85rem 1rem;
  margin-bottom: 1.5rem;
  border-left: 4px solid var(--color-error);
  background: var(--color-surface-alt);
  color: var(--color-error-text);
}
.invoice { border: 1px solid var(--color-border); border-radius: 12px; padding: 1.5rem; }
.invoice__title { margin: 0 0 1.25rem; font-size: 1.2rem; }
.invoice__parties { width: 100%; border-collapse: collapse; margin-bottom: 1.5rem; }
.invoice__parties th {
  width: 9rem;
  text-align: left;
  vertical-align: top;
  padding: 0.4rem 1rem 0.4rem 0;
  font-weight: 600;
  color: var(--color-muted);
}
.invoice__parties td { padding: 0.4rem 0; vertical-align: top; }
.invoice__items { width: 100%; border-collapse: collapse; margin-bottom: 1rem; }
.invoice__items th,
.invoice__items td {
  padding: 0.5rem;
  border: 1px solid var(--color-border);
  text-align: left;
}
.invoice__items th { background: var(--color-surface-alt); font-size: 0.9rem; }
.invoice__total { margin: 0.5rem 0 0.25rem; font-size: 1.1rem; text-align: right; }
.invoice__vat { margin: 0 0 1.25rem; text-align: right; color: var(--color-muted); }
.invoice__note { color: var(--color-muted); font-size: 0.9rem; }
.invoice__sign { margin-top: 2.5rem; max-width: 18rem; }
.invoice__sign-line {
  margin: 2rem 0 0;
  padding-top: 0.3rem;
  border-top: 1px solid var(--color-border);
  font-size: 0.8rem;
  color: var(--color-muted);
  text-align: center;
}

/* На бумаге не нужны ни кнопки, ни рамки, ни тёмная тема — только сам
   счёт. Печать всегда светлая: тёмный фон съест картридж и деньги. */
@media print {
  .invoice-toolbar { display: none; }
  .invoice-page { max-width: none; padding: 0; }
  .invoice { border: none; padding: 0; }
  body { background: #fff; color: #000; }
  /* Печатная форма остатков (2026-09-10, просьба салонов — тема 298 у
     Арники: «разлинованные печатные формы»). Кнопка «Печать» и ссылка
     назад на бумаге не нужны, а сетка нужна: лист заполняют ручкой у
     полки. */
  .print-sheet__hide,
  .sidebar,
  .topbar { display: none !important; }
  .print-table { width: 100%; }
}

/* Лист для инвентаризации (2026-09-10). Рамки у каждой ячейки: печать
   чёрно-белая, и полосатый фон, которым таблицы разделены на экране, на
   бумаге не виден вовсе. */
.print-table {
  width: 100%;
  border-collapse: collapse;
  margin: 1rem 0;
  font-size: 0.9rem;
}
.print-table th,
.print-table td {
  border: 1px solid #999;
  padding: 0.35rem 0.5rem;
  text-align: left;
}
/* Пустые графы «факт» и «расхождение» — шире прочих: в узкую клетку
   число ручкой не влезает, и лист становится бесполезным. */
.print-table__blank { min-width: 5.5rem; }
.print-sheet__head h1 { margin-bottom: 0.25rem; }
.print-sheet__sign { margin-top: 2rem; }

/* Личный счёт и баллы в карточке клиента (2026-08-16). Две карточки
   рядом: деньги клиента и подаренные салоном баллы — разные сущности, и
   путать их у стойки нельзя. */
.wallet-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1rem;
  margin: 0.75rem 0 1.5rem;
}
.wallet-card {
  padding: 1rem 1.15rem;
  border: 1px solid var(--color-border);
  border-radius: 12px;
  background: var(--color-surface-alt);
}
.wallet-card__value { margin: 0 0 0.25rem; font-size: 1.6rem; font-weight: 700; }
.wallet-card__value span { font-size: 1rem; font-weight: 400; color: var(--color-muted); }
.wallet-card__form { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.85rem; }
.wallet-card__form input {
  flex: 1 1 7rem;
  min-height: 40px;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface);
  color: var(--color-text);
}
/* Поля списания в форме продажи — на видном месте, но отделены от суммы:
   это не «сколько платит клиент», а «чем платит». */
.sale-box__wallet {
  margin-top: 0.85rem;
  padding-top: 0.85rem;
  border-top: 1px dashed var(--color-border);
}

/* Приглашения в карточке клиента (2026-08-26). Ссылка полем, а не
   текстом: её копируют целиком и пересылают в мессенджер, а длинный
   адрес мышью выделяется криво — клик выделяет всё сразу. */
.referral-box {
  margin-top: 1.25rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface-alt);
}
.referral-box__title { margin: 0 0 0.5rem; font-size: 1rem; }
.referral-box__stats {
  display: flex; flex-wrap: wrap; gap: 0.35rem 1.25rem;
  font-size: 0.9rem; margin-bottom: 0.75rem;
}
.referral-box__label {
  display: block; font-size: 0.85rem; color: var(--color-muted);
  margin-bottom: 0.25rem;
}
.referral-box__link {
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface);
  font-size: 0.85rem;
}

/* Список приглашённых (2026-08-26). У салона — таблица с деньгами,
   у клиента — простой список имён: суммы ему видеть не положено. */
.referral-box__table { margin: 0.75rem 0; }
.cabinet__invited { list-style: none; margin: 0.5rem 0; padding: 0; }
.cabinet__invited li {
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--color-border);
  font-size: 0.95rem;
}
.cabinet__invited li:last-child { border-bottom: none; }
.cabinet__invited-ok { color: var(--color-link); font-size: 0.85rem; }
.cabinet__invited-wait { color: var(--color-muted); font-size: 0.85rem; }

/* Раскрывающееся пояснение в настройках и на импорте (2026-08-26).
   Свёрнуто по умолчанию: подробности нужны один раз при переезде, а
   место на экране занимали бы всегда. */
.settings__details {
  margin: 0.75rem 0;
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface-alt);
}
.settings__details > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 0.95rem;
}

/* Отбор салонов по состоянию подписки в панели платформы (2026-08-26). */
.platform-filters { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; margin: 0.75rem 0; }
.platform-filters a { font-size: 0.9rem; }
.platform-filters--on { font-weight: 700; text-decoration: none; }

/* Переключатель салонов сети в шапке меню (2026-08-26). Виден только
   тем, у кого салонов несколько. */
.sidebar__network { padding: 0 1rem 0.75rem; }
.sidebar__network-label {
  display: block; font-size: 0.75rem; color: var(--color-muted);
  margin-bottom: 0.25rem; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis;
}
.sidebar__network-select {
  width: 100%; padding: 0.35rem 0.5rem; font-size: 0.9rem;
  background: var(--color-surface); color: var(--color-text);
  border: 1px solid var(--color-border); border-radius: 6px;
}
html.sidebar-collapsed .sidebar__network { display: none; }

/* Плашка сеанса поддержки (2026-08-26). Красная, в отличие от зелёной
   плашки просмотра ролей: там человек смотрит СВОЙ кабинет чужими
   глазами, здесь — чужую клиентскую базу. Разница должна быть видна с
   первого взгляда, не читая текст. */
.support-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin: 0 0 1rem;
  padding: 0.7rem 1rem;
  border: 1px solid var(--color-error);
  border-left-width: 4px;
  border-radius: 10px;
  background: rgba(220, 38, 38, 0.08);
  font-size: 0.95rem;
}
.support-bar .btn { margin: 0; }

.platform-support-enter { margin: 0.75rem 0 1.25rem; }
.platform-support-enter .settings__hint { display: block; margin-top: 0.35rem; }

/* Строка-триггер подгрузки списка клиентов (2026-08-27). Занимает место
   строки таблицы, чтобы прокрутка не дёргалась в момент подмены. */
.client-list__more-cell {
  padding: 0.9rem 0.75rem;
  text-align: center;
  color: var(--color-muted);
  font-size: 0.9rem;
}


/* Сдача клиенту (2026-08-27, просьба салонов). Крупно и спокойно: цифру
   читают, держа деньги в руке, а «не хватает» должно выделяться цветом —
   это ошибка, а не результат. */
.sale-box__change-result {
  margin: 0.35rem 0 0;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--color-warranty-text);
}
.sale-box__change-result--short { color: #b91c1c; }


/* Переключатель порядка строк графика (2026-08-27). Мелкий и приглушённый:
   это настройка вида, а не действие — она не должна спорить с кнопками
   месяца рядом. */
.schedule-order {
  margin: 0.25rem 0 0.75rem;
  font-size: 0.85rem;
  color: #6b7280;
}
.schedule-order__current {
  font-weight: 600;
  color: #111827;
}


/* Тихие часы (2026-08-27): две границы рядом, а не одна под другой —
   это один интервал, и читать его надо целиком. */
.quiet-hours {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 0.75rem;
}
.quiet-hours > div { flex: 1 1 8rem; }

/* Варианты услуги в виджете (2026-08-27): «длинные волосы», «объём 500 мл».
   Строками с ценой справа — так же, как список услуг выше, чтобы человек
   читал их одним движением глаз. */
.widget-options {
  border: 1px solid #e5e7eb;
  border-radius: 10px;
  padding: 0.75rem;
  margin: 0 0 1rem;
}
.widget-options legend {
  padding: 0 0.35rem;
  font-size: 0.85rem;
  color: #6b7280;
}
.widget-options__item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.25rem;
  cursor: pointer;
}
.widget-options__item + .widget-options__item { border-top: 1px solid #f3f4f6; }
.widget-options__name { flex: 1; }
.widget-options__price { font-weight: 600; white-space: nowrap; }

/* Групповое занятие в журнале (2026-08-27). Своя заливка, чтобы блок не
   путали с обычной записью: у него нет клиента, зато есть счётчик мест. */
.journal-grid__session {
  background: #ede9fe;
  border-left: 3px solid #7c3aed;
  color: #1f2937;
  text-decoration: none;
}
.journal-grid__session:hover { background: #ddd6fe; }

/* Метка «часто срывает записи» (2026-08-27) и оценка мастера в витрине.
   Метка предупреждающая, но не пугающая: это подсказка администратору,
   а не приговор клиенту. */
.badge--warning {
  background: #fef3c7;
  color: var(--color-warn-on-chip);
}
.widget-rating {
  color: #b45309;
  font-weight: 600;
}

/* Отчёт «Прибыли и убытки» (2026-08-27). Убыток красным: это то, что
   владелец должен заметить, не вчитываясь в цифры. */
.pnl-row__category { padding-left: 1.5rem; color: #4b5563; }
.pnl-row__profit { border-top: 2px solid #e5e7eb; }
.pnl-row__loss { color: #b91c1c; font-weight: 600; }
.table-scroll { overflow-x: auto; }

/* Витрина абонементов и товаров (2026-08-27). Кнопка «Хочу» справа от
   цены: строка читается слева направо — что, сколько, действие. */
.widget-shop__item { align-items: center; }
.widget-shop__want { white-space: nowrap; }

/* «Мастер не важен» на карточке журнала (2026-08-27). Приглушённо: это
   подсказка на случай перестановок, а не состояние записи. */
.journal-grid__appointment-flexible {
  display: block;
  font-size: 0.65rem;
  line-height: 1.1;
  color: #6b7280;
  font-style: italic;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Выручка мастера за день в шапке его колонки (2026-08-27). Заметнее
   должности, но тише имени: администратор ищет глазами имя, а цифру
   считывает следом. */
.journal-grid__staff-revenue {
  display: block;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--color-warranty-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Кнопки связи в карточке клиента (2026-08-29): мельче поля, чтобы не
   спорить с ним за внимание — это действие «попутно», а не главное. */
.client-detail__contacts { margin: 0.35rem 0 0; font-size: 0.85rem; }

/* Часы приёма в онлайн-записи (2026-08-29). Дни недели карточками, часы
   внутри — списком: так видно всю неделю разом, а не по одному дню. */
.fixed-slots {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: 1rem;
  margin-top: 1.5rem;
}
.fixed-slots__day {
  padding: 0.9rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: var(--ok-r-lg);
}
.fixed-slots__day h2 { font-size: 1rem; margin: 0 0 0.5rem; }
.fixed-slots__list { list-style: none; padding: 0; margin: 0; }
.fixed-slots__list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.25rem 0;
  border-bottom: 1px solid var(--color-border);
}
.fixed-slots__list li:last-child { border-bottom: none; }
.fixed-slots__list form { margin: 0; }
.fixed-slots__add {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 1rem;
  margin: 1.5rem 0;
}
.fixed-slots__add label { display: flex; flex-direction: column; gap: 0.35rem; font-size: 0.9rem; }

/* Страница сравнения (2026-08-29). Живёт по соседству с лендингом и
   пользуется его же сеткой, но карточки тарифов и таблица нужны только
   здесь. */
.compare { margin: 2.5rem 0; }
.compare h2 { font-size: 1.4rem; margin: 0 0 1rem; }
.compare__form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 1rem;
  margin-bottom: 1.5rem;
}
.compare__form label { display: flex; flex-direction: column; gap: 0.35rem; font-size: 0.9rem; }
.compare__form input { width: 10rem; padding: 0.6rem 0.75rem; }
.compare__cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1rem;
}
.compare__card {
  padding: 1.25rem;
  border: 1px solid var(--color-border);
  border-radius: var(--ok-r-lg);
  background: var(--color-surface);
}
/* Наша карточка выделена подложкой, а не рамкой потолще: это единственное
   цветное пятно на странице, и глаз должен идти к нему первым. */
.compare__card--ours {
  background: var(--color-surface-alt);
  border-color: var(--ok-sand);
}
.compare__card-title { font-weight: 600; font-size: 1.05rem; }
.compare__card-note { color: var(--color-muted); font-size: 0.85rem; margin-top: 0.15rem; }
.compare__price { font-size: 1.8rem; font-weight: 600; margin: 0.75rem 0 0.5rem; }
.compare__price span { font-size: 1rem; font-weight: 400; color: var(--color-muted); }
.compare__breakdown { list-style: none; padding: 0; margin: 0; font-size: 0.9rem; }
.compare__breakdown li { padding: 0.25rem 0; border-bottom: 1px solid var(--color-border); }
.compare__breakdown li:last-child { border-bottom: none; }
.compare__hint { color: var(--color-muted); font-size: 0.8rem; }
.compare__year { margin-top: 0.75rem; font-weight: 600; }
.compare__disclaimer {
  margin-top: 1.25rem;
  padding: 1rem 1.25rem;
  border-left: 3px solid var(--color-border);
  color: var(--color-muted);
  font-size: 0.9rem;
}
.compare__table { width: 100%; border-collapse: collapse; }
.compare__table th, .compare__table td {
  text-align: left;
  padding: 0.7rem 0.9rem;
  border-bottom: 1px solid var(--color-border);
  vertical-align: top;
}
.compare__table thead th { background: var(--color-surface-alt); }
/* Строки, ради которых страница и написана, — плотнее: разница видна не
   в списке возможностей, а в том, за что придётся доплатить. */
.compare__row--key td { font-weight: 500; }
.compare__them { color: var(--color-muted); }
.compare__cta { text-align: center; }
/* Кнопка под расчётом: в строку с подписью, чтобы не разрывать связь
   «увидел цифры — вот что делать дальше». */
.compare__after-calc {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.25rem;
}
/* Две кнопки в шапке рядом: заявка акцентная, вход — обычной ссылкой. */
.landing__top-actions { display: flex; align-items: center; gap: 0.5rem; }

@media (max-width: 768px) {
  /* На телефоне кнопка из шапки уходит: рядом с названием ей места нет,
     она наезжала на логотип. Терять её не жалко — в первом же экране она
     стоит прямо под ценой, то есть видна сразу, без прокрутки. */
  .landing__top-actions .btn--primary { display: none; }
  .landing__top { padding: 0.9rem 0; }
}

@media (max-width: 768px) {
  /* Таблица сравнения прокручивается внутри себя. Мобильные правила
     кабинета сюда не достают: лендинг и эта страница живут вне .content,
     у них своя разметка (2026-08-29). */
  .compare__table-wrap {
    display: block;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .compare__table { min-width: 34rem; }
  .compare__form input { width: 100%; }
  .compare__form label { flex: 1 1 100%; }
  .compare h2 { font-size: 1.2rem; }
}

/* Резервные коды второго фактора (2026-08-29). Моноширинным и крупно:
   их переписывают на бумагу от руки, и «0» с «O» здесь путать нельзя. */
.backup-codes {
  list-style: none;
  padding: 0;
  margin: 0.75rem 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: 0.5rem;
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  font-size: 1.05rem;
  letter-spacing: 0.02em;
}
.backup-codes li {
  padding: 0.4rem 0.6rem;
  background: var(--color-surface-alt);
  border-radius: var(--ok-r-sm);
  text-align: center;
}
/* Врезка с тем, что нельзя пролистать: рамка слева, как у срочных строк
   сводки. */
.settings__callout {
  padding: 1rem 1.25rem;
  border-left: 3px solid var(--color-accent);
  background: var(--color-surface-alt);
  border-radius: var(--ok-r-sm);
  margin-bottom: 1rem;
}
.login__resend { margin-top: 0.5rem; }

/* ===== Мобильный кабинет (2026-08-29) ==================================

   Решение основателя: мобильная версия заменяет приложения в магазинах,
   поэтому кабинет ставится на телефон как PWA и им пользуются все —
   администратор за стойкой, мастер между клиентами, владелец в дороге.

   До этого кабинет был рассчитан на компьютер: панель меню шириной 220 px
   занимала на телефоне больше половины экрана, а свёрнутая до 56 px
   прятала всё, кроме трёх верхних пунктов, — до клиентов и финансов
   добраться было нельзя.

   Устройство мобильной раскладки:

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

   Граница 768 px: телефоны и планшет вертикально. На планшете в альбоме
   и на компьютере всё остаётся как было.
   ====================================================================== */

/* Кнопка меню в шапке. На компьютере её нет: там панель видна всегда, а
   свернуть её можно бургером внутри самой панели. */
.topbar__burger {
  display: none;
  align-items: center;
  justify-content: center;
  /* 44 px, а не 40 (2026-09-18, аудит мобильной версии): это главная и
     единственная дверь в меню на телефоне. */
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--ok-r-sm);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

/* Затемнение под шторкой. Существует всегда, но видимо только когда
   шторка открыта: анимировать появление элемента, которого нет в DOM,
   нечем. */
.mobile-scrim {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 39;
  background: rgba(0, 0, 0, 0.45);
}

/* Нижняя панель разделов. На компьютере скрыта. */
.tabbar { display: none; }

@media (max-width: 768px) {
  .topbar {
    justify-content: space-between;
    padding: 0.6rem 0.9rem;
    /* Шапка липкая: на длинных списках (клиенты, товары) иначе
       приходится крутить наверх ради выхода и меню. */
    position: sticky;
    top: 0;
    z-index: 20;
    background: var(--color-bg);
  }
  .topbar__burger { display: inline-flex; }
  .topbar__user { font-size: 0.85rem; }
  /* Быстрый поиск клиента на телефоне скрыт целиком (2026-09-17, просьба
     основателя: «эта строка сверху вообще на каждом экране, она мешает»).

     Заводился он для компьютера, где живёт в одной строке с шапкой и
     ничего не занимает. На телефоне ему пришлось отдать вторую строку —
     и полоса на пол-экрана оказалась над КАЖДЫМ разделом, отодвигая вниз
     то, ради чего человек зашёл. Поиск по базе никуда не делся: он на
     своём месте в разделе «Клиенты». */
  .quick-search { display: none; }

  /* --- Панель меню как шторка ---------------------------------------- */
  .sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 40;
    width: 82vw;
    max-width: 20rem;
    overflow-y: auto;
    /* Прячется сдвигом, а не display: none: так она выезжает, и человек
       видит, откуда она взялась и куда уходит. */
    transform: translateX(-100%);
    transition: transform 180ms ease-out;
    /* Вырез и полоса жестов на iPhone учитывает кнопка внизу (см. ниже):
       у самой панели нижнего отступа нет, иначе под прилипшей кнопкой
       оставалась бы полоска с кусками пунктов. */
  }
  html.mobile-menu-open .sidebar { transform: none; }
  html.mobile-menu-open .mobile-scrim { display: block; }
  /* Страница под открытой шторкой не прокручивается: иначе палец
     листает список за шторкой вместо самой шторки. */
  html.mobile-menu-open body { overflow: hidden; }

  /* Свёрнутое состояние — настройка компьютера, и на телефоне она не
     действует: там панель либо закрыта целиком, либо открыта полностью.
     Без этого сохранённое с компьютера «свёрнуто» дало бы на телефоне
     шторку из трёх иконок без подписей. */
  html.sidebar-collapsed .sidebar { width: 82vw; }
  html.sidebar-collapsed .sidebar__logo,
  html.sidebar-collapsed .sidebar__brand-text,
  html.sidebar-collapsed .sidebar__label,
  html.sidebar-collapsed .sidebar__group-toggle { display: revert; }
  html.sidebar-collapsed .sidebar__brand { justify-content: flex-start; padding: 0 1rem 1rem; }
  html.sidebar-collapsed .sidebar__nav-link {
    justify-content: flex-start;
    padding: 0.7rem 1rem;
    gap: 0.65rem;
  }
  /* Кнопка внизу шторки остаётся, но означает другое (2026-09-13,
     просьба основателя: «в мобильной версии тоже должно быть удобно»).

     На телефоне сворачивать панель до полоски иконок незачем — шторка
     либо открыта, либо закрыта. Зато закрыть её кнопкой удобнее, чем
     тапом мимо: палец уже внизу экрана, а промахнуться мимо узкой
     полоски справа легко. Поэтому здесь она подписана «Закрыть».  */
  .sidebar__collapse-text { display: none; }
  .sidebar__collapse-text--mobile { display: inline; }
  /* Отступ под вырез и полосу жестов iPhone — внутри кнопки: так она
     доходит до самого низа шторки, и под ней ничего не проглядывает. */
  .sidebar__collapse { padding-bottom: calc(0.7rem + env(safe-area-inset-bottom)); }

  /* Пункты меню под палец: 44 px — минимальная цель нажатия, ниже
     промахиваются. */
  .sidebar__nav-link { padding: 0.7rem 1rem; min-height: 2.75rem; }
  .sidebar__group-toggle { padding: 0.7rem 1rem; min-height: 2.75rem; }

  /* --- Нижняя панель разделов ---------------------------------------- */
  .tabbar {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 30;
    background: var(--color-sidebar);
    border-top: 1px solid rgba(246, 241, 232, 0.14);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .tabbar__item {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.15rem;
    padding: 0.5rem 0.25rem;
    min-height: 3.25rem;
    border: none;
    background: none;
    color: var(--ok-on-dark-muted);
    font: inherit;
    font-size: 0.7rem;
    text-decoration: none;
    cursor: pointer;
  }
  .tabbar__item--active { color: var(--ok-on-dark); }
  /* Активный раздел отмечен ещё и полоской сверху: цветом одним его
     отличать нельзя — при нарушении цветовосприятия разница пропадёт. */
  .tabbar__item--active::before {
    content: "";
    position: absolute;
    top: 0;
    width: 2.25rem;
    height: 2px;
    border-radius: 0 0 2px 2px;
    background: var(--ok-sand);
  }
  .tabbar__item { position: relative; }
  .tabbar__icon { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.8; }
  .tabbar__label { white-space: nowrap; }

  /* Содержимое не должно уходить под нижнюю панель. Боковые поля
     меньше компьютерных: на 375 px каждый лишний пиксель поля — это
     пиксель, отнятый у таблицы. */
  .content {
    padding: 1rem 0.9rem calc(4.5rem + env(safe-area-inset-bottom));
  }

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

     Виджеты календаря и выручки уходят ПОД сетку: сбоку на 375 px им
     места нет, а нужны они реже, чем сама сетка. */
  /* align-items: stretch, а не flex-start с компьютера: в колонку
     flex-start задаёт детям ширину по содержимому, и сетка мастеров
     растягивала страницу вместо того, чтобы прокручиваться внутри. */
  .journal__layout {
    flex-direction: column;
    align-items: stretch;
    gap: 1rem;
    min-width: 0;
  }
  .journal__sidebar { width: 100%; }
  /* Поиск на телефоне занимает всю строку: рядом с ним в этой же полосе
     живут галочки и кнопки, и на 375 px им вместе не разойтись. */
  .search-field { flex-basis: 100%; max-width: none; }
  .journal__toolbar { gap: 0.5rem; margin-bottom: 1rem; }
  .journal__toolbar-actions { margin-left: 0; }
  /* Колонка мастера уже компьютерной, но не настолько, чтобы имя
     клиента превратилось в многоточие: 140 px — две-три записи в
     видимой части экрана, дальше прокруткой. */
  /* Сетка сама по себе шире экрана — ей это и положено. Но без явного
     ограничения ширины она растягивает страницу целиком, и вбок уезжает
     весь кабинет вместе с шапкой. Ограничение включает её собственную
     прокрутку (overflow-x у неё уже есть). */
  .journal-grid {
    font-size: 0.8rem;
    width: 100%;
    max-width: 100%;
    /* Клетка выше, чем на компьютере (2026-09-18, аудит мобильной
       версии: пустая клетка была 20 px при норме касания 44).
       По этим клеткам заводят запись пальцем, и промах — это запись на
       соседние 15 минут.

       32, а не 44: при 44 час занимает 176 px, и в экран телефона
       помещается меньше трёх часов — журнал перестаёт быть журналом.
       32 px увеличивают площадь касания больше чем в полтора раза и
       оставляют в экране почти четыре часа. Остаток риска гасит сама
       форма записи: выбранное время в ней видно и правится. */
    --journal-slot-h: 32px;
  }
  .journal__main { min-width: 0; }

  /* --- Таблицы -------------------------------------------------------
     Любая широкая таблица прокручивается внутри себя, а не растягивает
     страницу: горизонтальная прокрутка всей страницы — то, из-за чего
     на телефоне «уезжает вёрстка» и теряются кнопки. */
  /* Ленты, которые листаются вбок: строка разделов над списком и
     полоса фильтров (2026-09-18, аудит мобильной версии). У первой из
     десяти ссылок в экран помещаются три — «Каналы привлечения», «Кто
     кого привёл», «Откуда узнают» и ещё две живут за краем, и найти их
     на телефоне можно было только случайным движением пальца.

     Тень у края — тот же приём, что у таблиц ниже. */
  .page-head__actions,
  .client-list__categories {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    flex-wrap: nowrap;
  }

  .table-scroll,
  .client-table-wrap,
  .content table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    /* Тень у края — единственный знак, что таблица листается вбок
       (2026-09-18, аудит мобильной версии). Скроллбар на телефоне не
       показывается, и колонки за краем выглядели не спрятанными, а
       отсутствующими: в базе клиентов таблица шириной 847 px живёт в
       окне 346 px, и половина колонок пропадала бесследно.

       Приём известный: две пары фонов в одном свойстве. Белые
       «заглушки» прижаты к краям и не двигаются, тени нарисованы под
       ними и прокручиваются вместе с содержимым — поэтому тень видна
       ровно тогда, когда с этой стороны есть что показать, и гаснет,
       когда докрутили до края. Никакого JS. */
    background:
      linear-gradient(to right, var(--color-bg) 30%, transparent),
      linear-gradient(to left, var(--color-bg) 30%, transparent),
      linear-gradient(to right, var(--scroll-hint), transparent 14px),
      linear-gradient(to left, var(--scroll-hint), transparent 14px);
    background-position: left center, right center, left center, right center;
    background-repeat: no-repeat;
    background-size: 28px 100%, 28px 100%, 14px 100%, 14px 100%;
    background-attachment: local, local, scroll, scroll;
  }

  /* Та же тень для лент разделов и фильтров: они flex-строки, и
     display: block выше их бы сломал, поэтому фон задаётся отдельно. */
  .page-head__actions,
  .client-list__categories {
    background:
      linear-gradient(to right, var(--color-bg) 30%, transparent),
      linear-gradient(to left, var(--color-bg) 30%, transparent),
      linear-gradient(to right, var(--scroll-hint), transparent 14px),
      linear-gradient(to left, var(--scroll-hint), transparent 14px);
    background-position: left center, right center, left center, right center;
    background-repeat: no-repeat;
    background-size: 28px 100%, 28px 100%, 14px 100%, 14px 100%;
    background-attachment: local, local, scroll, scroll;
  }
  .content table > * { display: revert; }

  /* Мини-календарь — исключение из правила выше (2026-09-17, заметил
     основатель: «сентябрь наполовину сжат, и октябрь тоже, правой части
     нету»).

     Правило для широких таблиц делает их display: block с прокруткой
     внутри. Для таблицы с двадцатью колонками это спасение, а календарю
     ломает ширину: у блока width: 100% относится к самому блоку, а
     строки внутри собираются в безымянную таблицу, и та сжимается по
     содержимому — семь узких колонок цифр занимают половину экрана, а
     правый край недели обрывается.

     Календарь широким не бывает: семь колонок помещаются в любой
     телефон. Возвращаем ему обычное поведение таблицы. */
  /* Селектор нарочно длиннее правила выше: у «.content table» вес больше,
     чем у одного класса, и короткая запись молча проигрывала. */
  .journal-calendar table.journal-calendar__table {
    display: table;
    width: 100%;
    max-width: none;
    overflow-x: visible;
    /* Колонки поровну: иначе «1» уже, чем «30», и недели не встают
       столбиками. */
    table-layout: fixed;
  }

  /* Строки «Требует внимания» на сводке: текст и ссылка рядом в строку —
     на 375 px текст сжимается до трёх слов в строке, а ссылка справа
     наезжает на него. В колонку читается как карточка. */
  .dashboard__attention-item {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
  }

  /* --- Панели действий, фильтров и вкладок ---------------------------
     Они собраны флексом в одну строку — на компьютере это правильно, на
     телефоне последние кнопки уезжают за край и становятся недоступны.
     Правило только разрешает перенос и поджимает промежутки; порядок и
     состав панелей не меняются.

     Селектор по части имени класса, а не список из полусотни панелей:
     они называются единообразно (__actions, __toolbar, __filters), и
     новый экран получит перенос сам, без правки этого файла. */
  /* Кнопки действий и наборы фильтров — лента, листается пальцем вбок.
     Переносом их складывать нельзя: у базы клиентов восемь действий и
     семь фильтров, и в столбик они занимали бы пол-экрана, отодвигая
     сам список — то, ради чего экран и открыли. */
  [class*="__actions"],
  [class*="__categories"],
  [class*="__tabs"],
  [class$="-tabs"] {
    flex-wrap: nowrap;
    overflow-x: auto;
    gap: 0.5rem;
    padding-bottom: 0.25rem;
  }
  [class*="__actions"] > *,
  [class*="__categories"] > *,
  [class*="__tabs"] > *,
  [class$="-tabs"] > * { flex: 0 0 auto; }

  /* Панели с заголовком и полями — наоборот, переносом: поле ввода в
     ленте прокрутки нащупать пальцем нельзя. */
  [class*="__toolbar"],
  [class*="__filters"],
  [class*="__search"],
  .journal__date-nav {
    flex-wrap: wrap;
    gap: 0.5rem;
  }
  [class*="__search"] input[type="search"],
  [class*="__search"] input[type="text"] { flex: 1 1 100%; max-width: none; }

  /* Заголовки: фирменные 34 px рассчитаны на лист шириной с монитор, на
     375 px такой заголовок занимает две строки и полэкрана. */
  .content h1 { font-size: 1.5rem; }
  .content h2 { font-size: 1.2rem; }

  /* --- Формы и поля --------------------------------------------------
     16 px — не про красоту: при меньшем размере Safari на iPhone
     увеличивает страницу, как только палец попадает в поле, и вернуть
     масштаб обратно человек уже не может. */
  /* 16 px — не про красоту: при меньшем размере Safari на iPhone
     увеличивает страницу, как только палец попадает в поле, и вернуть
     масштаб обратно человек уже не может.

     max-width — про поля с заданной шириной в символах (в настройках
     их десятки): на 375 px такое поле вылезает за край и растягивает
     страницу вместе с собой. */
  input, select, textarea {
    font-size: 16px;
    max-width: 100%;
  }
  .btn { min-height: 2.75rem; }

  /* Модальные окна на телефоне — во весь экран: окно «поверх страницы»
     на 375 px и так занимает почти всё, а поля с отступами по краям
     отнимают у него последнее место. */
  .modal-overlay__content {
    max-width: none;
    width: 100%;
    max-height: 100%;
    height: 100%;
    border-radius: 0;
    margin: 0;
    padding: 1.25rem 1rem calc(1.25rem + env(safe-area-inset-bottom));
  }
  .modal-overlay { padding: 0; }
}

/* ==========================================================================
   Экраны онлайн-записи по дизайн-пакету (2026-08-30,
   design_handoff_fenhel_booking).

   Запись идёт четырьмя шагами: услуга → мастер → время → контакты. До
   этого каждый шаг был просто списком со ссылкой «← Назад» сверху, и
   человек не понимал ни сколько всего шагов, ни сколько осталось. Здесь
   появляются две вещи из дизайна: строка «ШАГ N ИЗ 4» над заголовком и
   постоянная нижняя панель с кнопкой возврата.

   Панель прижата к низу экрана намеренно: страницу записи открывают с
   телефона одной рукой, и низ экрана — единственное место, докуда палец
   дотягивается без перехвата. Ссылка «назад» сверху требовала дотянуться
   до противоположного угла.
   ========================================================================== */

.widget-step {
  display: block;
  /* 0.8rem, а не 0.72 (2026-09-18, аудит): 11,5 px заглавными с широкой
     разрядкой — нижняя граница читаемости, а это единственная строка,
     которая говорит клиенту, сколько шагов осталось. */
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--color-muted);
  margin: 0 0 0.35rem;
}

.widget-title { margin: 0 0 1rem; font-size: 1.5rem; line-height: 1.25; }

/* Прогресс — четыре отрезка, а не полоска с процентами: шагов ровно
   четыре, и «три из четырёх пройдено» человек считывает по заполненным
   отрезкам мгновенно, без чтения подписи. */
.widget-progress {
  display: flex;
  gap: 4px;
  margin: 0 0 1.1rem;
  list-style: none;
  padding: 0;
}
.widget-progress__part {
  height: 3px;
  flex: 1;
  border-radius: var(--ok-r-pill, 999px);
  background: var(--ok-disabled-bg, var(--color-border));
}
.widget-progress__part--done {
  background: var(--widget-accent, var(--color-accent));
}

/* Нижняя панель действий.

   Панель прижата к экрану (fixed), а содержимому страницы выдан отступ
   снизу ровно на её высоту. Отступ здесь не косметика: сначала панель
   была sticky, и на шаге контактов она накрыла собой строчку «Нажимая
   „Записаться“, вы соглашаетесь на обработку персональных данных» —
   человек видел кнопку и не видел, на что соглашается. Согласие,
   которое не показали, согласием не является, так что перекрывать
   содержимое панель не должна нигде и никогда.

   Панель во всю ширину окна, но кнопки в ней стоят ровно над колонкой с
   содержимым: слева обычный отступ, справа — всё остальное место. Иначе
   на большом экране кнопка «Записаться» уезжала на середину экрана, а
   форма оставалась слева, и они переставали читаться как одно целое. */
.widget-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 5;
  /* 24px — отступ колонки содержимого; правый padding досчитывает
     остаток до её правого края (640 − 24 = 616). */
  padding: 0.75rem max(24px, calc(100% - 616px)) 0.75rem 24px;
  /* Плюс запас под домашнюю полосу iPhone — без него кнопка
     оказывается ровно под ней и нажимается с третьего раза. */
  padding-bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px));
  background: var(--widget-bg, var(--color-bg));
  border-top: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* Запас под панель. Считается от её высоты: 52 пикселя кнопка, по 12 —
   отступы сверху и снизу, плюс безопасная зона iPhone. */
.widget-page:has(.widget-bar) .widget-content {
  padding-bottom: calc(92px + env(safe-area-inset-bottom, 0px));
}

/* На телефоне колонка содержимого прижата к краям сильнее — панель
   повторяет её отступы, чтобы кнопка и поля формы стояли по одной
   линии. */
@media (max-width: 768px) {
  .widget-bar { padding-left: 14px; padding-right: 14px; }
}

.widget-bar__back {
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-border);
  border-radius: var(--ok-r-pill, 999px);
  background: transparent;
  color: var(--color-text);
  font-size: 1.25rem;
  text-decoration: none;
  transition: border-color 0.15s, background-color 0.15s;
}
.widget-bar__back:focus-visible {
  border-color: var(--ok-border-hover, var(--color-accent));
  background: var(--color-surface-alt, transparent);
}

/* Главная кнопка занимает всю оставшуюся ширину: на шаге, где действие
   одно, ему и место всё. */
.widget-bar__main {
  flex: 1 1 auto;
  min-height: 52px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 1.25rem;
  border: 0;
  border-radius: var(--ok-r-pill, 999px);
  font-size: 1rem;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  background: var(--widget-accent, var(--ok-primary-bg, var(--color-accent)));
  color: var(--ok-primary-text, #fff);
}

/* Заблокированный вид — он же подсказка «что сделать».

   На шагах со списком выбор — это и есть переход дальше: человек жмёт
   карточку и оказывается на следующем шаге, лишнего подтверждения мы не
   просим. Но место главной кнопки внизу пустым не оставляем: там стоит
   «Выберите услугу» — тот же элемент, что на других шагах, только
   неактивный. Так низ экрана всегда отвечает на вопрос «что от меня
   хотят». */
.widget-bar__main--off,
.widget-bar__main[disabled] {
  background: var(--ok-disabled-bg, var(--color-border));
  color: var(--ok-disabled-text, var(--color-muted));
  cursor: default;
  pointer-events: none;
}

/* Экран филиалов (2026-08-30, просьба основателя). */
.branch-row--hidden { opacity: 0.55; }
.branch-badge {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.05rem 0.4rem;
  border-radius: var(--ok-r-pill, 999px);
  background: var(--color-surface-alt, var(--color-border));
  color: var(--color-muted);
  font-size: 0.72rem;
  vertical-align: middle;
}
.branch-actions { white-space: nowrap; }
/* Счётчики под названием — только на телефоне, на большом экране они
   стоят своими колонками. */
.branch-counts { display: none; }
@media (max-width: 768px) {
  .branch-col-count { display: none; }
  .branch-counts { display: block; }
}
.branch-edit > summary {
  cursor: pointer;
  color: var(--color-link);
  font-size: 0.85rem;
  padding: 0.2rem 0;
}
/* Поля правки не на всю ширину таблицы: строка адреса длиной в экран
   читается хуже короткой, а вводят сюда десяток символов.

   Класс .appointment-form на форме обязателен — он и делает её
   столбиком; без него подписи остаются inline и всё выстраивается в одну
   строку вперемешку с полями (см. комментарий у .appointment-form). */
.branch-form { max-width: 420px; margin-top: 0.5rem; }

/* ==========================================================================
   Галочки и переключатели-точки — единый вид (2026-08-30, просьба
   основателя: «поправь галочку везде»).

   В кабинете десятки форм, и почти в каждой стоит правило вида
   `.что-то input { width: 100%; min-height: 44px; padding: … }` — оно
   написано для текстовых полей, но задевает и `<input type="checkbox">`.
   Галочка растягивалась на всю ширину поля и на 44 пикселя в высоту: на
   экране бонусной программы это была большая красная плашка вместо
   аккуратной отметки.

   Цвет тоже был системный (`accent-color: auto`) — то есть свой у каждой
   операционной системы: у одного салона синяя галочка, у другого
   красная, и ни у кого не фирменная.

   Правило стоит в конце файла намеренно: специфичность у него такая же,
   как у правил вида `.класс input`, и при равной силе выигрывает то, что
   объявлено ниже. Селектор с `body` — на случай, если ниже когда-нибудь
   появится ещё одно такое правило.

   Скрытых галочек это не касается: у переключателя (.switch__input) и у
   ячейки графика свои размеры и opacity: 0, и они объявлены собственными
   правилами по этим классам. */
body input[type="checkbox"]:not(.switch__input),
body input[type="radio"] {
  width: auto;
  height: auto;
  min-height: 0;
  padding: 0;
  margin: 0;
  /* Не даём флекс-контейнерам растягивать и сжимать отметку: почти все
     формы кабинета — флекс, и именно это сплющивало её до нуля. */
  flex: 0 0 auto;
  /* Размер задан явно: браузерный по умолчанию мельче, а по галочке
     попадают пальцем с телефона. 20 px, а не 18 (2026-09-18, аудит
     мобильной версии): 18 — это меньше половины нормы касания, и в
     таблице клиентов, где галочки идут подряд через 60 px, промах
     означает отметку соседней строки. Больше 20 делать нельзя — в
     плотных формах отметка станет крупнее подписи рядом. */
  width: 20px;
  height: 20px;
  accent-color: var(--color-accent);
  cursor: pointer;
}

/* Внутри самой ячейки графика галочка спрятана — она там служебная,
   выбор рисуется заливкой ячейки. */
body .schedule-cell input[type="checkbox"] {
  width: 0;
  height: 0;
}

/* Удаление операции кассы (2026-08-30) — отдельной строкой под формой
   правки и приглушённо: это редкое действие и необратимое, оно не должно
   спорить за внимание с «Сохранить». */
.entry-delete { margin-top: 0.75rem; }
.entry-delete__button { color: var(--color-error-text); padding-left: 0; }

/* Настройки в две колонки: подпись и поле (2026-08-30, замечание
   основателя: «поля для ввода не выровнены относительно текста слева от
   них»).

   Сетка вместо строк-флексов. Подписи в таких блоках разной длины — от
   «Спящий: не был дней» до «Новый: заведён не раньше, дней назад», — и
   при выравнивании построчно каждое поле начиналось со своей вертикали.
   Сетка задаёт одну колонку под подписи на весь блок, и поля выстраиваются
   в столбик. */
.settings-grid {
  display: grid;
  /* minmax(0, max-content), а не просто max-content (2026-09-12, найдено
     сплошной проверкой вёрстки на ширине планшета).

     Колонка подписей тянулась по самой длинной надписи и не умела
     сжиматься: «Постоянный: сколько визитов подряд» распирала её до
     1200 пикселей. Поле ввода уезжало на координату 1255 при экране
     753 — то есть за край, и настроить сегменты на планшете было
     нельзя вовсе. Страница: было 1261, стало 753.

     minmax(0, …) разрешает колонке сжиматься ниже своего содержимого —
     подпись тогда переносится на вторую строку, что и нужно. */
  grid-template-columns: minmax(0, max-content) minmax(0, 16rem);
  align-items: center;
  gap: 0.6rem 1rem;
  max-width: 100%;
}
.settings-grid label { color: var(--color-muted); font-size: 0.9rem; margin: 0; }
.settings-grid input { width: 100%; }
/* Подсказки, ошибки и кнопка — во всю ширину сетки, а не в колонку поля. */
.settings-grid__wide { grid-column: 1 / -1; margin: 0.2rem 0 0; }
.settings-grid .btn { justify-self: start; }

/* На телефоне двух колонок нет: подпись над полем, иначе поле сжимается
   до нечитаемого. */
@media (max-width: 640px) {
  .settings-grid { grid-template-columns: 1fr; gap: 0.2rem; }
  .settings-grid label { margin-top: 0.6rem; }
}


/* Кнопка «Скопировать» рядом со ссылкой (2026-08-30). Подпись меняется на
   «Скопировано» — без этого не понять, сработало нажатие или нет. */
.copy-button { white-space: nowrap; padding-left: 0.5rem; }
.copy-button--done { color: var(--color-success-text); }


/* --- Витрина онлайн-записи: правки 2026-09-09 -----------------------------

   Сообщение салона в шапке, карточка услуги с описанием и фото,
   предупреждение у услуги, смайл и фраза на экране «Спасибо». Все — на
   фирменных цветах салона, а не на своих: витрина уже красится
   настройками компании, и вводить рядом второй набор цветов значило бы
   ломать её вид у тех, кто цвета настроил. */
.widget-notice {
  margin: 0; padding: 0.6rem 1rem;
  background: #fef3c7;
  color: var(--color-warn-on-chip);
  font-size: 0.95rem; text-align: center;
}
.widget-service-card {
  display: flex; gap: 1rem; align-items: flex-start;
  margin: 0 0 1rem;
}
.widget-service-card__photo {
  width: 96px; height: 96px; object-fit: cover; border-radius: 12px; flex: none;
}
.widget-service-card__text { margin: 0; font-size: 0.95rem; line-height: 1.5; }
.widget-service-notice {
  margin: 0 0 1rem; padding: 0.6rem 0.8rem; border-radius: 10px;
  background: #fef3c7;
  color: var(--color-warn-on-chip);
  font-size: 0.95rem;
}
.widget-success__emoji { margin: 0 0 0.25rem; font-size: 3rem; line-height: 1; text-align: center; }
.widget-success__note { margin: 0 0 1rem; text-align: center; }

@media (max-width: 480px) {
  .widget-service-card { gap: 0.75rem; }
  .widget-service-card__photo { width: 72px; height: 72px; }
}

/* Выбор услуг в форме акции (2026-09-09): свёрнутый список с прокруткой —
   в прайсе бывает под сотню строк, и развёрнутыми они заслоняют форму. */
.promo-services { margin: 0.5rem 0; }
.promo-services__list { max-height: 14rem; overflow-y: auto; padding: 0.25rem 0; }
.promo-services__list ul { list-style: none; margin: 0; padding: 0; }

/* Метка акции и накопительной скидки в списке услуг (2026-09-09). Акция —
   заметной плашкой у цены, накопительная скидка — строкой в подписи: она
   не «сейчас дешевле», а награда за постоянство, и обещать ею скидку
   этому визиту было бы обманом. */
.widget-list__promo {
  align-self: center; padding: 0.1rem 0.45rem; border-radius: 999px;
  background: #fef3c7; color: var(--color-warn-on-chip);
  font-size: 0.75rem; white-space: nowrap;
}
.widget-list__loyalty { color: var(--color-warn-on-chip); }

/* Выбор количества процедур и его показ на подтверждении (2026-09-09). */
.widget-chips--qty { align-items: center; }
.widget-chips__label { color: var(--widget-muted, #6b7280); font-size: 0.9rem; margin-right: 0.25rem; }
.widget-summary__qty { margin: 0.25rem 0 0; font-size: 0.95rem; }

/* Сторис витрины (2026-09-09): кружки над списком услуг и полноэкранная
   карточка по нажатию. Диалог — родной <dialog>, без библиотек: Escape и
   затемнение браузер делает сам. */
.widget-stories {
  display: flex; gap: 0.75rem; overflow-x: auto; padding: 0.25rem 0 0.75rem;
  scrollbar-width: none;
}
.widget-stories::-webkit-scrollbar { display: none; }
.widget-story {
  display: flex; flex-direction: column; align-items: center; gap: 0.35rem;
  background: none; border: 0; padding: 0; cursor: pointer; flex: none; width: 4.5rem;
}
.widget-story__circle {
  width: 4rem; height: 4rem; border-radius: 50%; object-fit: cover;
  border: 2px solid var(--widget-accent, #1b5953); padding: 2px;
}
.widget-story__title {
  font-size: 0.75rem; color: inherit; text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
.widget-story-dialog {
  border: 0; border-radius: 16px; padding: 0; max-width: 22rem; width: 92vw;
  background: var(--widget-bg, #fff); color: var(--widget-text, #111827);
}
.widget-story-dialog::backdrop { background: rgba(0, 0, 0, 0.7); }
.widget-story-dialog__image { display: block; width: 100%; max-height: 60vh; object-fit: cover; }
.widget-story-dialog__body { padding: 1rem; display: grid; gap: 0.75rem; }
.widget-story-dialog__title { margin: 0; font-size: 1.1rem; }
.widget-story-dialog__text { margin: 0; font-size: 0.95rem; line-height: 1.5; }
.widget-story-dialog__close-form { position: absolute; top: 0.5rem; right: 0.5rem; margin: 0; }
.widget-story-dialog__close {
  background: rgba(0, 0, 0, 0.5); color: #fff; border: 0; border-radius: 50%;
  width: 2rem; height: 2rem; font-size: 1.25rem; line-height: 1; cursor: pointer;
}
/* Миниатюра сторис в списке кабинета. */
.story-thumb { width: 48px; height: 48px; border-radius: 8px; object-fit: cover; }

/* Поиск на экранах абонементов и сертификатов (2026-09-09): поле и
   кнопка в одну строку, как у прочих фильтров в кабинете. */
.widget-search-form { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; }
.widget-search-form .widget-search { flex: 1 1 16rem; }

/* Неподвижная шапка длинных таблиц (2026-09-09, просьба салонов — тема
   1345 у Арники): в расчёте зарплаты колонок семь, и, прокрутив список
   мастеров, уже не понять, где «Товары», а где «Материалы».

   position: sticky у самих <th>, а не у <thead>: в таблицах его
   поддерживают именно ячейки. Фон обязателен — прозрачная шапка
   наезжала бы на строки. z-index — над содержимым, но ниже модальных
   окон и выпадающих списков. */
.client-table--sticky-head thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--color-surface);
  box-shadow: inset 0 -1px 0 var(--color-border);
}
/* На узком экране не действует, и это осознанно: там любая широкая
   таблица становится блоком с горизонтальной прокруткой (медиа-выборка
   `.content table` выше), а внутри блока прилипать не к чему. Расчёт зарплаты
   смотрят с компьютера, а на телефоне важнее не потерять колонки. */

/* --- Опасная зона платформы (2026-09-10) ------------------------------
   Экран окончательного удаления салона. Нарочито неприметная ссылка в
   карточке и нарочито громкое предупреждение на самом экране: путь сюда
   должен быть трудным, а последствия — очевидными. */
.platform-company__danger { margin-top: 3rem; opacity: 0.6; }
.danger-zone__warning {
  margin: 1rem 0 1.5rem;
  padding: 0.9rem 1.1rem;
  border-left: 4px solid var(--color-error);
  background: rgba(220, 38, 38, 0.08);
}
.danger-zone__table { max-width: 26rem; }
.danger-zone__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--color-border);
}
.danger-zone__name { min-width: 12rem; }
.danger-zone__form { max-width: 32rem; margin-bottom: 2rem; }
/* Отделяем удаление всего салона от частичных: между ними должно быть
   видимое расстояние, а не соседняя строка списка. */
.danger-zone__final { margin-top: 3rem; color: var(--color-error); }

/* --- Свободное время (2026-09-10) -------------------------------------
   Две половины: слева настройки, справа готовое объявление. На узком
   экране — одна колонка, настройки сверху: сначала выбирают, потом
   смотрят результат. */
.openings {
  display: grid;
  grid-template-columns: minmax(240px, 22rem) 1fr;
  gap: 1.5rem;
  align-items: start;
}
@media (max-width: 900px) {
  .openings { grid-template-columns: 1fr; }
}
.openings__field { margin-bottom: 0.9rem; }
.openings__field select,
.openings__field input[type="date"] { width: 100%; }
.openings__check { display: flex; align-items: center; gap: 0.5rem; }
.openings__text {
  width: 100%;
  font-family: inherit;
  font-size: 0.95rem;
  line-height: 1.5;
}
.openings__actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0.5rem 0 1.5rem;
}
/* Картинка показывается уменьшенной: она квадрат 1080×1080, а на экране
   должна помещаться рядом с текстом. */
.openings__card svg {
  width: 100%;
  max-width: 22rem;
  height: auto;
  border: 1px solid var(--color-border);
  border-radius: 8px;
}
.openings__link {
  display: inline-block;
  padding: 0.35rem 0.6rem;
  background: var(--color-surface-alt);
  border-radius: 6px;
  word-break: break-all;
}
/* Сколько окошек скрыто прореживанием — тот, кто публикует, обязан это
   видеть, иначе сам поверит, что у мастера два окна. */
.openings__hidden {
  padding: 0.6rem 0.9rem;
  border-left: 3px solid var(--color-accent);
  background: var(--color-surface-alt);
  border-radius: 6px;
}

/* --- Полосы в отчёте «После кого не возвращаются» (2026-09-10) ---------
   Тот же приём с переменной --bar-pct, что у аналитики выручки, только
   внутри ячейки таблицы: число слева, полоса справа. Число остаётся
   главным — полоса лишь показывает соотношение. */
.churn-cell {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  white-space: nowrap;
}
.churn-cell__value { min-width: 2.5rem; }
.churn-bar {
  display: block;
  flex: 1;
  min-width: 3rem;
  max-width: 12rem;
  height: 0.55rem;
  background: var(--color-surface-alt);
  border-radius: 4px;
  overflow: hidden;
}
.churn-bar__fill {
  display: block;
  height: 100%;
  width: calc(var(--bar-pct) * 1%);
  min-width: 2px;
  background: var(--color-accent);
  border-radius: 4px;
}
/* Доля — своим цветом: две одинаковые полосы в соседних колонках
   читались бы как продолжение одной. */
.churn-bar__fill--share { background: var(--color-success-text); }

/* --- Виды сообщения в шапке онлайн-записи (2026-09-10, темы 372 и 690) -
   Салоны просили «крупнее и ярче — его практически не видно» и окно на
   весь экран. Готовые виды вместо свободного выбора цвета: свободный
   выбор — это жёлтый текст на белом фоне у первого же салона. */
/* Витрина внутри чужой рамки (2026-09-11): предупреждение должно быть
   заметнее содержимого под ним — иначе человек заполнит форму впустую. */
.widget-framed {
  margin: 0.75rem;
  padding: 0.75rem 1rem;
  border-radius: 10px;
  background: var(--color-accent-soft);
  border: 1px solid var(--color-link);
  font-weight: 600;
}
.widget-framed a { display: block; margin-top: 0.35rem; }

.widget-notice--loud {
  padding: 0.9rem 1rem;
  background: #dc2626;
  color: #fff;
  font-size: 1.1rem;
  font-weight: 600;
}
.widget-notice-modal {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: rgba(0, 0, 0, 0.55);
}
.widget-notice-modal__box {
  max-width: 26rem;
  width: 100%;
  padding: 1.5rem;
  border-radius: 12px;
  background: var(--color-surface, #fff);
  text-align: center;
}
.widget-notice-modal__text {
  margin: 0 0 1.25rem;
  font-size: 1.05rem;
  line-height: 1.5;
}

/* --- Баллы в кабинете клиента и подсказка при записи (2026-09-10) ------
   По разбору отзывов о приложениях конкурентов: клиент не видел ни
   остатка, ни срока сгорания, и баллы «копились впустую». */
.cabinet__balance { margin: 0.25rem 0; font-size: 1.6rem; }
.cabinet__expires { margin: 0 0 0.5rem; color: var(--color-warn-text); }
.cabinet__history { margin-top: 0.75rem; }
.cabinet__history summary { cursor: pointer; color: var(--color-muted); }
.widget-summary__bonus {
  margin: 0.75rem 0 0;
  padding: 0.6rem 0.9rem;
  border-radius: 8px;
  background: var(--color-surface-alt);
}

/* --- Чек-лист смены (2026-09-10) --------------------------------------
   Два списка рядом: открытие и закрытие. Строка — форма с одной
   кнопкой-галочкой: нажал, отметилось, видно кто и во сколько. */
.shift-lists {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
}
.shift-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--color-border);
}
.shift-row__toggle {
  flex: none;
  width: 1.8rem;
  height: 1.8rem;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: var(--color-surface-alt);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
}
.shift-row--done .shift-row__toggle {
  border-color: transparent;
  background: var(--color-success-text);
  color: #fff;
}
.shift-row__text { flex: 1; }
.shift-row--done .shift-row__text { color: var(--color-muted); }
.shift-row__who { color: var(--color-muted); font-size: 0.8rem; white-space: nowrap; }
.shift__branch { margin-left: auto; }


/* Правка пунктов продающей страницы в панели платформы (2026-09-11):
   цифра, заголовок и порядок в одну строку — так список читается сверху
   вниз как таблица, а не как десять одинаковых форм. */
.landing-item {
  border-top: 1px solid var(--color-border);
  padding-top: 1rem;
  margin-top: 1rem;
}
.landing-item__row {
  display: grid;
  grid-template-columns: 7rem 1fr 5rem;
  gap: 0.5rem;
}
.landing-item__actions { display: flex; gap: 0.5rem; margin-top: 0.5rem; }
@media (max-width: 700px) {
  .landing-item__row { grid-template-columns: 1fr; }
}

/* Страница сравнения: чем отличаемся и чего у нас нет (2026-09-11).
   Честный список пробелов намеренно спокойный, без красного: это не
   предупреждение об опасности, а ответ на вопрос, который салон задаст
   всё равно. */
.compare__unique {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1rem;
  margin-top: 1.25rem;
}
.compare__unique-item {
  border: 1px solid var(--color-border);
  border-radius: 12px;
  padding: 1.1rem;
  background: var(--color-surface);
}
.compare__unique-item h3 { margin: 0 0 0.4rem; font-size: 1.02rem; }
.compare__unique-item p { margin: 0; color: var(--color-muted); line-height: 1.5; }
.compare__gaps { margin-top: 1rem; }
.compare__gaps dt { font-weight: 600; margin-top: 0.9rem; }
.compare__gaps dd { margin: 0.2rem 0 0; color: var(--color-muted); line-height: 1.5; }

/* --- Публичная база знаний, support.fenhel.com (2026-09-11) ------------
   Оболочка та же, что у лендинга (класс landing на body), поэтому здесь
   только своё: поиск, оглавление разделами и тело статьи.

   Колонка текста узкая намеренно: справку читают, а не просматривают, и
   строка в сто символов утомляет глаз на второй минуте. */
.support-hero { padding: 3rem 1.5rem 1.5rem; text-align: center; }
.support-search {
  display: flex;
  gap: 0.5rem;
  max-width: 34rem;
  margin: 1.5rem auto 0;
}
.support-search input { flex: 1 1 auto; min-width: 0; }
.support-sections {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1.5rem;
  max-width: 60rem;
  margin: 0 auto;
  padding: 1.5rem;
}
.support-section h2 { font-size: 1.05rem; margin: 0 0 0.6rem; }
.support-list { list-style: none; margin: 0; padding: 0; }
.support-list li { margin-bottom: 0.5rem; line-height: 1.45; }
.support-list__section { color: var(--color-muted); font-size: 0.85rem; margin-left: 0.4rem; }
.support-results { max-width: 60rem; margin: 0 auto; padding: 0 1.5rem; }
.support-contacts {
  max-width: 60rem;
  margin: 1rem auto 3rem;
  padding: 1.5rem;
  border-top: 1px solid var(--color-border);
}
.support-article { max-width: 42rem; margin: 0 auto; padding: 2rem 1.5rem 3rem; }
.support-article__crumbs { color: var(--color-muted); font-size: 0.9rem; }
.support-article__body { line-height: 1.65; }
.support-article__body h2 { margin-top: 1.8rem; font-size: 1.15rem; }
.support-article__body li { margin-bottom: 0.4rem; }
.support-neighbours {
  margin-top: 2.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--color-border);
}
.support-neighbours h2 { font-size: 1.02rem; }
.support-article__back { margin-top: 2rem; }


/* Появление разделов при прокрутке (2026-09-18).

   Через animation-timeline: view() — это встроенная в браузер
   прокруточная анимация, без единой строки JS и без библиотек. Браузер
   считает её сам, в своём потоке; обработчик на scroll в этом месте
   означал бы пересчёт на каждый кадр ради украшения.

   Строго внутри @supports: где механизм не поддерживается (Safari
   постарше, Firefox по умолчанию), правило не применяется вовсе, и
   разделы просто видны. Скрыть содержимое и не показать его обратно —
   худшее, что может сделать анимация появления; здесь это невозможно
   по устройству.

   animation-range заканчивается на 40% входа: раздел проявляется, пока
   поднимается к середине экрана, а не когда уже прочитан. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .lp-price,
    .lp-client,
    .lp-fact,
    .lp-gallery figure,
    .lp-features__pair,
    .lp-steps li {
      animation: landing-section-in 0.8s linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 40%;
    }
  }
}

@keyframes landing-section-in {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: none; }
}

/* --- Закреплённые разделы меню (2026-09-13) ----------------------------
   Просьба основателя: «сделай быстрые команды в панели меню». В меню
   шестьдесят шесть пунктов, а человек ходит каждый день в три-четыре. */

.sidebar__favorites {
  padding-bottom: 0.5rem;
  margin-bottom: 0.5rem;
  border-bottom: 1px solid rgba(246, 241, 232, 0.14);
}

/* Обёртка появляется скриптом: она нужна только затем, чтобы звёздочка
   встала поверх правого края пункта. */
.sidebar__item { position: relative; display: block; }

/* Звёздочка молчит, пока на пункт не навели: шестьдесят шесть значков
   в столбик — это шум, из-за которого перестают читаться сами названия.
   Закреплённые показываются всегда: по ним видно, что уже в избранном. */
.sidebar__pin {
  position: absolute;
  top: 50%;
  right: 0.4rem;
  transform: translateY(-50%);
  padding: 0.15rem 0.3rem;
  border: none;
  border-radius: 4px;
  background: none;
  color: var(--ok-on-dark-muted);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
}
.sidebar__item:hover .sidebar__pin,
.sidebar__pin:focus-visible { opacity: 1; }
.sidebar__pin--on { opacity: 1; color: var(--ok-sand); }
.sidebar__pin:hover { color: var(--ok-on-dark); }

/* Свёрнутое меню: звёздочке негде встать рядом с одной иконкой, и она
   перекрыла бы её. Там закрепляют из развёрнутого вида.

   Селектор именно html.sidebar-collapsed: класс живёт на <html>
   (base.html ставит его через documentElement.classList). Раньше здесь
   стояло `.sidebar--collapsed`, такого класса в разметке нет, и правило
   молчало — звёздочки висели поверх иконок в свёрнутой панели
   (2026-09-14, замечено основателем по скриншоту). */
html.sidebar-collapsed .sidebar__pin { display: none; }

/* На телефоне звёздочка видна всегда (2026-09-14, найдено сплошной
   проверкой мобильной вёрстки).

   Она появлялась при наведении мышью — а наведения на телефоне не
   бывает: закрепить раздел пальцем было нельзя вовсе. Раз мастера будут
   работать в основном с телефона, функция там и нужнее всего.

   Приглушена, чтобы не спорить с названиями пунктов, и увеличена до
   размера под палец: 18 пикселей — промах, 44 — попадание. */
@media (max-width: 768px) {
  .sidebar__pin {
    opacity: 0.5;
    right: 0.1rem;
    /* 44 пикселя в обе стороны — размер, по которому палец попадает. */
    padding: 0.85rem 0.8rem;
    font-size: 1.05rem;
  }
  .sidebar__pin--on { opacity: 1; }
}

/* Ссылка на настройку быстрых разделов (2026-09-13). Тихая: её
   открывают раз в месяц, а сами закладки видят каждый день. */
.sidebar__favorites-settings {
  display: block;
  padding: 0.2rem 0.9rem 0.3rem;
  color: var(--ok-on-dark-muted);
  font-size: 0.75rem;
  text-decoration: none;
}
.sidebar__favorites-settings:hover { color: var(--ok-on-dark); text-decoration: underline; }
html.sidebar-collapsed .sidebar__favorites-settings { display: none; }

/* Стрелки порядка (2026-09-14) — работают там, где перетаскивание не
   работает: пальцем и с клавиатуры. ⠿ остаётся для мыши. */
.draggable-row__handle { white-space: nowrap; }
.row-move { display: inline-flex; gap: 0.15rem; margin-left: 0.35rem; }
.row-move__btn {
  width: 1.9rem;
  height: 1.9rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: none;
  color: var(--color-muted);
  font-size: 0.8rem;
  line-height: 1;
  cursor: pointer;
}
.row-move__btn:hover { color: var(--color-text); background: var(--color-surface-alt); }
@media (max-width: 768px) {
  /* Палец: 44 пикселя — попадание, меньше — промах. Сам ⠿ на телефоне
     бесполезен, и место занимать ему незачем. */
  .draggable-row__grip { display: none; }
  .row-move__btn { width: 2.75rem; height: 2.75rem; font-size: 1rem; }
}

/* Минус, убирающий раздел из быстрых (2026-09-13, по образцу «Быстрых
   действий» Точки). Круглый и приглушённо-красный: это единственное
   разрушительное действие на экране, и оно должно читаться как таковое,
   не крича при этом на весь список. */
.quick-remove { width: 3rem; text-align: right; }
.quick-remove form { margin: 0; }

/* Кнопка «Сохранить» под списком быстрых команд (2026-09-13): без
   отступа она прижимается к последней строке таблицы вплотную и
   читается как её часть. */
.quick-actions { margin-top: 1rem; }
.quick-remove__btn {
  width: 1.6rem;
  height: 1.6rem;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: none;
  color: var(--color-error-text);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}
.quick-remove__btn:hover {
  border-color: var(--color-error-text);
  background: var(--color-error-bg, rgba(185, 28, 28, 0.08));
}

/* Прогноз выручки на конец месяца (2026-09-14, просьба основателя).

   Одно главное число крупно: владелец заходит сюда с одним вопросом —
   «выйдем на свои деньги или нет», и ответ не должен искаться глазами
   среди карточек. Раскладка на слагаемые идёт ниже отдельным блоком. */
.forecast-hero {
  display: flex; flex-direction: column; gap: 0.35rem;
  padding: 1.25rem 1.5rem; margin-bottom: 1.25rem;
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border);
  border-radius: 12px;
}
.forecast-hero__label { font-size: 0.85rem; color: var(--color-muted); }
.forecast-hero__value { font-size: 2rem; font-weight: 600; line-height: 1.1; }
.forecast-hero__hint { font-size: 0.8rem; color: var(--color-muted); }

/* Объяснение расчёта словами — часть экрана, а не сноска: числом,
   которого владелец не понимает, он пользоваться не станет. */
.forecast-explain {
  padding: 1rem 1.25rem; margin: 1rem 0;
  border-left: 3px solid var(--color-accent);
  background: var(--color-surface-alt);
  border-radius: 0 8px 8px 0;
}
.forecast-explain h2 { font-size: 0.95rem; margin: 0 0 0.4rem; }
.forecast-explain p { margin: 0; font-size: 0.9rem; line-height: 1.5; }

/* Будущие дни на графике: приглушённая заливка плюс диагональная
   штриховка. Штриховка здесь не украшение — она оставляет разницу
   «факт / ещё только записано» видимой при чёрно-белой печати и при
   дальтонизме, когда цвет не помогает. */
.finance-chart__bar--plan {
  background-color: var(--color-accent-soft);
  background-image: repeating-linear-gradient(
    45deg,
    var(--color-accent) 0, var(--color-accent) 2px,
    transparent 2px, transparent 6px
  );
}

/* Предпочтения клиента в карточке записи (2026-09-14) — список без
   маркеров: это не перечисление дел, а справка в две-три строки, и
   точки перед ними только рябят. */
.client-prefs { list-style: none; margin: 0; padding: 0; font-size: 0.9rem; }
.client-prefs li { margin: 0.1rem 0; }
.client-prefs__label { color: var(--color-muted); }

/* Цена привлечённого клиента (2026-09-14): окупился канал или нет.
   Цвет здесь подсказка, а не единственный носитель смысла — рядом в той
   же строке стоит «На клиента», и сравнить два числа можно глазами. */
.cac--good { color: var(--color-success-text); }
.cac--bad { color: var(--color-error-text); }

/* Денежная ячейка таблицы (2026-09-14, замечание основателя по
   «Начислениям»): сумма и знак рубля не должны разрываться переносом —
   «20000» на одной строке и «₽» на другой читаются как две величины.
   Класс общий: та же беда есть у любой узкой колонки с деньгами. */
.money-cell { white-space: nowrap; }

/* Кнопки под таблицей (2026-09-14, замечание основателя по «Часам работы
   салона»): без отступа кнопка «Сохранить» подпирала последнюю строку и
   читалась как её продолжение.

   Отдельная обёртка, а не отступ самой кнопке: `.btn--inline` снимает
   верхний отступ намеренно — он нужен кнопке в ряду с другими, а не под
   таблицей. */
.table-actions { margin-top: 1.25rem; }

/* Две кнопки под карточкой сотрудника (2026-09-14): «Сохранить» и
   «Сохранить и закрыть». В ряд и с отступом от последнего поля. */
.staff-form__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.25rem;
}

/* Поле адреса ссылки и кнопка «Сохранить» — одной строкой (2026-09-14):
   правка и результат стоят рядом, и после сохранения новая ссылка видна
   сразу под формой, а не на экран выше. */
.settings__slug-form { margin-bottom: 1rem; }
/* Кнопка стоит РЯДОМ с полем, а не внутри его рамки: у поля общая рамка
   с приставкой адреса и overflow: hidden — кнопка внутри читалась бы
   частью поля ввода. */
.settings__slug-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.settings__slug-row .settings__slug-field { flex: 1; min-width: 16rem; }
.settings__slug-row .btn { margin-top: 0; }


/* Подсветка под курсором — только там, где курсор есть (2026-09-18,
   навык mobile-native).

   У касания ховера нет, и браузер его подделывает: первое нажатие
   вешает :hover и оставляет его, пока человек не ткнёт в другое место.
   В списке услуг это выглядело так, будто выбранная строка осталась
   выделенной, — и клиент, вернувшись назад, видел «выбранную» услугу,
   которую не выбирал.

   Подсветка нажатия у касания остаётся своя, через :active. Правила с
   :focus-visible вынесены из этой пары намеренно: клавиатура работает
   на любом устройстве. */
@media (hover: hover) and (pointer: fine) {
  .widget-chip:hover { border-color: var(--color-link); background: var(--color-accent-soft); }
  .widget-shortcut:hover {
    border-color: var(--color-link);
    background: var(--color-accent-soft);
  }
  .widget-category__summary:hover { background: var(--color-surface-alt); }
  .widget-category .widget-list__item:hover {
    border-color: var(--color-link);
    background: var(--color-accent-soft);
  }
  .widget-header__cabinet:hover {
    border-color: var(--color-link);
    background: var(--color-accent-soft);
  }
  .widget-session:hover {
    border-color: var(--color-link);
    background: var(--color-accent-soft);
  }
  .widget-list__item:hover {
    border-color: var(--color-link);
    background: var(--color-accent-soft);
  }
  .widget-slots__item:hover {
    border-color: var(--color-link);
    background: var(--color-accent-soft);
  }
  .widget-bar__back:hover {
    border-color: var(--ok-border-hover, var(--color-accent));
    background: var(--color-surface-alt, transparent);
  }
}

/* Отклик на нажатие (2026-09-18, навык mobile-native).

   Подсветку под курсором мы только что закрыли от касания — значит у
   пальца не осталось ответа вовсе. Родная кнопка телефона отвечает в тот
   миг, когда палец её коснулся; веб-кнопка, меняющаяся только по click,
   отвечает, когда палец УЖЕ ушёл, и это читается как задержка, даже
   когда её нет.

   Сжатие на три процента и 120 мс — столько же, сколько у подсветки
   рядом: заметно пальцу и не заметно глазу как анимация. */
.widget-list__item,
.widget-session,
.widget-slots__item,
.widget-chip,
.widget-shortcut,
.widget-bar__main,
.widget-category__summary {
  transition: border-color 0.15s, background-color 0.15s, transform 0.12s ease-out;
}
.widget-list__item:active,
.widget-session:active,
.widget-slots__item:active,
.widget-chip:active,
.widget-shortcut:active,
.widget-category__summary:active {
  transform: scale(0.985);
  background: var(--color-accent-soft);
}
/* Главная кнопка внизу жмётся сильнее: она большая, и на ней мелкое
   сжатие незаметно. Неактивная не отвечает — ей нечего подтверждать. */
.widget-bar__main:not(.widget-bar__main--off):not([disabled]):active {
  transform: scale(0.97);
}

/* Прокрутка внутри ленты не тянет за собой страницу (2026-09-18).

   Лента дней в выборе времени и полосы разделов в кабинете листаются
   вбок. Докрутив до края, человек продолжал движение — и вместе с лентой
   ехала вся страница, а на Android сверх того срабатывало обновление
   страницы вытягиванием. `contain` оставляет ленте её собственную
   отдачу и не пускает движение дальше. */
.widget-chips,
.page-head__actions,
.client-list__categories,
.table-scroll,
.client-table-wrap {
  overscroll-behavior-x: contain;
}

/* --- Выбор услуг для варианта уведомления (2026-09-18) ------------------
   Просьба основателя: «у одного салона 140 услуг, это 140 строчек,
   приходится много листать». Категории + поиск + полка отмеченного.
   Оформление намеренно повторяет вкладку услуг мастера: там та же беда
   решена так же, и два экрана про одно и то же должны выглядеть
   одинаково. */

.svc-choice { margin: 0.5rem 0 1rem; }
.svc-choice__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
  margin-bottom: 0.6rem;
}
.svc-choice__count {
  margin: 0;
  font-size: 0.85rem;
  color: var(--color-muted);
}
/* Полка отмеченного: что выбрано, видно не открывая категории. */
.svc-choice__chosen {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 0.7rem;
}
.svc-choice__chip {
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-accent-soft);
  color: inherit;
  padding: 0.2rem 0.6rem;
  font-size: 0.8rem;
  cursor: pointer;
  /* Кнопка, по которой будут попадать пальцем на телефоне. */
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.svc-choice__chip:active { transform: scale(0.96); }

.svc-choice__group {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  margin-bottom: 0.6rem;
  padding: 0.5rem 0.75rem;
}
.svc-choice__group > summary {
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
}
.svc-choice__group-name { font-weight: 600; }
.svc-choice__group-count { font-size: 0.8rem; color: var(--color-muted); }
.svc-choice__all {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0.6rem 0 0.4rem;
  font-size: 0.85rem;
}
.svc-choice__item { display: flex; gap: 0.5rem; align-items: flex-start; }
/* Метка «в варианте „Брови"»: услуга уже занята соседним текстом.
   Приглушённая и с новой строки — это подсказка, а не запрет. */
.svc-choice__taken {
  display: block;
  font-size: 0.78rem;
  color: var(--color-muted);
}
.svc-choice__empty { font-size: 0.9rem; color: var(--color-muted); }
/* «и ещё 22» на полке отмеченного: полка остаётся сводкой, а не вторым
   длинным списком (2026-09-18, замерено на телефоне — тридцать услуг
   занимали целый экран). */
.svc-choice__more {
  border: 0;
  background: none;
  color: var(--color-link);
  font-size: 0.8rem;
  padding: 0.2rem 0.3rem;
  cursor: pointer;
}

/* --- Полоса демо-салона (2026-09-19) -----------------------------------
   Гость пришёл с продающей страницы смотреть продукт. Полоса держит в
   голове две вещи: система настоящая и менять в ней ничего нельзя.
   Цвет акцентный, а не красный: это не ошибка и не предупреждение, а
   рамка происходящего. */
.demo-notice {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 0.75rem;
  margin: 0 0 0.75rem;
  padding: 0.55rem 0.9rem;
  border-radius: 8px;
  background: var(--color-accent-soft);
  font-size: 0.9rem;
}
.demo-notice a { white-space: nowrap; }

/* === Продающая страница, редизайн 2026-09-19 ===========================

   Префикс .lp вместо прежнего .landing__: старые правила остаются жить у
   страницы сравнения, которая на новую композицию не переезжала.

   Регуляторы, согласованные с основателем: асимметрия 6, движение 4,
   плотность 4. Плотность подняли с 3: страница уже была 8,5 экрана на
   телефоне, а её читают днём между клиентами. Разреженность здесь не
   роскошь, а лишние полтора экрана листания.

   Ни одного правила без префикса .lp или .landing: кабинет и продающая
   страница живут в одном файле стилей, и протечка в любую сторону
   ломает то, чего автор правки не открывал.

   Шрифт Onest вариативный, веса 400-600 настоящие. Нигде не ставим 700:
   браузер рисует его синтетически, раздувая контуры, и на крупном кегле
   это видно. */

/* Продающая страница шире прочих: у неё первый экран с подложкой во всю
   ширину окна. Ограничение ставит не тело страницы, а сами разделы. */
body.landing--wide {
  max-width: none;
  padding-left: 0;
  padding-right: 0;
}

.lp { margin: 0 auto; }

/* КАЖДЫЙ РАЗДЕЛ ЦЕНТРИРУЕТ СЕБЯ САМ, отступом по бокам (2026-09-19).

   Прежде первый экран растягивался на всю ширину отрицательным полем
   `calc(50% - 50vw)`. Это известная ловушка: 50vw считает вместе с
   полосой прокрутки, а 50% — без неё, и страница уезжала вбок на
   ширину полосы. Лечится это `overflow: clip`, но лечение оказалось
   хуже болезни: headless-браузер, которым снимаются картинки для
   страницы, рисовал такой блок со сдвигом — замеры говорили «всё на
   месте», а на снимке заголовок был обрезан.

   Теперь никаких vw и отрицательных полей: раздел занимает всю ширину,
   а содержимое держится в 1120 пикселях за счёт бокового отступа. Он же
   учитывает вырез на айфоне в альбомной ориентации — env() работает,
   потому что в head стоит viewport-fit=cover. */
.lp > section {
  padding: clamp(3rem, 6vw, 4.5rem)
           max(1.25rem, env(safe-area-inset-right), calc(50% - 560px))
           clamp(3rem, 6vw, 4.5rem)
           max(1.25rem, env(safe-area-inset-left), calc(50% - 560px));
}
.lp h2 {
  margin: 0 0 1.25rem;
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
}
.lp h3 {
  margin: 0 0 0.4rem;
  font-size: 1.05rem;
  font-weight: 600;
}
.lp p { margin: 0 0 0.75rem; }
.lp p:last-child { margin-bottom: 0; }

/* Общий вид снимков продукта: одно окно, одна рамка, одна тень.
   Тень тёплая, в тон фону страницы: чёрная на песочном выглядит
   грязной. */
.lp img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: var(--ok-r-lg);
  border: 1px solid var(--color-border);
  box-shadow: 0 24px 60px -28px rgba(78, 70, 54, 0.35);
}

/* --- Первый экран ------------------------------------------------------
   Асимметричный разворот 46/54 в пользу продукта: половина на половину
   читается как таблица, а не как композиция. */
/* Селектор из двух классов намеренно: у общего правила `.lp > section`
   специфичность выше, чем у одного класса, и оно обнуляло боковые
   отступы первого экрана — текст прижимался к самому краю телефона.
   Поймано замером, глазами это читается как «так задумано». */
.lp .lp-hero {
  display: grid;
  grid-template-columns: minmax(0, 46fr) minmax(0, 54fr);
  align-items: center;
  gap: clamp(1.75rem, 4vw, 3.5rem);
  /* Подложка доходит до краёв окна: фон первого экрана, ограниченный
     колонкой, выглядит вставленным блоком, а не первым экраном.
     Содержимое при этом держится в той же колонке, что и остальные
     разделы, — за счёт бокового отступа выше. */
  padding-top: clamp(2rem, 5vw, 3.5rem);
  padding-bottom: clamp(2rem, 5vw, 3.5rem);
  background: var(--color-surface-alt);
  /* Обрезаем выступающий за правый край снимок. */
  overflow: hidden;
}
.lp-hero__title {
  margin: 0 0 1rem;
  /* Браузер сам выравнивает строки заголовка по длине: без этого
     последняя строка часто остаётся из одного слова. Работает вместе с
     фильтром «без_висячих» — тот запрещает разрыв после коротких слов,
     этот делает строки соразмерными. */
  text-wrap: balance;
  /* Планка по методике: заголовок не длиннее двух строк на компьютере.
     Верхняя граница 3,2 рем подобрана под самый длинный из текстов,
     которые основатель правит в панели. */
  font-size: clamp(1.9rem, 3.9vw, 3.1rem);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.03em;
}
.lp-hero__lead {
  max-width: 34ch;
  font-size: clamp(1rem, 1.3vw, 1.15rem);
  line-height: 1.55;
  color: var(--color-muted);
}
.lp-hero__price {
  margin: 1.5rem 0 0;
  max-width: 30ch;
  color: var(--color-muted);
}
.lp-hero__price b {
  font-size: 1.75rem;
  font-weight: 600;
  color: var(--color-text);
}
.lp-hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.5rem;
  margin-top: 1.75rem;
}
/* width: auto перебивает .btn--primary с её шириной 100%: та задумана
   для кнопок в узких формах. Текст в одну строку при любой ширине —
   перенос на вторую строку ломает кнопку. */
.lp-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  min-height: 52px;
  margin-top: 0;
  padding: 0 1.9rem;
  border-radius: var(--ok-r-sm);
  font-size: 1.05rem;
  white-space: nowrap;
  text-decoration: none;
  /* Длинное нажатие на ссылку-кнопку выделяло бы её подпись и открывало
     меню «скопировать»: у настоящей кнопки такого не бывает. Правило
     проекта покрывает button и [role=button], а это ссылка. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.lp-hero__second {
  font-size: 0.95rem;
  /* Не меньше подушечки пальца: 24 px высоты текста мало, промахи по
     второй кнопке уводят человека мимо заявки. Отступ съедает разницу,
     а не увеличивает буквы. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  transition: transform 160ms var(--ok-ease-out);
}
/* Отклик на нажатие и у второй ссылки: палец должен получать ответ от
   всего, что нажимается, а не только от главной кнопки. */
.lp-hero__second:active { transform: scale(0.97); }

.lp-hero__shot { min-width: 0; }
/* Снимок шире своей колонки и уходит за край экрана: журнал салона шире
   одного взгляда, и обрез читается как продолжение, а не как ошибка. */
.lp-hero__shot img { width: 124%; max-width: none; }

/* --- Цена целиком ------------------------------------------------------
   Первая боль владельца: «уже есть система, но дорогая, и за каждый
   сервис доплата». Одной широкой плашкой, а не карточками: сумма
   читается как ответ, а не как пункт из списка достоинств. */
.lp-price {
  display: grid;
  grid-template-columns: minmax(0, 38fr) minmax(0, 62fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: start;
}
.lp-price__figure b {
  display: block;
  font-size: clamp(2.6rem, 6vw, 4rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.03em;
}
.lp-price__figure span {
  display: block;
  margin-top: 0.6rem;
  max-width: 22ch;
  color: var(--color-muted);
}
.lp-price__included h2 { margin-bottom: 0.9rem; font-size: 1.15rem; }
.lp-price__included ul {
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0.4rem 1.5rem;
}
.lp-price__included li {
  padding-left: 1.4rem;
  position: relative;
}
/* Галочка псевдоэлементом, а не картинкой и не эмодзи: цвет следует за
   темой, вес не зависит от шрифта эмодзи в системе. */
.lp-price__included li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 0.5rem;
  height: 0.28rem;
  border-left: 2px solid var(--color-link);
  border-bottom: 2px solid var(--color-link);
  transform: rotate(-45deg);
}
.lp-price__included p { color: var(--color-muted); font-size: 0.95rem; }

/* --- Клиент записывается сам -------------------------------------------
   Снимок телефона слева, текст справа. Единственный раз на странице,
   когда картинка стоит слева: дальше разворот не повторяется. */
.lp-client {
  display: grid;
  grid-template-columns: minmax(0, 34fr) minmax(0, 66fr);
  gap: clamp(1.5rem, 4vw, 3.5rem);
  align-items: center;
}
.lp-client__shot img { margin: 0 auto; max-width: 320px; }
.lp-client__text p { color: var(--color-muted); max-width: 46ch; }
.lp-client__note {
  margin-top: 1rem;
  padding: 0.7rem 1rem;
  border-radius: var(--ok-r-sm);
  background: var(--color-accent-soft);
  color: var(--color-text) !important;
  font-size: 0.95rem;
}

/* --- Чем отличаемся ----------------------------------------------------
   Доводы с цифрой — строками во всю ширину: цифра и есть то, что читают
   вместо текста. Остальные ниже в две колонки, вторым голосом. */
.lp-diff__intro {
  max-width: 52ch;
  margin-bottom: 2rem;
  color: var(--color-muted);
}
.lp-diff__facts { display: grid; gap: 1.25rem; }
.lp-fact {
  display: grid;
  grid-template-columns: minmax(7rem, 10rem) minmax(0, 1fr);
  gap: 1rem 1.5rem;
  align-items: baseline;
  padding-top: 1.25rem;
  border-top: 1px solid var(--color-border);
}
.lp-fact__figure {
  font-size: clamp(1.6rem, 3vw, 2.2rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  white-space: nowrap;
}
.lp-fact__body p { margin: 0; color: var(--color-muted); max-width: 60ch; }

.lp-diff__rest {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1.75rem 2.5rem;
  margin-top: 2.5rem;
}
.lp-diff__rest p { margin: 0; color: var(--color-muted); }
.lp-diff__link { margin-top: 2rem; }

/* --- Что умеет ---------------------------------------------------------
   Два снимка рядом, под ними шесть пунктов парами. Пары, а не ровная
   сетка из шести одинаковых ячеек: ровная сетка читается как таблица. */
.lp-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: 1.5rem;
  margin-bottom: 2.5rem;
}
.lp-gallery figure { margin: 0; }
.lp-gallery figcaption {
  margin-top: 0.6rem;
  font-size: 0.9rem;
  color: var(--color-muted);
}
.lp-features__groups { display: grid; gap: 1.75rem; }
.lp-features__pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1.5rem 2.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--color-border);
}
.lp-features__pair p { margin: 0; color: var(--color-muted); }

/* --- Как перейти -------------------------------------------------------
   Шаги лентой: счёт остаётся (это правда последовательность), цифра
   вынесена и набрана крупно, чтобы список читался как путь. */
.lp-steps ol {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.5rem 2rem;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: шаг;
}
.lp-steps li {
  padding-top: 2.75rem;
  position: relative;
  color: var(--color-muted);
}
.lp-steps li::before {
  counter-increment: шаг;
  content: counter(шаг);
  position: absolute;
  top: 0;
  left: 0;
  font-size: 1.6rem;
  font-weight: 600;
  line-height: 1;
  color: var(--color-link);
  font-variant-numeric: tabular-nums;
}

/* --- Заявка ------------------------------------------------------------ */
.lp-form {
  display: grid;
  grid-template-columns: minmax(0, 42fr) minmax(0, 58fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: start;
  border-top: 1px solid var(--color-border);
}
.lp-form__note { color: var(--color-muted); max-width: 34ch; }
.lp-form__demo { font-size: 0.95rem; }

/* Поля формы. Рамка была видна с контрастом 1,34 при норме 3 для
   нетекстовых элементов: человек не понимал, где кончается поле. */
.lp-form .landing__form { max-width: 26rem; }
.lp .lp-form input,
.lp .lp-form textarea {
  width: 100%;
  min-height: 48px;
  padding: 0.65rem 0.8rem;
  /* Не --color-border: у него контраст к белому 1,34 при норме 3 для
     нетекстовых элементов, и поле не читалось как поле. Смешиваем
     текстовый цвет с фоном — так рамка остаётся в палитре и проходит
     норму. */
  border: 1px solid color-mix(in srgb, var(--color-text) 48%, var(--color-surface));
  border-radius: var(--ok-r-sm);
  background: var(--color-surface);
  /* 16px обязательны: при меньшем размере Safari на iPhone зумит всю
     страницу, как только палец попадает в поле. */
  font-size: 16px;
}
.lp .lp-form input:focus-visible,
.lp .lp-form textarea:focus-visible {
  outline: 2px solid var(--color-text);
  outline-offset: 1px;
}

/* --- Телефон -----------------------------------------------------------
   Больше половины владельцев салонов открывают страницу с телефона.
   Все развороты схлопываются в одну колонку; снимок первого экрана
   уходит под текст: человек сначала читает, что это, и только потом
   разглядывает. */
@media (max-width: 860px) {
  /* Через .lp, потому что первый экран объявлен так же: правило с одним
     классом проиграло бы ему, и на телефоне развороты остались бы
     двухколоночными. Поймано замером: колонка текста 154 px, заголовок
     в четыре строки. */
  .lp .lp-hero,
  .lp .lp-price,
  .lp .lp-client,
  .lp .lp-form { grid-template-columns: minmax(0, 1fr); }

  .lp-hero__lead,
  .lp-hero__price,
  .lp-price__figure span,
  .lp-form__note { max-width: none; }

  .lp-hero__shot img { width: 116%; }

  /* Снимок телефона кадрируем сверху, а не сжимаем целиком: в полный
     рост он занимает две трети экрана, а нужное — название салона,
     услуги и время — стоит в верхней половине. */
  .lp-client__shot { order: 2; }
  .lp-client__shot img {
    max-width: 260px;
    max-height: 420px;
    object-fit: cover;
    object-position: top;
  }

  /* Два широких снимка кабинета листаются вбок, а не лежат столбиком.
     Столбиком это два экрана подряд, на которых всё равно не разобрать
     мелкий текст; лентой — один экран и понятный жест. */
  .lp-gallery {
    display: flex;
    gap: 0.75rem;
    overflow-x: auto;
    /* Прокрутка не уходит странице: без этого свайп в конце ленты
       превращается в жест «назад» браузера, и человек вылетает со
       страницы, листая снимки. */
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    /* Край подсказывает, что лента листается (та же тень, что в
       кабинете). */
    mask-image: linear-gradient(to right, #000 88%, transparent);
  }
  .lp-gallery figure {
    flex: 0 0 85%;
    scroll-snap-align: start;
  }
  .lp-fact {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.35rem;
  }
  .lp-steps li { padding-top: 0; padding-left: 2.75rem; }
  .lp-steps li::before { top: -0.15rem; }
}

@media (max-width: 520px) {
  /* Кнопка во весь экран, ссылка под ней. flex-basis, а не width: обе
     стоят в одной flex-строке, и одной ширины мало — гибкий элемент
     всё равно ужимается до содержимого, пока сосед просит места.
     Поймано замером: кнопка оставалась 141 px вместо 335. */
  .lp .lp-hero__actions {
    flex-direction: column;
    align-items: stretch;
    gap: 0.75rem;
  }
  .lp .lp-cta { width: 100%; }
  .lp .lp-hero__second { align-self: flex-start; }
}

/* --- Движение первого экрана (2026-09-19) -------------------------------

   ЗАЧЕМ. Единственная задача — показать порядок чтения: сначала слова,
   через мгновение продукт. Страница без него выглядит как неподвижная
   картинка, страница с ним — как собранная.

   Ничего больше на первом экране не двигается. Человек здесь решает,
   читать ли дальше, и всё, что шевелится помимо этого, забирает
   внимание у заголовка.

   ЧЕМ. Чистый CSS: движение предопределено, его не надо прерывать и им
   не надо управлять, а CSS-анимация считается вне основного потока и не
   спотыкается, пока страница догружает снимки.

   Только transform и opacity — они не вызывают пересчёта разметки.

   Полсекунды, а не 0,2: это не отклик на действие, а появление
   страницы, и для него короткие сроки читаются как рывок. Шаг между
   строками 60 мс: короче — очередь не читается как очередь, длиннее —
   страница заметно ждёт, прежде чем показать кнопку.

   Кривая --ok-ease-entrance стартует быстро и долго тормозит: движение
   успевает начаться раньше, чем человек сфокусировал взгляд, а
   заканчивается мягко. ease-in здесь запрещён — он тянет старт ровно
   в тот момент, когда смотрят внимательнее всего. */
@media (prefers-reduced-motion: no-preference) {
  .lp-hero__text > *,
  .lp-hero__shot {
    animation: lp-hero-in 0.5s var(--ok-ease-entrance) both;
  }
  .lp-hero__title { animation-delay: 0.04s; }
  .lp-hero__lead { animation-delay: 0.1s; }
  .lp-hero__price { animation-delay: 0.16s; }
  .lp-hero__actions { animation-delay: 0.22s; }
  /* Снимок вступает вместе с подзаголовком, а не последним: он половина
     композиции, и страница, которая доезжает справа после кнопки,
     читается как две страницы вместо одной. */
  .lp-hero__shot {
    animation-name: lp-hero-shot-in;
    animation-delay: 0.14s;
  }
}

@keyframes lp-hero-in {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}

/* Окно продукта входит иначе, чем строки текста: чуть уменьшенным и с
   меньшим сдвигом. Масштаб — потому что это окно, а окно приближается, а
   не подъезжает. 0.985, а не 0.9: предмет не появляется из ничего, он
   уже был, просто дальше. Ниже 0.97 в кадре видно, как мылится текст
   внутри снимка. */
@keyframes lp-hero-shot-in {
  from { opacity: 0; transform: translateY(10px) scale(0.985); }
  to { opacity: 1; transform: none; }
}

/* --- Что внутри: карта возможностей (2026-09-19) ------------------------
   Салон, который выбирает систему, читает не доводы, а перечень: есть ли
   склад, считается ли зарплата, будет ли онлайн-запись. Отсюда плотный
   список группами — единственное место на странице, где плотность выше
   остальных: это справочник, а не рассказ. */
.lp-inside__intro {
  max-width: 52ch;
  margin-bottom: 2rem;
  color: var(--color-muted);
}
.lp-inside__groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 2rem 2.5rem;
}
.lp-inside__group { min-width: 0; }
/* Заголовок группы. На широком экране это просто подпись над списком, на
   телефоне — то, что нажимают. Стрелка браузера убрана: своя ставится
   ниже и только там, где группы действительно сворачиваются. */
.lp-inside__group > summary {
  padding-bottom: 0.6rem;
  margin-bottom: 0.8rem;
  border-bottom: 1px solid var(--color-border);
  font-size: 1.05rem;
  font-weight: 600;
  list-style: none;
  cursor: default;
}
.lp-inside__group > summary::-webkit-details-marker { display: none; }
.lp-inside__groups ul {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.95rem;
  color: var(--color-muted);
}
.lp-inside__groups li {
  padding: 0.3rem 0 0.3rem 1.1rem;
  position: relative;
  line-height: 1.45;
}
/* Точка вместо маркера списка: маркер браузера висит слева от текста и
   ломает выравнивание колонок. */
.lp-inside__groups li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.75em;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--color-link);
}
/* Чего нет. Отдельной строкой и тише остального: это не довод, а
   предупреждение, которое экономит салону звонок. */
.lp-inside__missing {
  margin-top: 2.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--color-border);
  max-width: 62ch;
  font-size: 0.95rem;
  color: var(--color-muted);
}

@media (max-width: 860px) {
  /* Свёрнутые группы: одна строка вместо семи. Плюс справа вместо
     стрелки браузера — он одинаково выглядит во всех браузерах и не
     съезжает от длины заголовка. */
  .lp-inside__group > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    min-height: 44px;
    margin-bottom: 0;
    padding: 0.5rem 0;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    user-select: none;
    -webkit-user-select: none;
  }
  .lp-inside__group > summary::after {
    content: "+";
    font-size: 1.3rem;
    font-weight: 400;
    line-height: 1;
    color: var(--color-link);
  }
  .lp-inside__group[open] > summary::after { content: "−"; }
  .lp-inside__group[open] > summary { margin-bottom: 0.6rem; }
  .lp-inside__groups { gap: 0; }
}

/* ======================================================================
   Шапка экрана и панель инструментов (2026-09-20, замечание основателя:
   «внутри устаревший дизайн»)

   Что чинится. Под заголовком раздела стояла россыпь одинаковых текстовых
   ссылок — в базе клиентов их было десять в два ряда, от «Возвращаемости»
   до «Импорта визитов». Все одного веса, включая главное действие; чтобы
   найти нужное, человек читал весь ряд.

   Теперь у экрана три уровня. Заголовок и под ним строка контекста —
   сколько всего записей. Справа одно главное действие. Остальное — в
   меню, сгруппированное по смыслу: отчёты отдельно, служебное отдельно.

   Меню — на <details>, без библиотек: открывается и с клавиатуры, и
   работает при выключенном JS (скрипт в base.html только закрывает
   чужие меню и слушает Esc).
   ====================================================================== */

.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  margin-bottom: 1.25rem;
}
.page-head__title,
.page-head h1,
.page-head h2 { margin: 0; }
/* Строка контекста под заголовком: сколько всего и что сейчас показано.
   Отвечает на вопрос, который человек задаёт первым, и заодно даёт
   заголовку опору — одинокий h1 висел в пустоте. */
.page-head__context {
  margin: 0.35rem 0 0;
  color: var(--color-muted);
  font-size: 0.95rem;
  font-variant-numeric: tabular-nums;
}
.page-head__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Панель инструментов: поиск и меню одной строкой, одной высоты.
   Поиск растёт, меню держат свою ширину — так строка не ломается на
   ноутбуке, где раньше уезжали последние кнопки. */
.toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1rem;
}
.toolbar__search {
  flex: 1 1 16rem;
  max-width: 26rem;
  min-width: 0;
}
.toolbar__spacer { flex: 1 1 auto; }

/* Кнопка-меню и вторичная кнопка панели — один вид: рамка, белая
   заливка, та же высота, что у поля поиска. Главное действие отличается
   от них заливкой, а не размером. */
.menu { position: relative; }
.menu > summary,
.toolbar__button {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  height: 2.65rem;
  padding: 0 0.85rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface);
  color: var(--color-text);
  font-size: 0.95rem;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.15s var(--ok-ease-out), background 0.15s var(--ok-ease-out);
}
.menu > summary { list-style: none; }
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary:hover,
.toolbar__button:hover { border-color: var(--color-link); }
.menu[open] > summary { border-color: var(--color-link); background: var(--color-surface-alt); }

/* Шеврон — фигура из бордюра, а не символ шрифта: со шрифтовой стрелкой
   ↓ вид зависит от того, какой шрифт подставит система. */
.menu > summary::after {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  margin-left: 0.1rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-0.12rem) rotate(45deg);
  transition: transform 0.18s var(--ok-ease-out);
}
.menu[open] > summary::after { transform: translateY(0.05rem) rotate(-135deg); }

.menu__list {
  position: absolute;
  z-index: 40;
  top: calc(100% + 0.4rem);
  left: 0;
  min-width: 15rem;
  padding: 0.35rem;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  background: var(--color-surface);
  /* Тень со смещением и мягким размытием: меню лежит НАД содержимым, и
     это должно читаться без рамки потолще. */
  box-shadow: 0 12px 28px -14px rgba(18, 18, 18, 0.45);
}
/* У правого края экрана меню открывается влево — иначе список уезжает
   за границу окна. */
.menu--right .menu__list { left: auto; right: 0; }
.menu__item {
  display: block;
  padding: 0.5rem 0.6rem;
  border-radius: 7px;
  color: var(--color-text);
  font-size: 0.95rem;
  text-decoration: none;
}
.menu__item:hover,
.menu__item:focus-visible { background: var(--color-surface-alt); }
.menu__group {
  padding: 0.5rem 0.6rem 0.25rem;
  color: var(--color-muted);
  font-size: 0.8rem;
  letter-spacing: 0.02em;
}
.menu__group + .menu__item { margin-top: 0; }
/* Разделитель между смысловыми частями меню — линия, а не пустота:
   пустоты в списке из восьми пунктов не видно. */
.menu__sep {
  height: 1px;
  margin: 0.35rem 0.3rem;
  background: var(--color-border);
}

/* На телефоне меню занимает ширину экрана, а не 15 rem где-то сбоку. */
@media (max-width: 640px) {
  .menu { position: static; }
  .menu__list { left: 0; right: 0; min-width: 0; }
}

/* Форма поиска внутри панели: растягивается, поле и меню «Вид» — в одну
   линию. Отдельной формой она нужна потому, что фильтры рядом — тоже
   форма, а вложенных форм браузер не допускает. */
.toolbar__form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1 1 22rem;
  min-width: 0;
}
.toolbar__form .search-field { margin: 0; }

/* Широкая панель меню — для фильтров: восьми полям ширины обычного
   меню не хватает, а растягивать все меню до этой ширины незачем. */
.menu__list--wide {
  min-width: 26rem;
  max-width: min(32rem, calc(100vw - 2rem));
  padding: 0.85rem;
}
.menu__list--wide .field { margin-bottom: 0.75rem; }
.menu__list--wide .field:last-of-type { margin-bottom: 1rem; }

/* Переключатель в меню: галочка и подпись на одной строке, вся строка —
   зона нажатия. */
.menu__item--check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
}
.menu__item--check input { margin: 0; }

/* ----------------------------------------------------------------------
   Таблица списка (2026-09-20, вместе с шапкой экрана)

   Три правки, которые делают длинный список читаемым.

   Числа вправо и моноширинными цифрами: «2 650 ₽» и «12 900 ₽» с разной
   шириной знаков не выстраиваются в столбик, и сравнить их глазом
   нельзя — а в базе клиентов ровно это и делают, ища, кто сколько
   оставил.

   Шапка липнет к верху: список на шестьсот строк прокручивают далеко, и
   через экран уже не помнишь, где «средний чек», а где «потрачено».

   Строки чуть просторнее: 0,6 rem по вертикали — плотность отчёта, а не
   рабочего списка, по которому ведут пальцем.
   -------------------------------------------------------------------- */

.client-table th,
.client-table td { padding: 0.7rem 0.85rem; }

.client-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--color-bg);
  /* Граница у липкой шапки рисуется тенью: обычный border-bottom при
     прокрутке уезжает вместе с ячейкой и оставляет шов. */
  box-shadow: inset 0 -1px 0 var(--color-border);
}

.client-table__num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Пустое состояние списка (2026-09-20). Прежняя серая строка «Клиентов
   пока нет» была верной и бесполезной: она сообщала факт, но не давала
   следующего шага. Заголовок, объяснение и действие — ровно то, что
   человеку нужно в этот момент.

   Без картинки и без рамки-карточки: это не отдельный объект на
   странице, а её временное содержимое. */
.empty-state {
  padding: 3rem 1.5rem;
  text-align: center;
}
.empty-state__title {
  margin: 0 0 0.5rem;
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--color-text);
}
.empty-state__text {
  max-width: 44ch;
  margin: 0 auto 1.25rem;
  color: var(--color-muted);
  line-height: 1.5;
}
.empty-state__actions {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Иконка дня рождения в списке клиентов (2026-09-20) — из общего набора,
   не эмодзи: размер и цвет наследуются от строки, и подпись «14 февраля»
   остаётся подписью, а не картинкой с текстом. */
.client-table__birthday-icon {
  width: 0.85rem;
  height: 0.85rem;
  vertical-align: -0.1rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Меню в строке дня журнала (2026-09-20). Печать и удалённые записи
   стояли вровень с «След. день», которую администратор жмёт весь день;
   промах по печати открывал новую вкладку и уводил со страницы.

   Отделено чертой слева — как раньше была отодвинута сама печать
   (2026-08-30, замечание основателя): это не продолжение навигации по
   дням, а другое занятие. */
.journal__day-menu {
  margin-left: 0.75rem;
  padding-left: 1rem;
  border-left: 1px solid var(--color-border);
}
@media (max-width: 768px) {
  /* На телефоне строка и так переносится — лишняя черта рвала бы её. */
  .journal__day-menu { margin-left: 0; padding-left: 0; border-left: 0; }
}

/* Меню в тёмной теме (2026-09-20). Чёрная тень на чёрном фоне не видна
   вовсе: раскрытый список отличался от страницы одной рамкой в один
   пиксель и читался как часть экрана, а не как слой над ним.

   Подложка светлее фона и та же тень поплотнее — тем же приёмом, что у
   подсказок прокрутки: цвет берётся из темы, а не задаётся числом. */
:root[data-theme="dark"] .menu__list {
  background: var(--color-surface-alt);
  box-shadow: 0 14px 32px -12px rgba(0, 0, 0, 0.75);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .menu__list {
    background: var(--color-surface-alt);
    box-shadow: 0 14px 32px -12px rgba(0, 0, 0, 0.75);
  }
}

/* «Отметить все услуги» — строкой над категориями (2026-09-20, просьба
   основателя: «выбирать все услуги»).

   Отделена чертой снизу: это не первая категория в списке, а действие
   над всем списком сразу. Заметнее, чем «вся категория» внутри: там
   галочка — мелочь внутри раскрытого блока, здесь — то, ради чего
   человек сюда и пришёл, когда сертификат действует на всё. */
.svc-choice__every {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-bottom: 0.6rem;
  margin-bottom: 0.4rem;
  border-bottom: 1px solid var(--color-border);
  font-weight: 500;
  cursor: pointer;
}

/* Выбор клиента после отзыва (2026-09-20, решение основателя: «добавь
   выбор клиенту»). Две кнопки рядом, а не одна под другой: это
   равноценный выбор, и вторая не должна выглядеть отказом от первой. */
.review-maps__choice {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
}
.review-maps__kept {
  margin: 1rem 0 0;
  color: var(--color-muted);
}

/* Полоса охвата на экране «Связь с клиентами» (2026-09-20). Цифра «0 из
   3256» и «3000 из 3256» читаются одинаково долго; полоса отвечает
   раньше, чем человек дочитает подпись. */
.reach-bar {
  height: 0.6rem;
  margin: 0 0 0.75rem;
  border-radius: 999px;
  background: var(--color-surface-alt);
  overflow: hidden;
}
.reach-bar__fill {
  height: 100%;
  background: var(--color-accent);
  border-radius: inherit;
}
/* Ссылка на бота: её диктуют, переписывают и пересылают — значит она
   должна быть крупной и целой, без многоточия. */
.connect-link {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  font-size: 1.05rem;
  word-break: break-all;
}
.connect-ways li { margin-bottom: 0.6rem; }

/* Подсказки по настройке (2026-09-20). Строка в потоке страницы, а не
   баннер: баннер закрывают не читая. Слева суть и цена вопроса, справа
   действие и «Скрыть».

   Рамка обычная, в один пиксель, и без цветной полосы сбоку: подсказка —
   не предупреждение, ей незачем кричать. */
.tips { display: flex; flex-direction: column; gap: 0.6rem; margin-bottom: 1.25rem; }
.tips__item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  background: var(--color-surface);
}
.tips__body { flex: 1 1 22rem; min-width: 0; }
.tips__title { margin: 0 0 0.25rem; font-weight: 600; }
.tips__badge {
  display: inline-block;
  margin-right: 0.4rem;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  background: var(--color-accent-soft);
  color: var(--color-text);
  font-size: 0.75rem;
  font-weight: 500;
  vertical-align: 0.05rem;
}
.tips__why { margin: 0; color: var(--color-muted); font-size: 0.95rem; line-height: 1.45; }
.tips__actions { display: flex; align-items: center; gap: 0.5rem; }
.tips__actions form { margin: 0; }

/* Шаблон недели (2026-09-20). Семь строк с галочкой и двумя временами —
   узкая таблица, которой незачем растягиваться на всю ширину экрана:
   глаз должен пробегать сверху вниз по дням, а не слева направо по
   пустоте. */
.week-template__table { max-width: 34rem; }
.week-template__table input[type="time"] { width: 7.5rem; }
.week-template__table td { vertical-align: middle; }

/* Лист для ресепшена с QR-кодом (2026-09-20, решение основателя «добавь
   qr»).

   Всё крупно и по центру: лист читают, стоя у стойки, мельком, с метра.
   Код — главное на листе, поэтому он занимает больше половины высоты, а
   слова вокруг сведены к двум строкам: что сделать и что человек за это
   получит. */
.connect-print {
  max-width: 30rem;
  margin: 0 auto;
  padding: 2rem 1.5rem;
  border: 1px solid var(--color-border);
  border-radius: 12px;
  background: var(--color-surface);
  text-align: center;
}
.connect-print__salon {
  margin: 0 0 0.35rem;
  color: var(--color-muted);
  font-size: 1rem;
}
.connect-print__title {
  margin: 0 0 1.5rem;
  font-size: 1.7rem;
  font-weight: 600;
  line-height: 1.2;
}
.connect-print__qr {
  display: block;
  width: 15rem;
  height: 15rem;
  margin: 0 auto 1.25rem;
}
.connect-print__how {
  margin: 0 0 0.6rem;
  font-size: 1.05rem;
  font-weight: 500;
}
.connect-print__what {
  margin: 0 0 1.25rem;
  color: var(--color-muted);
  line-height: 1.5;
}
.connect-print__link {
  margin: 0;
  color: var(--color-muted);
  font-size: 0.9rem;
  word-break: break-all;
}

@media print {
  /* На бумаге остаётся один лист: ни меню, ни шапки, ни кнопки
     «Распечатать» — иначе половина страницы уходит на интерфейс. */
  .connect-print__head,
  .support-launcher,
  .demo-notice { display: none !important; }
  .connect-print {
    max-width: none;
    border: none;
    padding: 0;
    background: #fff;
  }
  .connect-print__qr { width: 62mm; height: 62mm; }
}

/* QR рядом со ссылкой на экране «Связь с клиентами» (2026-09-20).
   Небольшой: здесь он не для сканирования с метра, а чтобы показать с
   экрана телефона стоящему рядом клиенту. */
.connect-qr {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.25rem;
  margin: 1rem 0 1.5rem;
}
.connect-qr__image {
  width: 9rem;
  height: 9rem;
  padding: 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  background: #fff;
}
