:root {
  /* palette — Notion paper */
  --bg: #FFFFFF;
  --surface: #FFFFFF;
  --surface-2: #F7F6F3;
  --hover: #EFEEEC;
  --ink: #37352F;
  --ink-soft: #5A584F;
  --muted: #9B9A93;
  --muted-soft: #BFBEB7;
  --rule: rgba(55, 53, 47, 0.09);
  --rule-strong: rgba(55, 53, 47, 0.16);
  /* Бегунок прокрутки. Свой цвет, а не `--rule-strong`: линейка отделяет и
     потому должна быть почти незаметна, а бегунок берут мышью — он темнее
     всего, что нарисовано рядом с ним в панели. */
  --scroll-thumb: rgba(55, 53, 47, 0.22);
  --scroll-thumb-hover: rgba(55, 53, 47, 0.34);
  --accent: #2383E2;
  --danger: #C8483B;

  /* month-grid shading — weekend, out-of-month, and both at once */
  /* Голубая подсветка дня под курсором. Отдельными токенами, а не `--accent`
     с прозрачностью: акцент — цвет кнопок и фокуса, и подсветка, привязанная к
     нему, менялась бы вместе с брендом агентства. */
  --day-hover: #E8F1FB;
  --day-hover-line: #BBD8F5;
  /* Насыщенный член той же голубой семьи: им залита «+ Событие» (issue #166 —
     «сделай синей, сочетающейся с цветом, которым выделяется день»). Токен
     стоит здесь, а не рядом с кнопкой, ровно затем, чтобы правка подсветки
     тянула за собой заливку: цвет кнопки — это утверждение о том, что она
     заводит событие в тот день, на который наведена мышь. */
  --day-hover-ink: #2470C6;
  --cal-we: #F9F8F5;
  --cal-off: #F4F3EF;
  --cal-off-we: #EFEEE9;

  /* legacy aliases (still used elsewhere in this file) */
  --border: var(--rule);
  --text: var(--ink);

  /* density / layout */
  /* Высота верхней полосы. Её спрашивают двое — сама полоса и полоса логотипа
     в сайдбаре, — потому что линия под ними одна на весь экран. Написанная
     числом дважды, она разъезжается на первой же правке содержимого полосы. */
  --topbar-h: 52px;
  --side-w: 248px;
  --pad-x: 32px;
  /* Левый край всего, что стоит внутри дня календаря: подписи колонки («Пн»),
     плашки однодневной съёмки и полосы многодневной. Одно число на всех, и это
     не бережливость — это и есть выравнивание из issue #183. Написанное
     порознь, оно и разъехалось: подпись колонки стояла на 10px, плашка на 8,
     полоса на 4, и в одном дне получалось три левых края. Полосы лежат
     отдельным слоем и считают свои проценты сами (`02-calendar.js`), поэтому
     число обязано быть переменной, а не константой в двух файлах.

     `--pad-x` — про другое: это рамка календаря, по ней стоят левый край
     сетки, коробка кнопки «Вид» и заголовок месяца в шапке. Два столбца,
     каждый внутри себя ровный. */
  --day-pad-x: 8px;
  /* Сетка часов в виде «Неделя» (issue #189). Высота часа объявлена здесь, а
     не в JS: карточка считает свой `top` тем же числом
     (`calc(var(--wk-hour-h) * …)`), и второе написание высоты часа — это
     карточка, стоящая не на своей линии.

     Ширина полосы с часами — ровно рамка календаря, и это тоже одно число, а
     не два похожих (issue #206: «сдвинь влево всю сетку»). Полоса стояла
     внутри рамки, своими 56 пикселями, набранными под слово «весь день», и
     сетка недели начиналась на эти 56 правее, чем сетка месяца, кнопка «Вид» и
     заголовок над ними: переключение вида двигало левую границу календаря —
     ровно та болезнь, которую в issue #183 лечили у timeline. Полоса уехала в
     саму рамку (отрицательный отступ у `.wk-gutter`), и левый край недели стал
     тем же `--pad-x`, что у всех. Подписи часов при этом обязаны помещаться в
     рамку — см. `.wk-hour-label`. */
  --wk-hour-h: 48px;
  --wk-gutter-w: var(--pad-x);
  --pad-y: 18px;
  --side-item-h: 28px;
  --row-h: 116px;
  /* Отступ боковой колонки. По правому его краю стоят цифра в строке списка,
     лупа с воронкой в заголовке «Моделей» и шестерёнка в заголовке «Статуса» —
     один столбец на всю колонку. Написанный числом в трёх местах, он и
     разъехался: у заголовка стояло 10px против 9px у строк, и значки оказались
     на пять пикселей левее цифр под ними. Совпадают при этом края рамок
     значков: у рисунка внутри неё свой запас, свой у каждого. */
  --sb-row-pad: 9px;
  /* Цвет цифры в строке колонки. Полшага от `--muted` букеру не хватило: он
     попросил снова, теми же словами и о том же — «на тон-два бледнее, это не
     основная информация» (issue #182). Здесь два тона, то есть почти вся
     дорога до `--muted-soft`, но не он сам: `--muted-soft` держит линейки и
     числа чужого месяца — то, что не читают вовсе, — а эту цифру читают,
     просто вторым движением, уже найдя строку с нужным именем. Мера у
     обеих просьб одна: цифра обязана быть тише имени рядом и тише заголовка
     секции над ним. */
  --sb-count: #B8B7B0;

  /* type */
  --display: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
  --sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
  --serif: 'DM Serif Display', 'Times New Roman', serif;
  --mono: 'JetBrains Mono', ui-monospace, monospace;

  --t-display-xl: 56px;
  --t-display: 32px;
  --t-display-sm: 22px;
  --t-h1: 22px;
  --t-h2: 16px;
  --t-body: 14px;
  --t-small: 12px;
  --t-micro: 11px;

  /* Радиусы: карточка/кнопка — 6, поле/чип — 4. Крупнее было у прежней
     «объёмной» темы; бумага не гнётся, и скруглять её сильнее нечем. */
  --radius-lg: 8px;
  --radius-md: 6px;
  --radius: 6px;
  --radius-sm: 4px;

  /* Тень здесь означает «лежит поверх»: модал, меню, панель. Кнопки и строки
     ничего не перекрывают, поэтому у них тени нет — их отделяет линия. */
  --shadow-modal: 0 8px 32px rgba(0,0,0,0.18);
  --shadow-menu:  0 8px 24px rgba(0,0,0,0.12);
  --shadow-panel: 0 8px 40px rgba(0,0,0,0.14);
  --shadow-card:  0 1px 4px rgba(0,0,0,0.04);
  --focus-ring:   0 0 0 2px rgba(35,131,226,0.18);

  /* Размер пальца. Держится токеном, а не числом на месте, потому что это
     единственная величина здесь, которая задана не вкусом, а рукой: 44px —
     та цифра, ниже которой промахиваются, и она одинакова для крестика на
     теге и для кнопки «Сохранить». */
  --tap: 44px;

  /* Значок «Пользователи» для узкого топбара, где кнопка остаётся без подписи.
     Лежит токеном, потому что в самом правиле это нечитаемая строка в сто
     символов; та же обводка 2px, что у соседних значков в разметке. */
  --ico-users: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2'/%3E%3Ccircle cx='9' cy='7' r='4'/%3E%3Cpath d='M22 21v-2a4 4 0 0 0-3-3.87'/%3E%3Cpath d='M16 3.13a4 4 0 0 1 0 7.75'/%3E%3C/svg%3E") center / 17px no-repeat;
  /* Неподтверждённое событие: серый набросок вместо цвета статуса. Токенами,
     а не значениями по месту, потому что спрашивают их пять селекторов, и
     пятый оттенок серого, написанный в пятый раз, — это пятый оттенок. */
  --unconf-fill:  #F4F4F2;
  --unconf-hover: #ECECE9;
  --unconf-line:  #B4B4AE;
  --unconf-ink:   #6B6B66;
}

body.dark, [data-theme="noir"] {
  --bg: #191919;
  --surface: #1F1F1F;
  --surface-2: #2A2A2A;
  --hover: #2F2F2F;
  --ink: #E6E6E5;
  --ink-soft: #B8B8B5;
  --muted: #8B8B85;
  --muted-soft: #5A5A55;
  /* Тот же шаг в тёмной теме: там бледнее — значит ближе к фону, а не белее. */
  --sb-count: #646460;
  --rule: rgba(255, 255, 255, 0.07);
  --rule-strong: rgba(255, 255, 255, 0.13);
  --scroll-thumb: rgba(255, 255, 255, 0.16);
  --scroll-thumb-hover: rgba(255, 255, 255, 0.26);
  --accent: #5BA0E0;
  --danger: #E66F61;
  --day-hover: #1E3247;
  --day-hover-line: #2C4C6B;
  --day-hover-ink: #2E77C9;
  --cal-we: #222222;
  --cal-off: #262626;
  --cal-off-we: #2A2A2A;
  --shadow-modal: 0 8px 32px rgba(0,0,0,0.55);
  --shadow-menu:  0 8px 24px rgba(0,0,0,0.45);
  --shadow-panel: 0 8px 40px rgba(0,0,0,0.5);
  --shadow-card:  0 1px 4px rgba(0,0,0,0.35);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
/* Серая вспышка во всю строку на каждом касании — то, из-за чего интерфейс
   читается как страница, а не как приложение: iOS рисует её по границам
   элемента с обработчиком, а здесь обработчик висит на строке целиком. Гасим
   её здесь, а нажатие показываем сами — `:active` ниже, в мобильном блоке. */
* { -webkit-tap-highlight-color: transparent; }
body { font-family: var(--sans); background: var(--bg); color: var(--text); min-height: 100vh; min-height: 100dvh; -webkit-font-smoothing: antialiased; padding: 0; transition: background .3s ease; }
body.theme-switching *, body.theme-switching *::before, body.theme-switching *::after { transition: none !important; }
.hidden { display: none !important; }

/* ── ВХОД ──
   Одна страница вместо прежних двух панелей с заставкой и «въездом» формы:
   человек пришёл ввести логин, а не смотреть анимацию, и вторая кнопка
   «Войти», открывавшая форму, стоила ему лишнего шага каждый рабочий день. */
.login-screen {
  position: fixed; inset: 0; z-index: 200;
  background: var(--bg);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
/* Марка размером в пол-экрана, но на пяти процентах непрозрачности: фон, с
   которого нечего читать, и потому он не спорит с формой. */
.login-watermark {
  position: absolute; left: -160px; bottom: -140px;
  width: 620px; opacity: .05;
  pointer-events: none; user-select: none;
}
body.dark .login-watermark { filter: invert(1); opacity: .07; }

.login-top {
  position: absolute; top: 0; left: 0; right: 0; height: 52px;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 0 24px; border-bottom: 1px solid var(--rule);
}
.login-lockup { height: 22px; width: auto; display: block; }
body.dark .login-lockup { filter: invert(1); }
.login-tagline {
  font-size: 11px; font-weight: 500; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted); white-space: nowrap;
}

.login-col { position: relative; z-index: 2; width: 100%; max-width: 340px; padding: 0 20px; }
.login-form { display: flex; flex-direction: column; gap: 14px; }
.modal-eyebrow {
  font-size: 11px; font-weight: 600; color: var(--muted);
  letter-spacing: 0.06em; text-transform: uppercase;
  margin-bottom: 1px;
}
.login-form-h {
  font-size: 32px; line-height: 1.1; letter-spacing: -0.02em;
  font-weight: 700; color: var(--ink); font-family: var(--display);
}
.login-sub { font-size: 14px; line-height: 1.5; color: var(--muted); margin-bottom: 14px; }

.field { display: flex; flex-direction: column; gap: 5px; }
.field label, .login-field label {
  font-size: 12px; font-weight: 500;
  letter-spacing: 0; text-transform: none;
  color: var(--muted);
}
.field input, .field select,
.login-field input {
  height: 34px; padding: 0 10px;
  border: 1px solid var(--rule-strong);
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--radius-sm);
  font-family: inherit; font-size: 14px;
  outline: none;
  transition: border-color .18s, box-shadow .18s;
}
.field input:focus, .field select:focus,
.login-field input:focus {
  border-color: var(--accent);
  box-shadow: var(--focus-ring);
}
.field input::placeholder,
.login-field input::placeholder { color: var(--muted-soft); }
.login-field { display: flex; flex-direction: column; gap: 5px; margin-bottom: 0; }
/* Пустая строка ошибки не должна раздвигать форму: место под неё берётся
   тогда, когда есть что сказать. */
.login-error { font-size: 12px; color: var(--danger); margin-top: -6px; }
.login-error:empty { display: none; }
/* «Письмо уже в пути» is not an error and must not read as one. */
.login-error.ok { color: var(--muted); }

.login-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  font-size: 11px; color: var(--muted-soft);
}
.login-alt {
  padding: 0; background: none; border: none; cursor: pointer;
  font-family: inherit; font-size: 11px; color: var(--muted-soft);
}
.login-alt:hover { color: var(--ink); }

.btn-primary {
  width: 100%; height: 38px; margin-top: 6px;
  background: var(--ink); color: var(--bg);
  border: none; border-radius: var(--radius-sm);
  font-family: inherit; font-size: 14px; font-weight: 600;
  cursor: pointer; transition: filter .15s;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
}
.btn-primary:hover { filter: brightness(1.15); }
.btn-primary .arr { display: inline-block; transition: transform .18s ease; }
.btn-primary:hover .arr { transform: translateX(4px); }
.btn-enter { /* alias kept for backwards compat */ }

@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .login-screen, .login-screen * { animation: none !important; transition: none !important; }
}
@media (max-width: 640px) {
  /* Водяной знак на телефоне занимает половину экрана и ничего не сообщает. */
  .login-watermark { display: none; }
  .login-col { max-width: 420px; padding: 0 22px; }
  .login-form-h { font-size: 28px; }
  /* Поля под палец; 16px, иначе iOS зумит страницу на фокусе. */
  .login-field input { height: 48px; font-size: 16px; }
  .btn-primary { height: 48px; font-size: 15px; }
  /* «Забыли пароль?» — 13 пикселей высотой, и это единственная дверь для
     того, кто уже не может войти. Растёт область нажатия, а не надпись. */
  .login-alt { padding: 12px 4px; }
}

/* ── APP SHELL ── */
.app {
  display: grid;
  grid-template-columns: var(--side-w, 248px) 1fr;
  /* dvh, не vh: на iOS `100vh` — это высота с убранными панелями браузера,
     поэтому при показанных панелях документ выше экрана, и оболочка, которая
     сама ничего не скроллит, всё равно резинится под пальцем вместе с топбаром.
     `vh` остаётся строкой выше как запас для браузера, не знающего `dvh`. */
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  background: var(--bg);
  border-radius: 0;
  border: none;
  box-shadow: none;
  transition: grid-template-columns .26s cubic-bezier(.65,0,.2,1);
}
.app.sidebar-hidden { grid-template-columns: 0 1fr; }
/* `visibility` — не про рисунок, а про Tab: свёрнутая панель нулевой ширины
   оставалась в порядке обхода, и десять нажатий Tab уходили в невидимые
   ссылки, включая саму кнопку сворачивания. Прячется с задержкой в ту же
   двадцатую секунды, что едет ширина, иначе содержимое исчезает раньше, чем
   панель успевает закрыться. */
.app.sidebar-hidden .sidebar {
  width: 0; padding: 0; border-right-color: transparent; overflow: hidden;
  visibility: hidden; transition: width .2s ease, padding .2s ease, visibility 0s linear .2s;
}
.app[data-density="compact"] { --row-h: 100px; --side-item-h: 26px; --pad-x: 24px; }

/* ── SIDEBAR ── */
.sidebar {
  background: var(--surface-2);
  border-right: 1px solid var(--rule, rgba(55,53,47,0.09));
  /* Справа отступа нет: правый край панели держит теперь полоса прокрутки
     (`#sidebar-scroll`), и восемь пикселей, отданные ей, — те же самые, что
     раньше были пустым полем. Отсюда же одинаковый правый край у пунктов
     навигации и у строк списка: до этого строки стояли на пятнадцать
     пикселей левее — ровно на ширину полосы, съедавшей их ширину. */
  padding: 0 0 0 8px;
  display: flex; flex-direction: column; gap: 2px;
  overflow: hidden;
  transition: width 0.2s ease, padding 0.2s ease, border-color 0.2s ease;
  min-width: 0;
}
/* Прокручиваемая часть панели — всё, кроме полосы логотипа: навигация, списки
   и подпись внизу едут вместе, и бегунок ходит от верха колонки до её низа
   (issue #249). Отступ снизу здесь, а не на `.sidebar`: на панели он обрезал
   бы дорожку бегунка на двенадцать пикселей раньше края. */
#sidebar-scroll {
  flex: 1; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 2px;
  padding-bottom: 12px;
  /* Место под полосу держится всегда: появляясь и исчезая вместе со списком,
     она двигала бы столбец цифр — та же беда, из-за которой полоса спрятана у
     `.sb-model-scroll`. */
  scrollbar-gutter: stable;
}
/* Полоса рисуется своя, потому что системная приносит с собой белую дорожку с
   рамкой: на сером поле панели она читается как чужая вставка, а не как её
   край (issue #249 — «оставить только серую часть, убрать белую рамку»).
   Ширина равна левому отступу панели: строки стоят между двумя одинаковыми
   полями, и в правом лежит бегунок, прижатый к границе с календарём.

   `scrollbar-width` и `scrollbar-color` рядом с этими правилами дописывать
   нельзя, хотя они и короче: Chrome, увидев любое из них, перестаёт читать
   `::-webkit-scrollbar` вовсе и рисует свою тонкую полосу — на два пикселя
   шире и с зазором до края. Проверено на живом экране: 8 пикселей и бегунок
   вплотную против 10 и бегунка, не достающего до границы. Firefox остаётся с
   системной полосой — она у него серая и без рамки, то есть та же просьба
   выполнена его собственными руками. */
#sidebar-scroll::-webkit-scrollbar { width: 8px; }
#sidebar-scroll::-webkit-scrollbar-track { background: transparent; }
#sidebar-scroll::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb); border-radius: 4px;
  /* Рамка бегунка обычно рисуется цветом фона, чтобы он казался у́же. Здесь
     она была бы белой полоской по краю панели — тем самым, что просили
     убрать. */
  border: none;
}
#sidebar-scroll::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover); }
/* `flex: 1 0 auto` — подпись внизу прижата к низу панели, пока содержимого
   мало, и уезжает вниз вместе с ним, когда его много. `shrink: 0` тут не
   украшение: сжавшись, список отдал бы прокрутку обратно самому себе. */
#sidebar-body { flex: 1 0 auto; display: flex; flex-direction: column; gap: 2px; }
.side-foot {
  flex-shrink: 0;
  margin-top: 8px;
  padding-top: 10px;
  border-top: 1px solid var(--rule, rgba(55,53,47,0.09));
}
.side-foot-user {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 8px; border-radius: var(--radius, 6px);
  cursor: pointer;
}
.side-foot-user:hover { background: var(--hover, #EFEEEC); }
.side-foot-avatar {
  width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 600; color: #fff;
  flex-shrink: 0;
}
.side-foot-info { min-width: 0; flex: 1; }
.side-foot-name {
  font-size: 13px; font-weight: 500; color: var(--ink, #37352F);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  line-height: 1.2;
}
.side-foot-role {
  font-size: 11px; color: var(--muted, #9B9A93);
  line-height: 1.2; margin-top: 1px;
}

/* Логотип стоит в своей полосе с линией снизу, а не в одном ряду с
   навигацией, иначе он читается как первый её пункт. Высота полосы ровно та
   же, что у топбара: линия под ней и линия под топбаром — одна горизонтальная
   линия через весь экран. Стоило ей разойтись на десяток пикселей, как весь
   верх выглядел собранным на глаз. */
/* Кнопка «свернуть» стоит в самой панели, а не в шапке продукта: сворачивают
   боковую панель, глядя на неё. Полосу с отступами и линией держит ряд, а не
   сама кнопка логотипа, — кнопку в кнопку не вложить. */
.brand-row {
  display: flex; align-items: center; gap: 4px;
  height: var(--topbar-h); flex-shrink: 0;
  /* Полоса вылезает влево на отступ панели, чтобы линия под ней шла от края
     до края; справа отступа у панели больше нет, и вылезать там некуда. */
  margin: 0 0 8px -8px; padding: 0 10px 0 16px;
  width: calc(100% + 8px);
  border-bottom: 1px solid var(--rule, rgba(55,53,47,0.09));
}
.brand {
  display: flex; align-items: center;
  height: 100%; flex: 1; min-width: 0;
  margin: 0; padding: 0; border: none;
  cursor: pointer; background: transparent; text-align: left;
}
.side-collapse {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex-shrink: 0;
  background: none; border: none; border-radius: var(--radius-sm, 4px);
  color: var(--muted, #9B9A93); cursor: pointer;
  transition: background .12s, color .12s;
}
.side-collapse:hover { background: var(--hover, #EFEEEC); color: var(--ink, #37352F); }
.side-collapse svg { width: 17px; height: 17px; display: block; }
.brand-lockup {
  height: 28px; width: auto; display: block;
  /* Знак нарисован цветом ink по прозрачному фону: в тёмной теме его
     достаточно вывернуть, отдельного файла для неё не нужно. */
}
body.dark .brand-lockup { filter: invert(1); }

/* primary nav */
.nav-list { display: flex; flex-direction: column; gap: 1px; }
.nav-item {
  display: flex; align-items: center; gap: 10px;
  padding: 0 10px; height: 32px; border: none;
  background: transparent; text-align: left; width: 100%;
  font-size: 13.5px; color: var(--ink, #37352F);
  border-radius: var(--radius-sm, 4px); cursor: pointer;
  transition: background .12s; white-space: nowrap;
  font-family: inherit; position: relative;
  /* The primary nav entries are <a href> so that the browser can open them in a
     tab and copy their address; nothing about them should look like body text. */
  text-decoration: none;
}
.nav-item:hover { background: var(--hover, #EFEEEC); }
/* Активный пункт — заливка, а не обводка с тенью: в плоской теме приподнятая
   строка читается как кнопка, которую ещё не нажали. */
.nav-item.active { background: var(--hover, #EFEEEC); font-weight: 500; }
.nav-item:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.nav-item .nav-ic {
  width: 16px; height: 16px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-soft, #5A584F);
}
.nav-item .nav-ic svg { width: 16px; height: 16px; }
.nav-item.active .nav-ic { color: var(--ink, #37352F); }
/* Цифра на пункте меню — сколько за ним ждёт человека. Правый край, а не
   рядом со словом: пункты разной длины, и приписанная к слову цифра стояла бы
   у каждого на своём месте, то есть столбца из них не получилось бы вовсе.
   Тише подписи — её смотрят вторым движением, уже прочитав, что за раздел, —
   и того же порядка тише, что цифра у имени модели в колонке расписания. */
.nav-item .nav-count {
  margin-left: auto; font-size: 11px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--muted-soft, #BFBEB7);
}
.nav-item.active .nav-count { color: var(--muted, #9B9A93); }

/* keep old .logo for any leftover */
.logo { display: none; }

.sidebar-section, .side-section { display: flex; flex-direction: column; gap: 1px; margin-top: 12px; }
.sidebar-section h4, .side-section h4 {
  padding: 6px var(--sb-row-pad, 9px) 4px;
  font-size: 11px; letter-spacing: 0.05em;
  color: var(--muted, #9B9A93);
  font-weight: 600;
  text-transform: uppercase;
  margin: 0;
  display: flex; align-items: center; gap: 2px;
}
/* The fold chevron belongs to the word, so it stands next to it; whatever else
   the section owns is an action of its own and keeps the right edge.

   «Тот же край» — это край значка, а не край кнопки вокруг него: кнопка шире
   своего рисунка на одинаковый запас с обеих сторон, и на этот запас она
   вылезает за правый край заголовка. Считается он из тех же двух чисел,
   которыми кнопка нарисована, поэтому выросшая под палец кнопка (мобильный
   блок в конце файла) правит только свой размер, а край остаётся на месте. */
.sb-head-row {
  --sb-tool-size: 22px;
  --sb-tool-icon: 14px;
}
/* Запас считается на самой кнопке, а не берётся готовым от заголовка: значение
   переменной вычисляется там, где она объявлена, и посчитанный наверху запас не
   узнал бы, что на телефоне лупа с воронкой выросли до 32px. */
.sb-head-tools { margin-right: calc((var(--sb-tool-size) - var(--sb-tool-icon)) / -2); }
.sb-head-label { flex-shrink: 0; }
/* Кнопки у правого края заголовка секции: лупа с воронкой у «Моделей», «+» с
   шестерёнкой у «Статуса». Класс на все четыре один — правый край столбца
   считается в одном месте, иначе первая же правка размера кнопки разводит две
   секции на пару пикселей молча. У «Моделей» открытая подсвечена: поле под
   заголовком и кнопка над ним должны читаться как одно состояние, иначе
   крестик на поле и нажатая кнопка спорят друг с другом. */
.sb-head-tools { margin-left: auto; display: inline-flex; align-items: center; gap: 2px; }
.sb-tool-btn {
  border: none; background: transparent; cursor: pointer;
  width: var(--sb-tool-size, 22px); height: var(--sb-tool-size, 22px);
  border-radius: var(--radius-sm, 4px);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted, #9B9A93); padding: 0; font-family: inherit;
  transition: background .1s, color .1s;
}
.sb-tool-btn svg { width: var(--sb-tool-icon, 14px); height: var(--sb-tool-icon, 14px); }
.sb-tool-btn:hover { background: var(--hover, #EFEEEC); color: var(--ink, #37352F); }
.sb-tool-btn.is-on { background: var(--hover, #EFEEEC); color: var(--ink, #37352F); }
.sb-fold-btn {
  border: none; background: transparent; cursor: pointer;
  width: var(--sb-tool-size, 22px); height: var(--sb-tool-size, 22px);
  border-radius: var(--radius-sm, 4px);
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 1; color: var(--muted, #9B9A93);
  padding: 0; font-family: inherit;
  transition: background .1s, color .1s;
}
/* Ручка, а не слово: шире прежнего треугольника почти в полтора раза и вдвое
   легче его по чернилам. Прозрачность — 0.8, а не «поменьше, как просили»:
   значок это единственное, чем кнопка видна вообще, и на 0.55 контраст падал
   до 1.6:1 — ниже того, с чего начинали, то есть просьба «прозрачнее» съедала
   бы просьбу «заметнее». Поворот здесь — состояние, а не движение: заголовок
   перерисовывается целиком, новый узел рисуется уже повёрнутым, и `transition`
   на `transform` обещал бы анимацию, которой никто не увидит. */
.sb-fold-ic {
  width: 20px; height: 20px; opacity: .8;
  transition: opacity .1s;
}
.sb-fold-btn.is-collapsed .sb-fold-ic { transform: rotate(-90deg); }
.sb-fold-btn:hover { background: var(--hover, #EFEEEC); color: var(--ink, #37352F); }
.sb-fold-btn:hover .sb-fold-ic,
.sb-fold-btn:focus-visible .sb-fold-ic { opacity: 1; }
.model-list { display: flex; flex-direction: column; gap: 0; }
/* Roster controls in the «Модели» section: search, gender/availability,
   and a capped list. Ten-ish rows, then the list scrolls on its own —
   a fifty-name roster must not push «Статус» off the screen. */
.sb-model-search {
  margin: 2px 4px 4px; padding: 4px 8px;
  border: 1px solid var(--rule, rgba(55,53,47,.12));
  border-radius: var(--radius-sm, 4px);
  background: var(--surface, #fff); color: var(--ink, #37352F);
  font: inherit; font-size: 12.5px;
}
.sb-model-search:focus { outline: none; border-color: var(--accent, #C8A882); }
.sb-model-selects { display: flex; gap: 4px; margin: 0 4px 6px; }
.sb-model-sel {
  flex: 1; min-width: 0; padding: 3px 4px;
  border: 1px solid var(--rule, rgba(55,53,47,.12));
  border-radius: var(--radius-sm, 4px);
  background: var(--surface, #fff); color: var(--ink-soft, #5A584F);
  font: inherit; font-size: 11.5px;
}
/* Полоса прокрутки здесь — это 15 пикселей, на которые уезжает столбец цифр:
   «Все модели» лежит вне прокручиваемого списка, а имена внутри, и полоса
   появляется и исчезает от одной набранной в поиске буквы. Прячем её, как у
   `.side-panel-inner` и `.set-tabs`, а о том, что список длиннее, говорит
   половина одиннадцатой строки, торчащая из-под края. */
.sb-model-scroll {
  max-height: calc(var(--side-item-h, 28px) * 10.5);
  overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: none;
}
.sb-model-scroll::-webkit-scrollbar { display: none; }
.sb-model-none { padding: 4px var(--sb-row-pad, 9px); font-size: 12px; color: var(--muted, #9B9A93); }
.model-item {
  display: flex; align-items: center; gap: 9px;
  padding: 0 var(--sb-row-pad, 9px);
  height: var(--side-item-h, 28px);
  border: none; background: transparent;
  text-align: left; width: 100%;
  font-size: 13.5px; color: var(--ink-soft, #5A584F);
  border-radius: var(--radius-sm, 4px);
  cursor: pointer;
  transition: background .1s, color .1s;
  white-space: nowrap; overflow: hidden;
  font-family: inherit;
}
.model-item:hover { background: var(--hover, #EFEEEC); }
.model-item.active {
  background: var(--hover, #EFEEEC); color: var(--ink, #37352F); font-weight: 500;
}
.model-dot {
  width: 9px; height: 9px; border-radius: 50%;
  flex-shrink: 0;
}
/* Цифра — подпись к имени, а не второе слово в строке: читают в этой колонке
   имена, а число смотрят, когда уже нашли нужную строку. Поэтому оно бледнее
   не только основного текста, но и заголовка секции. */
.view-count {
  margin-left: auto;
  font-size: 12px; color: var(--sb-count, #B8B7B0);
  font-variant-numeric: tabular-nums;
  background: transparent; border-radius: 0;
  /* Имя забирает остаток строки и обрезается, цифра держит свою ширину: так
     одна и две цифры стоят по одному правому краю. */
  flex-shrink: 0; min-width: 20px; text-align: right; padding: 0;
}

/* Подраздел «Расписания» — «Модели» и «Агентство». Тот же ряд, что у секции,
   но на ступень тише: заголовок секции — граница между разделами колонки,
   заголовок подраздела — граница внутри одного. Отступ слева не рисуется —
   строки внутри подразделов остаются на общем левом краю колонки: сдвинутые,
   они спорили бы с «Статусом», у которого подраздела нет и не будет. */
.sb-sub { display: flex; flex-direction: column; gap: 1px; }
/* Заголовок секции стал темнее подзаголовка, а не крупнее его: колонка узкая,
   и вложенность в ней приходится показывать весом, потому что отступа нет —
   строки подразделов остаются на общем левом краю (см. ниже). Одного размера
   на две ступени не хватило: «РАСПИСАНИЕ» и «МОДЕЛИ» читались как два соседних
   раздела, то есть ровно как до этой правки. */
.sidebar-section > .sb-head-row { color: var(--ink-soft, #5A584F); }
.sidebar-section .sb-sub-head {
  padding: 4px var(--sb-row-pad, 9px) 3px;
  font-size: 10.5px; letter-spacing: .05em;
  color: var(--muted, #9B9A93);
  font-weight: 600; text-transform: uppercase; margin: 0;
  display: flex; align-items: center; gap: 2px;
  opacity: .85;
}

/* Строка списка и её «…».

   Правый край здесь один и тот же для цифры и для точек — `--sb-row-pad`, по
   которому стоит вся колонка. Точки не приписываются сбоку, а встают на место
   цифры: приписанные, они сдвигали бы имя при каждом наведении, то есть текст
   ехал бы под курсором. Поэтому цифра при наведении гаснет, а точки
   проявляются в её границах.

   Появляются они и по фокусу с клавиатуры (`:focus-within`) — спрятанное
   навсегда действие ничем не отличается от ненаписанного. */
.sb-row { position: relative; display: flex; }
.sb-row > .model-item { flex: 1; min-width: 0; }
.sb-row-more {
  position: absolute; right: calc(var(--sb-row-pad, 9px) - 5px); top: 50%;
  transform: translateY(-50%);
  width: 22px; height: 22px; padding: 0;
  border: none; background: transparent; cursor: pointer;
  border-radius: var(--radius-sm, 4px);
  color: var(--muted, #9B9A93);
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none;
  transition: background .1s, color .1s, opacity .1s;
}
.sb-row-more svg { width: 15px; height: 15px; }
.sb-row:hover .sb-row-more,
.sb-row:focus-within .sb-row-more { opacity: 1; pointer-events: auto; }
.sb-row:hover .view-count,
.sb-row:focus-within .view-count { opacity: 0; }
.sb-row-more:hover { background: var(--rule, rgba(55,53,47,.12)); color: var(--ink, #37352F); }

/* На тач-экране наведения нет вовсе, и спрятанные точки были бы кнопкой,
   которую нельзя нажать. Показываем всегда — цифра при этом остаётся на месте,
   а точки встают за ней: на телефоне колонка шире, места хватает обеим. */
@media (hover: none) {
  .sb-row-more { opacity: 1; pointer-events: auto; position: static; transform: none; }
  .sb-row:hover .view-count, .sb-row:focus-within .view-count { opacity: 1; }
  .sb-row > .model-item { padding-right: 2px; }
}

/* ── Status manager modal ── */
.stm-modal { width: 460px; }
.stm-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
/* Отступ под заголовком «Статусов» держало пояснение, которое issue #257 убрал
   как лишнее: без него список вставал в четырёх пикселях под словом. Правило
   смотрит на соседство, а не на сам диалог, — у «Списков в форме события»
   разметка та же, но пояснение там осталось, и второй отступ подряд был бы
   дырой в полстроки. */
.stm-modal h3 + .stm-list { margin-top: 18px; }
/* Строка, из-за которой диалог открыли: «…» у статуса в колонке слева ведёт
   сюда, и без подсветки человек попадает в список и ищет в нём свою глазами. */
.stm-row-focus { background: var(--hover, #EFEEEC); }
.stm-row {
  display: flex; align-items: center; gap: 9px;
  padding: 6px; border-radius: var(--radius-md, 8px);
  border: 1px solid transparent;
  background: var(--surface, #fff);
  transition: border-color .1s, box-shadow .1s, opacity .1s;
}
.stm-row.dragging { opacity: 0.4; }
.stm-row.drop-above { box-shadow: 0 -2px 0 var(--accent, #2563EB); }
.stm-row.drop-below { box-shadow: 0 2px 0 var(--accent, #2563EB); }
.stm-drag {
  cursor: grab; color: var(--muted, #9B9A93); font-size: 13px;
  letter-spacing: -2px; user-select: none; flex-shrink: 0; padding: 0 2px;
}
.stm-color {
  position: relative; width: 24px; height: 24px; border-radius: 50%;
  flex-shrink: 0; cursor: pointer; overflow: hidden;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.10) inset;
}
.stm-color input[type="color"] {
  position: absolute; inset: -4px; width: calc(100% + 8px); height: calc(100% + 8px);
  border: none; padding: 0; background: transparent; cursor: pointer; opacity: 0;
}
.stm-name {
  flex: 1; min-width: 0;
  padding: 6px 9px; font-size: 14px; font-family: inherit;
  color: var(--ink, #37352F);
  background: var(--bg, #FCFCFA);
  border: 1px solid var(--rule, rgba(55,53,47,0.14));
  border-radius: var(--radius-sm, 5px); outline: none;
}
.stm-name:focus { border-color: var(--accent, #2563EB); }
.stm-count {
  font-size: 12px; color: var(--muted, #9B9A93);
  font-variant-numeric: tabular-nums; min-width: 20px; text-align: right;
}
.stm-del {
  border: none; background: transparent; cursor: pointer;
  width: 26px; height: 26px; border-radius: var(--radius-sm, 5px);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted, #9B9A93); flex-shrink: 0;
  transition: background .1s, color .1s;
}
/* Крест мельче общих 16px `.spn-ic`: в строке справочника он стоит рядом со
   значком статуса (13px) и не должен перевешивать то, что строка называет. */
.stm-del .spn-ic { width: 14px; height: 14px; }
.stm-del:hover { background: rgba(239,68,68,0.14); color: #EF4444; }
.stm-add {
  display: flex; align-items: center; gap: 9px;
  padding-top: 14px; border-top: 1px solid var(--rule, rgba(55,53,47,0.10));
}
.stm-add-btn {
  padding: 7px 14px; font-size: 13px; font-family: inherit; font-weight: 500;
  border: none; border-radius: var(--radius-sm, 5px); cursor: pointer;
  background: var(--ink, #37352F); color: #fff; flex-shrink: 0;
}
.stm-add-btn:hover { opacity: 0.9; }
/* Графа значка стоит после названия и перед кнопкой удаления — на том месте,
   куда показывает стрелка в issue #258. Пустая выглядит пустой: рамка и крест той
   же толщины, что у «+» в заголовке колонки статусов, а не серый квадрат,
   который читался бы как отключённая кнопка. */
.stm-icon {
  width: 34px; height: 30px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 15px; line-height: 1; font-family: inherit; font-weight: 700;
  color: var(--ink, #37352F);
  background: var(--bg, #FCFCFA);
  border: 1px solid var(--rule, rgba(55,53,47,0.14));
  border-radius: var(--radius-sm, 5px);
  cursor: pointer; transition: border-color .1s, color .1s;
}
.stm-icon:hover { border-color: var(--accent, #2563EB); }
.stm-icon-empty { color: var(--muted, #9B9A93); }
/* Два размера, потому что в графе стоят две разные вещи: «+» пустой графы —
   подсказка и держится мельче, значок из набора — содержимое и рисуется в
   полный рост клетки. */
.stm-icon svg { width: 13px; height: 13px; }
.stm-icon .st-ic { width: 17px; height: 17px; }

/* Набор значков — сетка в выпадающем меню (`spn-status-menu`, оно же держит
   рамку и тень). Шесть в ряд: набор виден целиком, не прокручиваясь, а меню
   не шире строки, из которой открылось. */
.stm-ic-grid {
  display: grid; grid-template-columns: repeat(6, 1fr);
  gap: 2px; padding: 8px;
}
.stm-ic-cell {
  width: 30px; height: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink, #37352F);
  border: 1px solid transparent; border-radius: var(--radius-sm, 5px);
  background: transparent; cursor: pointer;
  transition: background .1s, border-color .1s;
}
.stm-ic-cell svg { width: 17px; height: 17px; display: block; }
.stm-ic-cell:hover { background: var(--bg, #FCFCFA); }
/* Свой значок подсвечен рамкой, а не заливкой: заливка в сетке из двенадцати
   картинок читается наведением, то есть тем, что под рукой, а не тем, что
   выбрано. Щелчок по нему значок снимает — «убрать» здесь то же действие,
   что «поставить». */
.stm-ic-on { border-color: var(--accent, #2563EB); }
.stm-ic-abbr {
  display: flex; align-items: center; gap: 6px;
  padding: 8px;
  border-top: 1px solid var(--rule, rgba(55,53,47,0.10));
}
/* Классов у поля два, и второй — `spn-field-new-in` — не косметика: за него
   держится `_menuAt(…, true)`, то есть курсор в поле и закрытие меню по щелчку
   мимо. Он же рисует поле строкой без рамки, поэтому здесь селектор с
   родителем: в клетке набора поле обязано выглядеть полем, а не подписью. */
.stm-ic-abbr .stm-ic-in {
  width: 84px; flex: none; min-width: 0; text-align: center;
  padding: 5px 6px; font-size: 13px; font-family: inherit; font-weight: 600;
  color: var(--ink, #37352F);
  background: var(--bg, #FCFCFA);
  border: 1px solid var(--rule, rgba(55,53,47,0.14));
  border-radius: var(--radius-sm, 5px); outline: none;
}
.stm-ic-abbr .stm-ic-in:focus { border-color: var(--accent, #2563EB); }
.stm-ic-abbr .stm-ic-in::placeholder { font-weight: 400; font-size: 12px; }
.stm-ic-ok {
  padding: 6px 10px; font-size: 12px; font-family: inherit; font-weight: 500;
  border: none; border-radius: var(--radius-sm, 5px); cursor: pointer;
  background: var(--ink, #37352F); color: #fff;
}
.stm-ic-ok:hover { opacity: 0.9; }
.stm-ic-clear {
  display: block; width: 100%; text-align: left;
  padding: 8px 14px; font-size: 12.5px; font-family: inherit;
  color: var(--muted, #9B9A93);
  background: transparent; border: none;
  border-top: 1px solid var(--rule, rgba(55,53,47,0.10));
  cursor: pointer;
}
.stm-ic-clear:hover { background: var(--bg, #FCFCFA); }

/* Списки в форме события — та же оболочка, что у статусов: строка, счётчик,
   корзина. Отличие одно и оно по делу — значение здесь не правится на месте,
   потому что переименование в справочнике не переименует его в событиях. */
.stm-group + .stm-group { margin-top: 20px; }
.stm-group-title {
  font-size: 11px; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--muted, #9B9A93); margin-bottom: 8px;
}
.stm-empty { font-size: 13px; color: var(--muted, #9B9A93); padding: 2px 2px 4px; }

.model-profile {
  display: flex; flex-direction: column;
  gap: 8px; padding: 10px;
  margin: 4px 4px 8px;
  background: var(--surface, #FFFFFF);
  border: 1px solid var(--rule, rgba(55,53,47,0.09));
  border-radius: var(--radius, 6px);
}
.model-avatar {
  width: 36px; height: 36px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--sans, 'Inter', sans-serif);
  font-size: 13px; font-weight: 600; color: #fff;
}
.model-profile-name {
  font-family: var(--sans, 'Inter', sans-serif);
  font-size: 14px; font-weight: 600; line-height: 1.2;
  color: var(--ink, #37352F);
}
.model-stats { display: flex; flex-direction: column; gap: 4px; margin-top: 4px; }
.model-stat {
  display: flex; justify-content: space-between;
  font-size: 12px; color: var(--ink-soft, #5A584F);
}
.model-stat-label { color: var(--muted, #9B9A93); }
.model-stat-value { font-weight: 600; font-variant-numeric: tabular-nums; }

.upcoming-list { display: flex; flex-direction: column; gap: 2px; padding: 0 4px; }
.upcoming-item {
  padding: 6px 8px; border-radius: var(--radius-sm, 4px);
  background: transparent;
  border-left: 2px solid var(--rule-strong, rgba(55,53,47,0.16));
}
.upcoming-item-date {
  font-size: 11px; color: var(--muted, #9B9A93);
  margin-bottom: 2px; font-variant-numeric: tabular-nums;
}
.upcoming-item-title { font-size: 12px; font-weight: 500; color: var(--ink, #37352F); }

/* ── MAIN ── */
.main {
  display: flex; flex-direction: column;
  overflow: hidden; min-width: 0;
  position: relative;
  background: var(--bg, #FFFFFF);
}

/* ── TOPBAR ── */
/* Правый отступ — `--pad-x` плюс место под полосу прокрутки (`--sbw`, меряется
   в 04-ui.js). Сетка календаря стоит в прокручиваемом контейнере и отбита от
   правого края на padding плюс ширину полосы; шапка не прокручивается, и без
   этой добавки «Выйти», «+ Событие» и правая граница сетки давали три разных
   правых края на одном экране. Ровно про это и написано «выравнивание» в
   issue. Запасной 0px — для браузера с наложенной полосой (macOS) и для того
   часа, пока замер ещё не приехал.

   Слева — тот же `--pad-x`, что у сетки под шапкой: заголовок месяца стоит
   первым в полосе, и его левый край — это левый край продукта. Восемнадцать
   пикселей, стоявшие здесь, оставляли «Август» левее и края календаря, и
   кнопки «Вид» под ним — то есть единственная крупная строка экрана не стояла
   ни над чем. Ровно это и обвели в issue #183. Отступ один на всю шапку,
   потому что полоса одна на весь продукт, а `--pad-x` — общая рамка его
   экранов. */
.topbar {
  display: flex; align-items: center;
  padding: 0 calc(var(--pad-x) + var(--sbw, 0px)) 0 var(--pad-x);
  border-bottom: 1px solid var(--rule, rgba(55,53,47,0.09));
  gap: 8px; height: var(--topbar-h);
  background: var(--bg, #FFFFFF);
  flex-shrink: 0;
  position: relative; /* positioning context for the mobile search overlay */
}
.topbar-spacer { flex: 1; }

.tb-btn {
  background: transparent;
  border: 1px solid transparent;
  padding: 4px 8px;
  font-size: 13px;
  color: var(--ink-soft, #5A584F);
  border-radius: var(--radius-sm, 4px);
  display: inline-flex; align-items: center;
  gap: 5px; height: 28px; flex-shrink: 0;
  cursor: pointer;
  font-family: inherit;
  transition: background .12s, color .12s;
}
.tb-btn:hover { background: var(--hover, #EFEEEC); color: var(--ink, #37352F); }
.tb-btn.icon {
  padding: 0; width: 28px;
  justify-content: center; font-size: 15px;
}
.tb-btn.icon svg { width: 16px; height: 16px; display: block; }
.hamburger-btn { font-size: 15px; }

/* live clock chip */
.now-chip {
  display: inline-flex; align-items: center; gap: 7px;
  height: 28px; font-size: 12.5px; color: var(--ink-soft, #5A584F);
  font-variant-numeric: tabular-nums;
  padding: 0 10px; white-space: nowrap;
  flex-shrink: 0; /* never squish into neighbouring icons */
  background: none; border: 0; border-radius: 14px; font-family: inherit;
}
.now-chip:hover { background: var(--hover, #F2F1EE); }
/* Часы рядом со временем: чип открывает мировые часы, и это должно быть видно
   до того, как по нему щёлкнут наугад. */
.now-ico { width: 13px; height: 13px; color: var(--muted, #9B9A93); }
.now-chip .pulse {
  width: 7px; height: 7px; border-radius: 50%;
  background: #3AB06A;
  box-shadow: 0 0 0 0 rgba(58,176,106,0.5);
  animation: nowpulse 2.6s ease-out infinite;
}
.now-chip .cap { text-transform: capitalize; }

/* World clocks — revealed on hover (desktop) or tap (mobile) over the date/time. */
.now-wrap { position: relative; display: inline-flex; flex-shrink: 0; }
.now-chip { cursor: pointer; }
.world-clocks-pop {
  display: none; position: absolute; top: calc(100% + 6px); right: 0; z-index: 60;
  min-width: 190px; padding: 6px;
  background: var(--bg, #fff); border: 1px solid var(--rule-strong, #E9E8E5);
  border-radius: var(--radius, 8px); box-shadow: 0 10px 28px rgba(0,0,0,.14);
}
.world-clocks-pop.open { display: block; }
@media (hover: hover) { .now-wrap:hover .world-clocks-pop { display: block; } }
.world-clocks-pop .wc-item {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  padding: 5px 8px; border-radius: var(--radius, 6px);
  font-size: 13px; color: var(--ink, #37352F); font-variant-numeric: tabular-nums;
}
.world-clocks-pop .wc-item:hover { background: var(--hover, #F2F1EE); }
.world-clocks-pop .wc-city { color: var(--muted, #8B8680); }
.world-clocks-pop .wc-time { font-weight: 500; }
@keyframes nowpulse {
  0%   { box-shadow: 0 0 0 0 rgba(58,176,106,0.45); }
  70%  { box-shadow: 0 0 0 7px rgba(58,176,106,0); }
  100% { box-shadow: 0 0 0 0 rgba(58,176,106,0); }
}

/* search with icon — свёрнут до лупы, разворачивается по щелчку (см. toggleSearch) */
.search-wrap { position: relative; display: none; align-items: center; flex: 0 1 260px; min-width: 190px; }
.view-row.search-open .search-wrap { display: flex; }
.view-row.search-open .search-toggle { display: none; }
.view-row.search-open .search-close { display: inline-flex; align-items: center; }
.search-wrap .search-ic {
  position: absolute; left: 9px; width: 14px; height: 14px;
  color: var(--muted, #9B9A93); pointer-events: none;
  display: inline-flex; align-items: center; justify-content: center;
}
.search-wrap .search-ic svg { width: 14px; height: 14px; display: block; }

/* "+ Событие" CTA.

   Синяя, а не чёрная (issue #166): чёрных кнопок в шапке было две — «Вид» и
   эта, — и одинаковым весом они спорили за то, какая из них главная. Цвет взят
   из семьи подсветки дня под курсором (`--day-hover-ink`), а не из `--accent`:
   акцент — цвет фокуса и ссылок, а эта кнопка обещает событие в том дне, на
   который наведена мышь, и обещание читается тем, что цвет один.

   Белый текст написан цветом, а не `var(--bg)`: на тёмной теме `--bg` — почти
   чёрный, и заливка перестала быть той, что инвертируется вместе с темой. */
.btn-new {
  /* Номер стоит на самой кнопке, а не на подсказке под ней, и это не
     перестраховка. Наведение красит кнопку `filter`, а `filter` заводит
     собственный контекст наложения: подсказка внутри него считается своим
     номером относительно кнопки, а сама кнопка остаётся `auto` — то есть ниже
     липкой строки дней (`.day-headers`, z-index 10), и от подсказки видно
     верхние пять пикселей. Выше строки дней и ниже меню шапки (`.cal-pop`, 60). */
  position: relative; z-index: 20;
  background: var(--day-hover-ink, #2470C6); color: #fff;
  border: none; padding: 0 12px; height: 30px;
  font-size: 13px; font-weight: 600;
  border-radius: 15px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  transition: filter .14s, transform .14s, box-shadow .14s; font-family: inherit; flex-shrink: 0;
}
.btn-new:hover { filter: brightness(1.15); }
.btn-new svg { display: block; }
/* Плашки с ⌘K под кнопкой больше нет: кнопка открывает меню, а ⌘K по-прежнему
   открывает форму (04-ui.js), и подсказка под меню обещала бы не то. Шорткат
   написан там, куда он и ведёт, — строкой «Вручную» в самом меню. */

.search-toggle { display: inline-flex; position: relative; }

/* Подсказка под кнопкой шапки: что кнопка делает и какой клавишей. Лупа —
   единственное действие в полосе, у которого шорткат есть, а показать его было
   негде: ⌘/ живёт в поле поиска и виден только вместе с открытым полем, ⌘K —
   строкой в меню «Событие», а ⌘O не был написан нигде, кроме командного меню,
   куда за ним никто не пойдёт (issue #248).

   Появляется по наведению и по `:focus-visible`: клавиатурный совет, который
   показывают только мышью, достаётся не тем, кому он нужнее всего. И то и
   другое — внутри `hover: hover`, потому что на телефоне подсказка обещала бы
   клавишу, которой не к чему подключить (та же причина, что у `.kbd-hint`
   ниже по файлу).

   `pointer-events: none` обязателен: подсказка висит под кнопкой и накрывает
   сетку календаря, а щелчок по ней — щелчок по дню, которого не хотели. */
.tb-tip {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 60;
  display: flex; align-items: center; gap: 4px;
  padding: 5px 8px; white-space: nowrap;
  font-size: 12px; line-height: 1; color: var(--ink-soft, #5A584F);
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--radius-sm, 4px); box-shadow: var(--shadow-menu);
  opacity: 0; visibility: hidden; transform: translateY(-3px);
  transition: opacity .12s, transform .12s, visibility .12s;
  pointer-events: none;
}
.tb-tip .kbd { height: 17px; min-width: 16px; padding: 0 4px; font-size: 10.5px; }
@media (hover: hover) {
  .search-toggle:hover .tb-tip,
  .search-toggle:focus-visible .tb-tip {
    opacity: 1; visibility: visible; transform: none;
  }
}
.search-close {
  display: none;
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  background: transparent; border: none; cursor: pointer;
  color: var(--muted, #9B9A93); font-size: 15px; line-height: 1;
  padding: 4px 6px; flex-shrink: 0;
}

/* legacy topbar-btn alias */
.topbar-btn {
  background: transparent; border: 1px solid transparent;
  padding: 4px 8px; font-size: 13px;
  color: var(--ink-soft, #5A584F);
  border-radius: var(--radius-sm, 4px);
  cursor: pointer; height: 28px;
  font-family: inherit;
}
.topbar-btn:hover { background: var(--hover, #EFEEEC); color: var(--ink, #37352F); }

/* search */
.search-input {
  border: 1px solid var(--rule-strong);
  background: var(--surface);
  padding: 0 62px 0 28px;
  font-family: inherit; font-size: 13px;
  color: var(--ink, #37352F);
  width: 100%; height: 30px;
  outline: none; border-radius: var(--radius-sm, 4px);
  transition: border-color .15s, box-shadow .15s;
}
.search-input:focus {
  border-color: var(--accent, #2383E2);
  box-shadow: var(--focus-ring, 0 0 0 2px rgba(35,131,226,0.18));
}

/* ── KBD atom + ⌘K hint pill (v7) ── */
.kbd {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 19px; padding: 0 5px;
  font-family: var(--sans); font-size: 11px; font-weight: 600; color: var(--muted);
  background: var(--surface-2);
  border: 1px solid color-mix(in oklch, var(--rule-strong) 70%, transparent);
  border-radius: 5px;
  box-shadow: 0 1px 0 color-mix(in oklch, var(--rule-strong) 50%, transparent);
  line-height: 1; font-variant-numeric: tabular-nums;
}
/* Крестик и подсказка стоят в одном углу поля, поэтому оба позиционированы, а
   не сложены потоком: абсолютная подсказка легла бы поверх крестика. */
.kbd-hint {
  display: inline-flex; align-items: center; gap: 3px;
  position: absolute; right: 34px; top: 50%; transform: translateY(-50%);
  cursor: pointer; pointer-events: auto;
  background: transparent; border: none; padding: 0;
}
.kbd-hint .kbd { background: var(--bg); }

/* ── COMMAND PALETTE (⌘K) ── */
.cmd-overlay {
  position: fixed; inset: 0; z-index: 500;
  display: flex; align-items: flex-start; justify-content: center;
  padding: 14vh 20px 20px;
  background: color-mix(in oklch, var(--ink) 28%, transparent);
  backdrop-filter: blur(4px) saturate(1.1);
  -webkit-backdrop-filter: blur(4px) saturate(1.1);
  animation: cmdFade .14s ease;
}
@keyframes cmdFade { from { opacity: 0; } to { opacity: 1; } }
.cmd-panel {
  width: min(560px, 94vw); max-height: 64vh;
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius);
  box-shadow: var(--shadow-modal); overflow: hidden;
  transform-origin: top center; animation: cmdRise .2s cubic-bezier(.22,.61,.36,1);
}
@keyframes cmdRise { from { opacity: 0; transform: translateY(-12px) scale(.985); } to { opacity: 1; transform: none; } }
.cmd-search { display: flex; align-items: center; gap: 10px; height: 50px; padding: 0 14px; border-bottom: 1px solid var(--rule); position: relative; flex-shrink: 0; }
.cmd-search-ic { width: 16px; height: 16px; color: var(--muted); flex-shrink: 0; display: inline-flex; }
.cmd-search-ic svg { width: 16px; height: 16px; }
.cmd-input { flex: 1; border: none; background: transparent; outline: none; font-size: 14.5px; color: var(--ink); font-weight: 400; font-family: inherit; }
.cmd-input::placeholder { color: var(--muted); }
.cmd-list { overflow-y: auto; padding: 6px; min-height: 0; }
.cmd-empty { padding: 30px 10px; text-align: center; color: var(--muted); font-size: 13.5px; }
.cmd-group { margin-bottom: 2px; }
.cmd-group-h { font-size: 11px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); padding: 9px 10px 4px; }
.cmd-row {
  display: flex; align-items: center; gap: 11px; width: 100%;
  border: none; background: transparent; text-align: left;
  height: 36px; padding: 0 10px; border-radius: var(--radius-sm); cursor: pointer;
  transition: background .08s; font-family: inherit;
}
.cmd-row.active { background: var(--hover); }
/* Иконка команды без подложки: в списке из десяти строк десять серых квадратов
   читаются как сетка, а не как иконки. */
.cmd-ic {
  width: 16px; height: 16px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted);
  font-size: 11px; font-weight: 700;
}
.cmd-ic svg { width: 16px; height: 16px; }
.cmd-row.active .cmd-ic { color: var(--ink); }
.cmd-label {
  flex: 1; min-width: 0; font-size: 13.5px; color: var(--ink); font-weight: 400;
  display: flex; align-items: baseline; gap: 8px; overflow: hidden;
}
.cmd-sub { font-size: 12px; color: var(--muted); font-weight: 400; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cmd-row .kbd { opacity: 0; transition: opacity .1s; }
.cmd-row.active .kbd { opacity: 1; }
.cmd-foot { display: flex; align-items: center; gap: 16px; padding: 9px 16px; border-top: 1px solid var(--rule); flex-shrink: 0; background: var(--surface-2); }
.cmd-foot span { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--muted); }
@media (prefers-reduced-motion: reduce) {
  .cmd-overlay, .cmd-panel { animation: none !important; }
}
.search-input::placeholder { color: var(--muted, #9B9A93); }

.user-badge {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--muted, #9B9A93);
  padding: 0 6px;
}
.user-badge-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }

/* ── PAGE HEAD (month title, inside the topbar) ── */
/* Заголовок стоит в верхней полосе, слева, на месте прежнего гамбургера.
   Своей строки у него больше нет: она держала 34-пиксельный «Август» и
   отодвигала календарь вниз на пустой ряд. Кегль поэтому меньше — полоса
   ровно 52px и общая с часами и «Выйти», а её высота связана с полосой
   логотипа в сайдбаре: линия под ними — одна на весь экран. */
.page-head {
  display: flex; align-items: center;
  gap: 10px; flex-shrink: 0; min-width: 0;
}
.page-head-l { display: flex; flex-direction: column; gap: 6px; }
.page-head-title { display: flex; align-items: baseline; gap: 8px; }
/* Кегль написан один раз на два заголовка: «Август» на календаре и «База
   моделей» на моделях стоят в одной и той же полосе на разных экранах, и
   разойдясь в размере, читались бы как заголовки разного веса у одного
   продукта. Курсор и наведение остались у месяца: по нему щёлкают, чтобы
   выбрать другой, а название экрана никуда не ведёт. */
.page-head-title h1,
.screen-head {
  font-family: var(--display, 'Inter', sans-serif);
  font-size: 24px;
  font-weight: 700; line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ink, #37352F);
  margin: 0;
  white-space: nowrap;
}
.page-head-title h1 { cursor: pointer; }
.page-head-title h1:hover { color: var(--ink-soft, #5A584F); }
/* Длинное название сжимается, а не выталкивает часы и «Выйти» за край окна. */
.screen-head { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* Год того же кегля, что и месяц, но приглушённый: «Август 2026» — одна
   строка, в которой спрашивают месяц, а год лишь уточняет. */
.page-head-title .yr {
  font-size: 24px; font-weight: 600; line-height: 1;
  letter-spacing: -0.02em;
  color: var(--muted, #9B9A93);
  font-variant-numeric: tabular-nums;
}
[data-type="editorial"] .page-head-title h1,
[data-type="editorial"] .screen-head { font-weight: 400; }

.month-nav { display: flex; gap: 2px; align-items: center; flex-shrink: 0; }
.icon-btn {
  width: 26px; height: 26px; border: none;
  background: transparent;
  border-radius: var(--radius-sm, 4px);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-soft, #5A584F);
  font-size: 16px; cursor: pointer;
  font-family: inherit;
  transition: background .12s, color .12s;
}
.icon-btn:hover { background: var(--hover, #EFEEEC); color: var(--ink, #37352F); }
.nav-btn { /* legacy */
  width: 28px; height: 28px; border-radius: var(--radius-sm, 4px);
  border: none; background: transparent;
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-soft, #5A584F); font-size: 14px; line-height: 1;
}
.nav-btn:hover { background: var(--hover, #EFEEEC); color: var(--ink, #37352F); }
.today-btn {
  border: 1px solid var(--rule-strong);
  background: var(--bg, #FFFFFF);
  padding: 0 14px; font-size: 12.5px;
  color: var(--ink-soft, #5A584F);
  border-radius: 14px;
  font-weight: 500; height: 28px;
  cursor: pointer; font-family: inherit; margin: 0 4px;
  transition: background .14s, color .14s;
}
.today-btn:hover { background: var(--hover, #EFEEEC); color: var(--ink, #37352F); }
/* Search and «+ Событие» share the view-toggle row: they act on the same
   calendar the toggles switch. */
.page-head-actions { display: flex; align-items: center; gap: 10px; flex-shrink: 0; margin-left: auto; }

/* Hide calendar-only sidebar sections (Вид/Статус) when not in calendar mode.
   Документооборот was left out of this list when it was added and showed the
   calendar's filters over a table of visas; Финансы is here from the start. */
body.mode-today .sb-cal-only,
body.mode-db .sb-cal-only,
body.mode-mail .sb-cal-only,
body.mode-docs .sb-cal-only,
body.mode-finance .sb-cal-only,
body.mode-wiki .sb-cal-only { display: none !important; }


/* ── VIEW TOGGLE (pill chips · v6) ── */
.view-row {
  display: flex; align-items: center; gap: 16px;
  border-bottom: 1px solid var(--rule, rgba(55,53,47,0.09));
  /* Слева — ровно `--pad-x`, как у сетки под ней: левый край кнопки «Вид»
     стоит на левой границе календаря. Справа — плюс полоса прокрутки, см.
     `.topbar` выше, плюс ширина открытой боковой панели (`--side-pad`, правило
     под этим). */
  padding: 8px calc(var(--pad-x, 24px) + var(--sbw, 0px) + var(--side-pad, 0px)) 10px var(--pad-x, 24px);
  /* Едет тем же движением, что и сама панель (`.side-panel`): «должны
     незаметно сдвигаться» из issue — это про то, что край шапки и край сетки
     трогаются одновременно, а не догоняют друг друга. */
  transition: padding-right .3s cubic-bezier(.5,0,.2,1);
  margin-top: 4px;
  flex-shrink: 0;
  position: relative; /* positioning context for the mobile search overlay */
}
/* Полоса с «Видом», лупой, «…» и «+ Событие» стоит НАД `.content-area`, то есть
   над сеткой и боковой панелью сразу. Панель отбирает ширину у сетки, а полоса
   этого не замечала — и правый край шапки оставался у края окна, то есть уезжал
   за правый край календаря ровно на ширину панели. Это и обвели в issue #154.

   `:has()`, а не класс на `body`: «панель открыта» уже записано — классом на
   самой панели, — и второе написание того же факта разъезжается там, где панель
   закрывают не своей рукой (смена вида, уход в Модели, Escape, узкий экран).
   Браузер без `:has()` теряет сдвиг и ведёт себя ровно как до правки.

   Держится это на всех ширинах, кроме одной узкой полосы: примерно с 1000 до
   1120 пикселей окна, когда развёрнуто поле поиска. Там правая группа вместе с
   полем просто не помещается в остаток строки рядом с панелью — это
   арифметика, а не правило, — и группа остаётся стоять, ровно как стояла до
   этой правки. Ниже 1000px продукт на ту же тесноту отвечает сам: поле
   накрывает полосу целиком (см. блок 1000px). Порога «панель открыта, значит
   считай окно на 520 уже» здесь нет намеренно: это число пришлось бы вывести
   из ширин кнопок правой группы и переписывать при каждой правке шапки — а
   молча устаревший порог хуже, чем названная граница.

   Читателей у числа с issue #167 двое: полоса шапки и само содержимое под ней.
   Панель ушла из потока (она тянется до самого верха, см. `.side-panel`), и
   ширину ей теперь освобождает отступ, а не место соседки. Объявление всё равно
   одно: два правила с одним `--side-pad` разъехались бы на первой же правке
   ширины, и разъехались бы наполовину — сетка поехала бы не туда, куда шапка. */
.app:has(.side-panel.open) .view-row,
.app:has(.side-panel.open) .content-area { --side-pad: var(--panel-w, 0px); }
/* Ряда из шести вкладок больше нет — он и был той теснотой, ради которой тут
   стояла горизонтальная прокрутка: шестьсот пикселей вкладок вместе с полем
   поиска и «+ Событие» не помещались уже в ноутбучное окно, и за краем
   оказывался то «+ Событие», то крестик, закрывающий поиск. Теперь на его
   месте одна кнопка «Вид», а список — в выпадающем меню. */
.vt-btn {
  border: 1px solid transparent; background: transparent;
  flex-shrink: 0;
  height: 28px; padding: 0 14px; font-size: 13px;
  color: var(--muted, #9B9A93);
  margin-bottom: 0;
  font-weight: 500;
  display: inline-flex; align-items: center; gap: 6px;
  cursor: pointer; font-family: inherit;
  white-space: nowrap;
  box-shadow: none;
  border-radius: 999px;
  transition: background .14s, color .14s, box-shadow .14s, border-color .14s;
}
/* Высота и скругление — те же, что у «Сегодня» в полосе над ней (issue #166):
   две пилюли в шапке одного экрана, отличающиеся на шесть пикселей, читаются
   как две разные кнопки, а не как одна полоса управления календарём. Радиус —
   половина высоты, иначе пилюля превращается в прямоугольник со скруглениями. */
.vt-btn { border-radius: 14px; }
/* Правил `.vt-btn.active`, `.vt-btn .ic` и `.vt-underline` здесь больше нет, и
   это не уборка ради уборки: `.vt-btn` носит ровно одна кнопка в продукте —
   «Вид», — у неё нет ни значка, ни состояния «активна» (активный вид отмечен
   галочкой в меню). Правило, которое не матчится ни на что, читается как
   работающее и переживает следующую правку молча. */

/* ── МЕНЮ ШАПКИ КАЛЕНДАРЯ: «Вид» и «…» ──
   Какие виды показывать модели, решает теперь `ViewMenu.available()`, а не
   правило `body[data-role="model"] #vt-…`, которое стояло здесь: спрятанная
   стилями кнопка продолжала открываться цифрой и строкой командного меню, то
   есть прятала дверь, а не закрывала её. */
.cal-menu-wrap { position: relative; display: inline-flex; }
/* Кнопка «Вид» — прозрачная, с одним словом по центру и без значков (issue
   #179). Чёрной она была с #152 и там же была объявлена главной кнопкой шапки;
   чёрных кнопок в полосе с тех пор стало две — «Вид» и «+ Событие», — и
   одинаковым весом они спорили за то, какая из них главная. Спор решён в
   пользу той, которая заводит съёмку: заливка осталась у неё одной, а «Вид»
   отвечает теперь тем же, чем отвечает «Сегодня» рядом, — словом и рукой.

   Слово стоит по центру коробки, и это отменяет выравнивание над «Пн» из #152:
   там оно держалось на том, что содержимое флекса прижато к началу строки, а
   ширину кнопке добирал `min-width` вправо. Отменено по прямой просьбе — «Вид»
   над «Пн» читалось как подпись к первому столбцу, а не как кнопка. Отступ
   при этом остался тем же, что у `.day-header`: он больше ничего не выравнивает
   (при центрировании ширину решает `min-width`), но и разъезжаться с колонкой
   ему незачем — левый край коробки по-прежнему совпадает с краем сетки. С
   issue #183 это уже не совпадение двух чисел, а одно число: `--day-pad-x`,
   то самое, которым отбито от края дня всё, что в дне стоит.

   Рамка у неё теперь есть, и взята она у «Сегодня» (issue #190: «сделай
   обводку у кнопки вид по образцу кнопки сегодня»). Тем же токеном
   `--rule-strong` и с тем же радиусом 14px, который `.vt-btn` даёт и так: две
   кнопки одной высоты, стоящие в одной полосе, обязаны быть обведены
   одинаково, иначе разная толщина штриха читается как разное назначение.
   Высота от обводки не поехала — пиксель рамки у `.vt-btn` был и до этого,
   только прозрачный, и `box-sizing: border-box` держал под него место всё
   это время.

   Ширина — на 0,5 мм меньше с каждой стороны, тоже дословно из issue: 96px
   минус миллиметр на двоих. Написано это `calc`, а не готовым числом, потому
   что миллиметр здесь — просьба, а не результат: 92px рядом с 96px читается
   как подобранное на глаз, и следующая правка подберёт своё. */
.vt-menu-btn {
  border: 1px solid var(--rule-strong);
  color: var(--text);
  background: transparent;
  padding: 0 var(--day-pad-x);
  min-width: calc(96px - 1mm);
  justify-content: center;
  font-weight: 600;
}
.vt-menu-btn:hover { background: var(--surface-2); color: var(--text); }

/* ── ЗАКРЕПЛЁННЫЕ ВИДЫ ──

   Чипы между «Видом» и «+»: вид, к которому возвращаются каждый день, стоит
   рядом кнопкой и открывается одним щелчком, а не двумя через меню. Набор
   личный (localStorage, см. `ViewPins`), поэтому и рисуется он здесь, а не
   приходит из настроек агентства.

   «Незаметная кнопка +» из issue — это про вес, а не про прятки: «+» стоит на
   месте всегда и просто не спорит с чёрным «Видом» рядом. Спрятанная по
   наведению, она была бы дверью, которую не найти, — то же самое, что кнопка,
   которой нет. */
.vt-pin-row { display: flex; align-items: center; gap: 6px; min-width: 0; }
/* Чипы — единственное в этой полосе, что можно подвинуть: «Вид» и правая
   группа отвечают за действия, а чип — короткий путь к тому, что и так
   открывается меню. Поэтому сжимается здесь не подпись, а сам ряд: чип держит
   свою ширину (`flex-shrink: 0`), а не влезшие уезжают за край и достаются
   прокруткой. Сжимались они до этого поровну и превращались в двадцать
   пикселей на каждый — шесть закреплённых видов на ноутбуке с открытой
   панелью давали шесть огрызков, а огрызок читается как поломка, а не как «не
   влезло». Полоса прокрутки спрятана по той же причине, что у списка имён в
   боковой колонке: пятнадцать пикселей полосы стоили бы здесь целого чипа. */
.vt-pins {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
}
.vt-pins::-webkit-scrollbar { display: none; }
/* Поля симметричные, а крестик вынут из потока: подпись стоит ровно посередине
   своей таблетки, пока на неё не навели, — а именно эта таблетка и подсвечена
   как текущий вид, то есть смещение на ней видно лучше всего (issue #284).
   Раньше место под крестик было занято всегда, и подпись из-за этого сидела
   левее центра постоянно. */
.vt-pin {
  position: relative;
  display: inline-flex; align-items: center; flex-shrink: 0;
  height: 28px; padding: 0 16px; max-width: 170px; min-width: 0;
  border-radius: 999px; background: transparent;
  color: var(--muted); font-size: 13px; font-weight: 500;
  transition: background .14s, color .14s;
}
.vt-pin:hover { background: var(--hover); }
/* Текущий вид отмечен и здесь, и галочкой в меню — это один и тот же факт,
   написанный там, где на него смотрят. */
.vt-pin.active { background: var(--hover); color: var(--ink); font-weight: 600; }
.vt-pin-go {
  border: 0; background: transparent; color: inherit; font: inherit;
  padding: 0; cursor: pointer; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; min-width: 0;
  transition: transform .12s;
}
/* Крестик появляется под рукой, и подпись под него отъезжает влево — но только
   на том чипе, где уже стоит мышь. Двигаться при этом нечему, кроме подписи:
   крестик лежит `absolute`, ширина таблетки от него не зависит, и соседние
   чипы на наведении не едут. Постоянно видимым он быть не может — «закрыть
   вид» и «открепить» разные вещи. */
.vt-pin-off {
  position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
  border: 0; background: transparent; color: var(--muted);
  font: inherit; font-size: 11px; line-height: 1;
  width: 16px; height: 16px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; opacity: 0; transition: opacity .12s, background .12s;
}
.vt-pin:hover .vt-pin-off, .vt-pin-off:focus-visible { opacity: 1; }
.vt-pin-off:hover { background: var(--rule); color: var(--ink); }
/* Сдвиг подписи повторяет условие показа крестика, а не берёт `:focus-within`:
   табом по чипу первой ловится сама подпись, и на ней ехать некуда — крестик
   ещё не показан, а место он занял бы. Браузер без `:has()` теряет сдвиг и
   получает то же, что было до правки: крестик поверх хвоста подписи. */
.vt-pin:hover .vt-pin-go,
.vt-pin:has(.vt-pin-off:focus-visible) .vt-pin-go { transform: translateX(-10px); }
.vt-pin-add {
  border: 0; background: transparent; color: var(--muted);
  font: inherit; font-size: 17px; line-height: 1;
  width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; transition: background .14s, color .14s;
}
.vt-pin-add:hover, .vt-pin-add[aria-expanded="true"] { background: var(--hover); color: var(--ink); }

.cal-pop {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 60;
  min-width: 210px; max-width: 320px;
  max-height: min(70vh, 520px); overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 10px;
  box-shadow: 0 8px 28px rgba(0,0,0,.14);
  padding: 6px;
}
/* Меню «…» выпадает от правого края: оно стоит у самой кнопки «+ Событие»,
   и раскрывшись влево, уезжало бы за экран. */
.cal-pop-wide { left: auto; right: 0; min-width: 300px; }
/* Меню новых событий висит на самой правой кнопке шапки — от левого края оно
   выпадало бы за экран целиком, а не наполовину. Ширины ему хватает своей: в
   нём четыре строки, а не список стран. */
.cal-pop-end { left: auto; right: 0; }

.menu-row {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 7px 9px; border: 0; border-radius: 7px;
  background: transparent; color: var(--ink);
  font: inherit; font-size: 13px; text-align: left; cursor: pointer;
}
.menu-row:hover { background: var(--hover); }
/* Цифра в строке меню — подсказка, а не клавиша на картинке (issue #182).
   `.kbd` рисует клавишу: рамка, подложка, тень под нижним краем. В командной
   палитре это к месту — там цифра появляется у выбранной строки и говорит
   «нажми», — а в меню «Вид» шесть таких коробочек встали столбиком у правого
   края и читались как второй список рядом с первым: сначала глаз находил
   ряд серых квадратов, потом названия видов. Шорткат при этом остаётся
   написанным — просто настолько тихо, насколько тихо его и спрашивают. */
.menu-row .kbd {
  background: none; border: none; box-shadow: none;
  color: var(--muted-soft); font-weight: 500; padding: 0; min-width: 12px;
}
.menu-row.active { background: var(--hover); font-weight: 600; }
.menu-row-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-row-action { color: var(--accent); }
/* Меню строки боковой колонки. `fixed`, а не `absolute` от строки: список имён
   прокручивается со своим `overflow`, и меню, растущее внутри него, обрезалось
   бы по нижнему краю ровно у тех строк, до которых надо прокрутить. Координаты
   считает `RowMenu`, потому что от края окна оно должно отталкиваться вверх. */
.sb-row-pop { position: fixed; top: 0; left: 0; min-width: 160px; }
.menu-row-danger { color: var(--danger, #C4453C); }
.menu-row-danger:hover { background: var(--danger-bg, rgba(196,69,60,.10)); }
/* Галочка занимает место всегда — иначе строки прыгают вбок при включении. */
.menu-tick { width: 12px; text-align: center; color: transparent; flex-shrink: 0; }
.menu-check.checked .menu-tick { color: var(--accent); }
.menu-warn { color: #C4632F; font-weight: 700; flex-shrink: 0; }
.menu-head {
  padding: 6px 9px 4px; font-size: 11px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--muted);
}
/* Заголовок раздела с кнопкой в правом углу: «+» стоит там же, где у любого
   списка, в который добавляют, — на уровне названия списка, а не строкой под
   ним, где он читался бы шестой страной. */
.menu-head-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.menu-head-add {
  border: 0; background: transparent; color: var(--muted);
  font: inherit; font-size: 15px; line-height: 1;
  width: 20px; height: 20px; border-radius: var(--radius-sm, 4px);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; flex-shrink: 0;
}
.menu-head-add:hover { background: var(--hover); color: var(--ink); }
.menu-sep { height: 1px; background: var(--rule); margin: 6px 4px; }
.menu-note { padding: 4px 9px 8px; font-size: 12px; color: var(--muted); line-height: 1.45; }
.menu-empty { padding: 8px 9px; font-size: 12px; color: var(--muted); }
.menu-err { padding: 4px 2px 0; font-size: 12px; color: #C4632F; }
.menu-actions, .menu-form-actions { display: flex; gap: 6px; padding: 2px 7px 6px; flex-wrap: wrap; }
/* Три действия над ссылкой iCal — значками одного цвета. Надписями они занимали
   две трети меню и весом спорили со списком стран, ради которого его открывают;
   а «Отозвать» рядом с «Скопировать» одинаковой кнопкой — это ещё и два разных
   по цене действия, выглядящих одинаково важными. Подпись у каждого в `title`
   и `aria-label`, наведение — единственное, что их различает цветом. */
.menu-icon-actions { display: flex; gap: 2px; padding: 4px 2px 4px; }
.menu-icon-btn {
  border: 0; background: transparent; color: var(--muted);
  width: 26px; height: 26px; border-radius: var(--radius-sm, 4px);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; padding: 0; flex-shrink: 0;
}
.menu-icon-btn svg { width: 15px; height: 15px; display: block; }
.menu-icon-btn:hover { background: var(--hover); color: var(--ink); }
.menu-btn {
  border: 1px solid var(--rule); border-radius: 7px;
  background: var(--ink); color: var(--bg);
  font: inherit; font-size: 12px; padding: 5px 10px; cursor: pointer;
}
.menu-btn-quiet { background: transparent; color: var(--ink); }
.menu-btn-quiet:hover { background: var(--hover); }
.menu-form { padding: 4px 7px 8px; display: flex; flex-wrap: wrap; gap: 6px; }
.menu-input {
  border: 1px solid var(--rule); border-radius: 7px;
  background: var(--bg); color: var(--ink);
  font: inherit; font-size: 12px; padding: 6px 8px; width: 84px;
}
.menu-input-wide { width: 100%; }
.menu-input:focus { outline: none; border-color: var(--accent); }
.menu-subs { padding: 2px 0 4px; }
.menu-sub-row { display: flex; align-items: center; gap: 6px; padding: 4px 9px; font-size: 12px; }
.menu-sub-meta { color: var(--muted); font-size: 11px; flex-shrink: 0; }
.menu-x {
  border: 0; background: transparent; color: var(--muted);
  cursor: pointer; font: inherit; font-size: 12px; padding: 0 2px;
}
.menu-x:hover { color: #C4632F; }

/* ── ПОДПИСЬ ПРОИЗВОДСТВЕННОГО КАЛЕНДАРЯ ──
   `JP · New Year` серым текстом на фоне календаря — ни полоски, ни заливки, ни
   цвета страны. Так просил букер, и просьба про то же, про что весь этот блок
   был с самого начала: подпись обязана не выглядеть съёмкой, иначе клетка на
   Golden Week читается как семь занятых дней. Цветная плашка отвечала на вопрос
   «чей это праздник» — но на него отвечает и код страны в самой подписи, а
   платила она весом, которого у справки быть не должно.

   Рабочая суббота (китайское 调休) — курсивом: это обратный по смыслу день, и
   различать его надо, а различать больше нечем. */
.holiday-chip {
  font-size: 10.5px; line-height: 1.3;
  color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  flex-shrink: 0;
}
.holiday-chip.holiday-work { font-style: italic; }
/* Место подписи в клетке месяца — левая половина строки с числом (issue #166):
   число у правого края, подпись у левого, обе на одной строке.

   Обёртка нужна, а не подпись прямо во флекс-строке, и это не лишний узел. Во
   флекс-строке `flex-shrink: 0` у подписи значит «не сжимайся по ширине», а не
   «по высоте», как значило, пока подпись была ребёнком колонки-клетки: название
   длиннее клетки раздувалось бы до своей длины и обрезалось `overflow` строки —
   по букве и без многоточия. Названия приезжают из чужого .ics, и «CN ·
   Mid-Autumn Festival Observed» в колонке из семи не помещается на ноутбуке
   вовсе. Внутри обёртки подпись снова блок в блоке, то есть работает ровно так,
   как работала под числом. */
.day-hol { flex: 1 1 auto; min-width: 0; overflow: hidden; }
/* В годовом виде подписи нет — есть подложка под числом. Нейтральная: в сетке
   из двенадцати миниатюр вопрос один — «работают в этот день или нет». */
.year-day[data-hol] {
  background: var(--hover);
  border-radius: 4px;
}

/* ── CONTENT AREA ── */
/* Правый отступ — место под открытую панель, которая с issue #167 стоит над
   содержимым, а не в потоке рядом с ним. Читается тот же `--side-pad`, что и в
   шапке, и с тем же запасным нулём: пока панель закрыта, переменной нет вовсе,
   и весь `var()` без него стал бы невалидным. Едет тем же движением — иначе
   край сетки догоняет край панели. */
.content-area {
  flex: 1; display: flex; overflow: hidden;
  padding-right: var(--side-pad, 0px);
  transition: padding-right .3s cubic-bezier(.5,0,.2,1);
}
.views-wrapper { flex: 1; display: flex; flex-direction: column; overflow: hidden; min-width: 0; }

/* ── MONTH VIEW ── */
/* `scrollbar-gutter: stable` — вторая половина выравнивания правого края (см.
   `.topbar`): шапка отбита от края на ширину полосы всегда, значит и сетка
   обязана держать это место всегда, а не только в месяце, который не влез. Без
   этого переход с длинного месяца на короткий двигал бы правую границу
   календаря на пятнадцать пикселей, а шапка стояла бы на месте. То же и у
   остальных четырёх видов ниже. */
.cal-container {
  flex: 1; overflow-y: auto; scrollbar-gutter: stable;
  display: flex; flex-direction: column;
  padding: 0 var(--pad-x) var(--pad-x);
  background: var(--bg);
}
/* Семь колонок — `minmax(0, 1fr)`, а не `1fr`, и это одно и то же ровно до тех
   пор, пока в клетку что-нибудь не положили. `1fr` — это `minmax(auto, 1fr)`:
   колонка не сжимается ниже минимальной ширины своего содержимого, а минимум у
   клетки дня — это неразрывная строка, приехавшая из данных: «KR · Substitute
   Holiday» из чужого .ics или название съёмки, набранное букером. Строка длиннее
   седьмой части экрана раздувает свою колонку и отнимает ширину у шести
   соседних — а недели считаются семью отдельными гридами, и раздувается поэтому
   не сетка целиком, а одна неделя. Ровно это и видно на скриншоте issue #153:
   пока панель события закрыта, места хватает всем, а стоит ей открыться —
   границы дней в неделе с праздником уезжают от границ в неделе без него.

   Ноль в минимуме снимает и второе следствие того же `auto`: клетка-грид-элемент
   перестаёт требовать себе минимум по содержимому и встаёт ровно в свою колонку.
   Обрезка при этом уже написана там, где ей и место, — `overflow: hidden` у
   `.day-hol` и `.ev-card`, многоточие у `.ev-title-main`.

   Правило одно на все семёрки календаря: шапка дней, неделя месяца, шапка и тело
   вида «Неделя», миниатюры года. Шапка и сетка под ней — вообще два разных грида,
   и разъехаться им достаточно одной длинной подписи в одном из них. */
/* Рамка слева — та же, что у `.cal-grid` под ней, и она здесь не для красоты:
   без неё сетка отбита пикселем своей рамки, а шапка дней — нет, и семь
   колонок шапки стоят на пиксель левее семи колонок сетки. Разъезжаются от
   этого не только подписи с плашками, но и сами вертикальные линии — та, что
   между «Пн» и «Вт», и та, что между клетками под ними. */
.day-headers {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  border-bottom: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
  background: var(--bg);
  position: sticky; top: 0; z-index: 10; flex-shrink: 0;
}
.day-header {
  padding: 8px var(--day-pad-x); text-align: left;
  font-size: 12px; font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--muted);
  border-right: 1px solid var(--rule);
}
.day-header:last-child { border-right: none; }

/* Week-row grid · v6 — fixed cells */
.cal-grid {
  display: flex; flex-direction: column;
  border-left: 1px solid var(--rule);
  border-top: 1px solid var(--rule);
}

/* `minmax(0, 1fr)`, а не `1fr`: у `1fr` минимум — это min-content, и одно
   длинное название растягивало свою колонку вдвое, отбирая ширину у остальных
   шести. Заметно это не в клетке, а в шапке: подписи дней лежат отдельной
   сеткой из семи равных долей, и «Ср» переставало стоять над средой. Название
   при этом обрезается многоточием — `.ev-title-main` умеет это с самого
   начала, только шанса не имел. */
.cal-week {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  position: relative;
}

/* Заливка события: пастель статуса плюс двенадцать процентов его же точки.
   Одно место на обе фигуры месяца — коробку дня и полосу на несколько дней:
   два написания одного цвета разъехались бы на первой же правке.

   Тёмная тема считает заливку своей формулой (см. `body.dark .ev-card` ниже) и
   сюда не смотрит: там пастели нет вовсе — фон рисуется от цвета точки, потому
   что светлый `--ev-bg` на тёмном поле был бы не заливкой, а лампой. */
.cal-bar, .ev-card {
  --ev-fill: color-mix(in oklch, var(--ev-bg, var(--surface-2)) 88%, var(--ev-accent, var(--ev-bg, var(--surface-2))));
}

/* ── Multi-day bars overlay (month view) ── */
/* Слой лежит ровно на семи колонках недели и своего отступа не имеет: проценты
   в `left`/`width` полосы считаются от его ширины, и два пикселя padding'а
   сдвигали каждую полосу на свою долю пикселя относительно колонки, под
   которой она стоит. Отступ от края дня полоса берёт там же, где его берёт
   плашка, — `--day-pad-x` (см. `02-calendar.js`). */
.cal-bars {
  position: absolute;
  left: 0; right: 0;
  pointer-events: none;
  z-index: 4;
}
.cal-bar {
  position: absolute;
  pointer-events: auto;
  box-sizing: border-box;
  display: flex; align-items: center; gap: 7px;
  padding: 5px 9px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  cursor: pointer;
  background: var(--ev-fill);
  color: var(--ev-text, var(--ink));
  border: none;
  transition: left 0.16s cubic-bezier(.22,.61,.36,1),
              width 0.16s cubic-bezier(.22,.61,.36,1),
              filter .14s;
  animation: barIn 0.16s ease both;
}
.cal-bar:hover { filter: brightness(0.97); }
.cal-bar .ev-title-main { flex: none; }
@keyframes barIn { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }
.cal-bar.cont-l { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.cal-bar.cont-r { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.cell-bars-spacer { flex-shrink: 0; pointer-events: none; }

.day-cell {
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  padding: 6px var(--day-pad-x) 8px;
  cursor: pointer;
  background: var(--bg);
  transition: background .08s;
  display: flex; flex-direction: column; gap: 2px;
  min-height: var(--row-h, 116px);
  position: relative;
}
/* Four shades, weakest signal first: a weekend is tinted, a day outside the
   month the header names is greyed and its number muted. The two stack, so a
   weekend in a neighbouring month reads as the quietest cell on the grid. */
.day-cell.weekend { background: var(--cal-we); }
.day-cell.off-month { background: var(--cal-off); }
.day-cell.off-month.weekend { background: var(--cal-off-we); }
.day-cell.off-month .day-num { color: var(--muted-soft); }
.day-cell.off-month .day-month { color: var(--muted-soft); }
/* Doubled class so hover and selection outrank the two-class shading above.

   Наведение красится не серым, а голубым (`--day-hover`), и это не вкусовщина:
   четыре оттенка серого выше — это выходной, чужой месяц, их сумма и просто
   клетка, и пятый серый среди них не читался как «мышь здесь». Ровно об этом
   и просили словами «а то всё серое сливается». Выделенная клетка остаётся
   серой: она про «здесь открыто событие», а не про то, где рука. */
.day-cell.day-cell:hover { background: var(--day-hover); box-shadow: inset 0 0 0 1px var(--day-hover-line); }
.day-cell.day-cell.selected { background: var(--hover); }

/* Строка обрезается по клетке, а не выпускает `+` наружу. Колонка теперь ровно
   седьмая доля недели и под содержимое не растёт, поэтому в узкой клетке число,
   подпись праздника и кнопка перестают помещаться втроём — и у последнего дня
   недели кнопка вылезала за правую границу сетки, потому что закрыть её
   соседней клеткой там нечем.

   Порядок в строке — праздник, `+`, число, и он же порядок из issue #166:
   число прижато к правому краю у всех дней без исключения, подпись праздника —
   к левому. Держится это на том, что растяжимый здесь ровно один элемент
   (`.day-hol`), а не на `justify-content`: у дня без праздника обёртка пустая,
   но место занимает, и число остаётся там же, где у соседа с праздником. До
   этого растяжимого не было вовсе, и число стояло то слева, то справа —
   в зависимости от того, был ли слева от него счётчик событий. */
.day-num {
  min-width: 0; overflow: hidden;
  font-size: 13px; color: var(--ink-soft);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  display: flex; align-items: center;
  gap: 5px; height: 22px;
  margin-bottom: 2px;
}
.day-date { flex-shrink: 0; }
.today-badge {
  min-width: 22px; height: 22px; padding: 0 7px;
  background: var(--ink); color: var(--bg);
  border-radius: 11px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 600;
}
.day-add-btn {
  width: 20px; height: 20px; border-radius: var(--radius-sm);
  border: none; background: transparent; cursor: pointer;
  font-size: 14px; color: var(--muted); line-height: 1;
  display: flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity .1s, background .1s; flex-shrink: 0;
  font-family: inherit;
}
.day-cell:hover .day-add-btn { opacity: 1; }
.day-add-btn:hover { background: var(--hover); color: var(--ink); }

/* ── Drag & drop ── */
.ev-card[draggable="true"],
.week-event[draggable="true"] { cursor: grab; }
.ev-card.dragging,
.week-event.dragging { opacity: 0.35; }
.day-cell.drag-over,
.week-col-body.drag-over {
  background: rgba(55,53,47,0.04) !important;
  outline: 2px dashed rgba(55,53,47,0.18);
  outline-offset: -3px;
}
body.dark .day-cell.drag-over,
body.dark .week-col-body.drag-over {
  background: rgba(255,255,255,0.05) !important;
  outline-color: rgba(255,255,255,0.18);
}

/* ── Event resize handle ── */
/* Ширина полосы у правого края написана один раз и читателей у неё двое: сама
   полоса и «…», которое встаёт слева от неё (issue #210). Разъехавшись, они
   дают кнопку поверх зоны растяжения — то есть плашку, которую больше нельзя
   растянуть, и заметно это не сразу: обе появляются только по наведению.

   Полоса при этом осталась на самом краю, а кнопка отступила внутрь, а не
   наоборот. Тянуть съёмку за правый край — жест, который делают, не глядя, и
   работает он ровно у края; «…» ищут глазами, и куда его поставили, видно. */
.ev-card, .cal-bar, .wk-allday-bar { --ev-edge: 8px; }
/* У полосы через несколько дней зона у́же на два пикселя — так было и до «…»,
   и число здесь ровно то, что стояло. */
.cal-bar, .wk-allday-bar { --ev-edge: 6px; }
.ev-resize-handle {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: var(--ev-edge, 8px);
  cursor: ew-resize;
  opacity: 0;
  background: transparent;
  z-index: 3;
  transition: opacity .12s, background .12s;
}
.ev-card:hover > .ev-resize-handle,
.cal-bar:hover > .ev-resize-handle,
.wk-allday-bar:hover > .ev-resize-handle { opacity: 1; }
/* Серой палочки у правого края события больше нет (issue #150): на плашке
   высотой в двадцать пикселей она читалась как полоса прокрутки, то есть
   обещала спрятанное содержимое там, где его нет. Зона осталась — та же
   восьмипиксельная полоса с курсором `ew-resize`, и подсвечивается она, когда
   рука уже на ней; растянуть съёмку по-прежнему можно, а «здесь что-то
   прокручивается» больше не сказано. */
.ev-resize-handle:hover { background: rgba(35,131,226,0.14); }

/* ── «…» на плашке события ──
   Меню над съёмкой прямо в сетке, без открытой карточки (issue #210). Почему
   меню, а не крестик, и почему только по наведению — в `eventMoreBtn`
   (02-calendar.js).

   Заливка `inherit`, то есть цвет самой плашки: кнопка стоит поверх названия,
   и прозрачная она была бы тремя точками по буквам. Цвет статуса при этом не
   назван здесь ни разу — он у каждой съёмки свой, а `--ev-fill` считается
   тремя правилами выше и по-разному в светлой и тёмной теме.

   Поверх этой заливки лежит `::before` — подложка из `currentColor` в восемь
   процентов, и она нужна не для красоты. Три точки на голой заливке дочитывались
   как многоточие обрезанного названия: «Vogue — весен ⋯» выглядит строкой,
   которой не хватило места, а не кнопкой. Коробка вокруг них отвечает «сюда
   можно нажать». Наведение ту же подложку сгущает — своего фона, который можно
   было бы затемнить, у кнопки нет. Оба числа в `currentColor`, а не в сером:
   на пастели статуса и на тёмной теме серый читается по-разному, а цвет текста
   события к своей заливке подобран уже.

   Кнопка абсолютная и стоит после названия в разметке, то есть рисуется поверх
   него. Появись она в потоке — строка перекладывалась бы под курсором, и
   название, обрезанное многоточием, прыгало бы на каждом наведении. */
.ev-more-btn {
  position: absolute;
  top: 50%; right: var(--ev-edge, 8px);
  transform: translateY(-50%);
  /* Шире, чем высока, и это не про руку: под кнопкой лежит хвост названия, а
     обрезает его многоточие — коробка ровно по значку оставляла от «…» первую
     точку торчать слева, и строка читалась как «весен.⋯». */
  width: 22px; height: 18px; padding: 0;
  border: none; border-radius: var(--radius-sm, 4px);
  background: inherit; color: inherit;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none;
  z-index: 4;
  transition: opacity .1s;
}
.ev-more-btn::before {
  content: ''; position: absolute; inset: 0;
  border-radius: inherit; background: currentColor; opacity: .08;
  transition: opacity .1s;
}
/* Значок рисуется поверх подложки сам: `::before` — первый ребёнок по дереву,
   и `<svg>` за ним ложится сверху без единого `z-index`. */
.ev-more-btn svg { width: 14px; height: 14px; display: block; }
/* Открытое меню держит кнопку видимой само: мышь уходит с плашки на пункты, и
   без этой строки точки гасли ровно в тот момент, когда за ними уже открыто. */
.ev-card:hover > .ev-more-btn,
.ev-card:focus-within > .ev-more-btn,
.cal-bar:hover > .ev-more-btn,
.cal-bar:focus-within > .ev-more-btn,
.wk-allday-bar:hover > .ev-more-btn,
.wk-allday-bar:focus-within > .ev-more-btn,
.ev-more-btn[aria-expanded="true"] { opacity: 1; pointer-events: auto; }
.ev-more-btn:hover::before { opacity: .18; }
/* У карточки в сетке часов две строки и высота в целую съёмку: посередине
   кнопка встала бы между названием и именем модели, а не над ними. */
.ev-card.wk-ev > .ev-more-btn { top: 3px; transform: none; }
body.resizing-ev,
body.resizing-ev * { cursor: ew-resize !important; user-select: none; }
/* While resizing, event cards must not intercept the pointer — otherwise the
   stretched bar sits on top of the cells beneath it and the cursor can never
   reach an earlier day to shrink the span. Let elementFromPoint hit the cell. */
body.resizing-ev .cal-bar,
body.resizing-ev .ev-card { pointer-events: none !important; }
.day-cell.resize-target,
.wk-col.resize-target {
  background: rgba(35,131,226,0.10) !important;
  outline: 2px dashed var(--accent);
  outline-offset: -3px;
}
body.dark .day-cell.resize-target,
body.dark .wk-col.resize-target {
  background: rgba(80,160,255,0.14) !important;
}

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

   Полосы статуса слева больше нет. Она говорила то же самое, что и заливка,
   вторым способом — и на однодневной коробке в 20 пикселей высотой съедала
   заметную долю ширины ради повтора. Вместо неё чуть плотнее сама заливка:
   `--ev-fill` подмешивает к пастельному фону немного цвета точки, поэтому
   работает и для статуса, заведённого агентством, — его цвета живут в базе
   (`statusTint` в 03-events.js), и переписать их здесь было бы нечем. */
.ev-card {
  position: relative;
  background: var(--ev-fill);
  color: var(--ev-text, var(--ink));
  border: none;
  border-radius: var(--radius-sm);
  padding: 5px 9px;
  overflow: hidden; box-sizing: border-box;
  cursor: pointer; flex-shrink: 0;
  display: flex; flex-direction: column;
  transition: filter .14s;
}
.ev-card:hover { filter: brightness(0.97); }
/* Неделя даёт каждому событию втрое больше места, чем месяц, и заливка во всю
   карточку там из акцента превращается в фон экрана: остаётся белая карточка.

   Полосы статуса слева у неё больше нет (issue #190: «убери цветные полоски
   слева от события»). Цвет статуса от этого не пропал — его несёт точка перед
   названием, та же, что у полосы «весь день» над колонкой: точка отвечает на
   тот же вопрос одним знаком, а полоса высотой в съёмку рисовала вдоль
   карточки вертикальную линейку, которой в месяце рядом нет ни у одного
   события. Правило это про одну карточку в продукте — `ev-plain` носит только
   неделя. */
.ev-card.ev-plain {
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 9px 10px; gap: 5px;
}
.ev-card.ev-plain:hover { filter: none; background: var(--hover); }

/* ── Неподтверждённое событие ──

   То, что продукт распознал из письма или файла сам и за что пока никто не
   отвечает (`events.confirmed`, см. event-import.js). Выглядит как набросок:
   серая заливка вместо цвета статуса, пунктирная рамка кругом, приглушённый
   текст. Цвет статуса при этом не пропадает совсем — он остаётся точкой слева,
   и точка отвечает на единственный вопрос, который к наброску есть: «а куда
   оно попадёт, когда я нажму „Подтвердить“».

   Пунктир — не украшение к серому, а вторая половина ответа. Серый и пастель
   статуса различаются оттенком, то есть ровно тем, чего не видит человек с
   нарушением цветовосприятия; форма рамки видна всем. Поэтому здесь и то и
   другое, а не что-нибудь одно поярче.

   Постоянной надписи «Не подтверждено» внутри карточки нет намеренно: клетка
   месяца — это одна строка высотой в двадцать пикселей, и слово, повторяющее
   то, что уже сказали рамка и цвет, отъело бы её у названия. Слово живёт в
   `title`, то есть во всплывающей подсказке. */
.ev-card.ev-unconfirmed,
.cal-bar.ev-unconfirmed,
.wk-allday-bar.ev-unconfirmed,
.kb-card.ev-unconfirmed,
.list-item.ev-unconfirmed {
  background: var(--unconf-fill);
  color: var(--unconf-ink);
  border: 1px dashed var(--unconf-line);
  /* Точка статуса — единственное цветное, что здесь остаётся: она говорит, в
     каком виде событие вернётся в календарь после подтверждения. */
  box-shadow: inset 3px 0 0 -1px var(--ev-accent, var(--unconf-line));
}
/* У недельной карточки рамка уже есть, и своя левая полоса тоже: заменяем её
   пунктиром целиком, иначе сплошная линия слева спорит с пунктиром вокруг. */
/* В годовой сетке от события остаётся одна точка, и заливать её пунктиром
   негде. Поэтому у дня, занятого только распознанным, точка становится
   контуром: тот же ответ «это набросок», данный единственным, что там есть. */
.year-day.has-ev.year-day-draft { color: var(--unconf-ink); font-weight: 400; }
.year-day.has-ev.year-day-draft::after {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--dot, var(--unconf-line));
}

/* Полоса timeline красится цветом модели, а не статуса: там это единственное,
   чем строки различаются. Пунктир поверх неё и приглушение — тот же ответ, что
   и в остальных сетках, только цвет остаётся видимым сквозь него. */
.tl-ev-bar.ev-unconfirmed {
  opacity: 0.55;
  border: 1px dashed var(--unconf-line);
}

.list-item.ev-unconfirmed {
  border-left: 3px dashed var(--ev-accent, var(--unconf-line));
  box-shadow: none;
}
/* У карточки канбана цветной полосы слева нет ни сплошной, ни пунктирной
   (issue #269), а инсет-тень статуса — та же полоса, только нарисованная
   тенью: снимается вместе с ней. Пунктир кругом остаётся — он и есть ответ
   «это набросок». */
.kb-card.ev-unconfirmed { box-shadow: none; }
/* У недельной карточки цветной полосы слева нет ни сплошной, ни пунктирной
   (issue #190), и инсет-тень статуса — та же полоса, только нарисованная
   тенью: снимается вместе с ней. Пунктир кругом при этом остаётся — он и есть
   ответ «это набросок», а точка перед названием остаётся цветной. */
.ev-card.ev-plain.ev-unconfirmed { box-shadow: none; }
.ev-card.ev-unconfirmed .ev-title-main,
.cal-bar.ev-unconfirmed .ev-title-main,
.wk-allday-bar.ev-unconfirmed .ev-title-main { opacity: 0.85; }
/* Наведение возвращает полную непрозрачность: приглушённость означает «ещё не
   решили», а не «нельзя трогать», и рука на событии должна это подтверждать. */
.ev-card.ev-unconfirmed:hover,
.cal-bar.ev-unconfirmed:hover,
.wk-allday-bar.ev-unconfirmed:hover { filter: none; background: var(--unconf-hover); }

/* ── Date range tag for multi-day events ── */
.ev-date-range {
  font-size: 11px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ── Shared inner content ── */
/* Строка события: название плюс то, что стоит рядом с ним, — значок «их
   несколько» (issue #179). Своя строка нужна затем, что `.ev-card` в месяце
   выложена колонкой (в неделе под названием стоит имя модели), и
   значок, положенный в карточку прямо, вставал бы второй строкой и растил
   коробку вдвое.

   Цвет наследуется, а не назван `--ink`: в месяце карточка красится
   собственным цветом события (`--ev-text`), и `--ink` здесь означал бы значок
   чужого цвета поверх пастельной заливки. В неделе карточка белая и сама
   красится `--ink` — наследование даёт там ровно то же, что стояло. */
.ev-title {
  font-size: 12px; font-weight: 500; color: inherit;
  display: flex; align-items: center; gap: 4px;
  overflow: hidden; line-height: 1.3;
}
.ev-page-icon { font-size: 11px; opacity: 0.4; flex-shrink: 0; line-height: 1; }
/* Название не растягивается на всю строку, а занимает своё (issue #179): рядом
   с ним стоит значок «их несколько», и растянутое название отжимало бы его к
   правому краю. В коробке дня это выглядело бы решением — счётчик в столбик, —
   а на полосе многодневной съёмки, которая тянется через четыре колонки, тот же
   значок уезжал бы от названия на пол-экрана. Место, где значок стоит, должно
   быть одно, и «сразу за названием» — единственное, которое работает на обеих
   ширинах. Сжиматься название по-прежнему умеет: длинное обрезается
   многоточием, как обрезалось. */
.ev-title-main {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  flex: 0 1 auto; min-width: 0;
  font-size: 12.5px; font-weight: 600; color: inherit;
  line-height: 1.3; letter-spacing: -0.01em;
}
/* Время начала стоит на плашке месяца там, где раньше стояло имя модели, и
   красится не серым, а собственным цветом события приглушённо: серый по
   пастельной заливке статуса читался бы как чужая надпись поверх события, а
   так время остаётся внутри той же краски. Табличные цифры — чтобы «10:00» и
   «09:30» в соседних клетках стояли одной шириной. */
.ev-time-prefix {
  font-weight: 500; font-size: 11px; opacity: 0.8;
  font-variant-numeric: tabular-nums;
  /* Отступом, а не точкой-разделителем: « · » между временем и названием
     занимал двадцать четыре пикселя, а на экране в тысячу точек клетка месяца
     сама шириной в девяносто пять — то есть разделитель съедал треть строки, и
     от названия оставалось многоточие. Время и название и без него читаются
     двумя вещами: у времени свой вес и своя прозрачность. */
  margin-right: 5px;
}

/* Значок статуса — первое, что стоит в строке события (issue #258). Отступ тот
   же, что у времени строкой выше, и по той же причине: разделителя между
   значком и тем, что за ним, нет — в клетке месяца шириной в девяносто пять
   пикселей « · » съел бы треть строки.

   Своего цвета у него нет ни в одном из двух видов: рисованный значок красится
   `currentColor`, аббревиатура — просто текстом. В коробке месяца это цвет
   события (`--ev-text`) по пастели статуса, в белой карточке недели — `--ink`,
   и оба раза значок остаётся внутри той же краски, что и название рядом.
   Названный тут своим цветом, он был бы надписью поверх события.

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

   `font-weight` — про второй вид: аббревиатура «TR» рядом с названием той же
   жирности читалась бы началом названия. Двенадцать пикселей у рисованного —
   это половина его сетки в 24, то есть обводка ровно в пиксель, как у чипов
   списка и значков левого меню. */
.ev-status-icon {
  font-size: 11px; font-weight: 700; line-height: 1;
  letter-spacing: 0.01em;
  white-space: nowrap;
  margin-right: 5px;
}
.ev-status-icon svg {
  width: 12px; height: 12px;
  display: inline-block; vertical-align: -1.5px;
}

/* «Их несколько» — значок и число внутри коробки события (issue #179).

   Групповой кастинг перестал быть тремя одинаковыми коробками, и вместе с
   двумя лишними коробками с экрана ушёл единственный способ увидеть, что
   моделей на съёмке больше одной. Это и вернул значок.

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

   Цифры табличные: «2» и «3» в соседних клетках обязаны стоять одной шириной,
   иначе значок в каждой коробке чуть на своём месте. */
.ev-people {
  display: inline-flex; align-items: center; gap: 2px;
  flex-shrink: 0;
  font-size: 11px; font-weight: 600; line-height: 1;
  opacity: 0.75; color: inherit;
  font-variant-numeric: tabular-nums;
}
.ev-people svg { display: block; }

.ev-status-row {
  display: flex; align-items: center;
  overflow: hidden;
}
.ev-status-tag {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 9px; border-radius: var(--radius-sm);
  font-size: 11.5px; font-weight: 500;
  background: var(--bg); color: var(--ink);
  white-space: nowrap; overflow: hidden; max-width: 100%;
}
.ev-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.ev-status-lbl {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Агентство — не чип, а приписка: в карточке уже есть цвет статуса и точка,
   третья заливка рядом превращает её в мозаику. Носит эту приписку теперь одна
   карточка канбана: из недельной заказчик ушёл в подсказку вместе со статусом
   (issue #190), и обёртки `.ev-agencies` вокруг него не осталось. */
.ev-agency {
  font-size: 11px; font-weight: 400; color: inherit; opacity: .6;
  background: none; padding: 0;
  white-space: nowrap; max-width: 130px;
  overflow: hidden; text-overflow: ellipsis; flex-shrink: 1; min-width: 0;
}


/* ── TIMELINE VIEW ── */
/* Отбит от краёв тем же `--pad-x`, что месяц, неделя, список, год и канбан
   (issue #183: «выравнивание должно распространяться на все виды календаря»).
   Timeline был здесь единственным исключением — он шёл от края до края, то
   есть его лист начинался левее кнопки «Вид» над ним и кончался правее
   «+ Событие», и переключение вида двигало обе границы календаря разом.

   Справа — плюс полоса прокрутки, как у полосы над ним: прокручивается тут не
   контейнер, а лист внутри (`.tl-ev-outer`), и `scrollbar-gutter` резервировать
   контейнеру нечего. Место под полосу поэтому держит отступ — правая граница
   листа стоит там же, где правая граница сетки месяца, и не двигается от того,
   влезли строки или нет. */
.timeline-container {
  flex: 1; overflow: hidden; display: flex; flex-direction: column;
  padding: 0 calc(var(--pad-x) + var(--sbw, 0px)) var(--pad-x) var(--pad-x);
  background: var(--bg);
}
.tl-wrap { display: flex; flex-direction: column; height: 100%; overflow: hidden; }

/* Header */
.tl-head-row {
  display: flex; flex-shrink: 0;
  border-bottom: 1px solid var(--rule);
  background: var(--bg);
}
.tl-corner {
  flex-shrink: 0;
  border-right: 1px solid var(--rule);
  background: var(--bg);
}
.tl-head-outer { flex: 1; overflow: hidden; }
.tl-head-inner { display: flex; flex-direction: column; }

/* Полоса месяцев над числами: лист длиннее месяца (issue #227), числа доходят
   до 31 и начинаются заново, и без названия сказать, где ты, нечем. Подпись
   липкая, поэтому месяц, наполовину уехавший под левый край, продолжает
   называть себя, пока виден хоть один его день; прижимает её к краю не
   контейнер, а своя клетка — `position: sticky` держится в границах
   родителя, и название следующего месяца её отодвинет, а не перекроет.
   Клетка поэтому и не режет содержимое: `overflow` на ней создал бы свой
   контейнер прокрутки, а внутри него подпись липнет к нулю, который никогда
   не двигается. */
.tl-mo-row { display: flex; border-bottom: 1px solid var(--rule); }
.tl-mo-cell {
  flex-shrink: 0; white-space: nowrap;
  border-right: 1px solid var(--rule);
  padding: 7px 0 6px;
  font-size: 12px; font-weight: 600; color: var(--ink);
}
.tl-mo-cell span { position: sticky; left: 0; display: inline-block; padding: 0 10px; }
.tl-day-row { display: flex; }
.tl-day-hd {
  flex-shrink: 0; text-align: center;
  border-right: 1px solid var(--rule);
  padding: 10px 2px 8px;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
}
.tl-day-hd.tl-we { background: var(--surface-2); }
.tl-day-hd.tl-tod-hd { background: transparent; }
.tl-dnum { font-size: 13px; font-weight: 600; color: var(--ink); line-height: 1.2; font-variant-numeric: tabular-nums; }
.tl-dnum.tl-tod-num {
  background: var(--accent); color: #fff;
  width: 24px; height: 24px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700;
}
.tl-day-hd small {
  font-size: 10px; color: var(--muted);
  letter-spacing: 0; text-transform: none;
}
.tl-day-hd.tl-tod-hd small { color: var(--accent); }

/* Body */
.tl-body-row { display: flex; flex: 1; overflow: hidden; }
.tl-name-col {
  flex-shrink: 0; overflow: hidden;
  border-right: 1px solid var(--rule);
  background: var(--bg);
}
.tl-name-cell {
  display: flex; align-items: center; gap: 10px;
  padding: 0 14px;
  border-bottom: 1px solid var(--rule);
}
.tl-avatar {
  width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-family: inherit; font-size: 11px;
  font-weight: 600; color: #fff;
}
.tl-name-txt {
  font-size: 13px; font-weight: 500; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Events area */
.tl-ev-outer { flex: 1; overflow: auto; }
.tl-strip { position: relative; }

/* Сетка дней и выходные — фон строки, а не колонка на каждый день. Колонок
   было ровно столько, сколько дней, и на каждую модель: на месяце это три
   сотни пустых div'ов, на листе в два года (issue #227) у агентства на два
   десятка моделей — пятнадцать тысяч, и добавлялись бы они при каждом подходе
   к краю. Неделя же повторяется через семь дней всегда, поэтому повторяющийся
   градиент рисует ровно то же самое и не стоит ничего.

   Ширина дня и неделя приходят переменными от того, кто рисует лист
   (`TL_DAY_W` в 02-calendar.js): число, записанное здесь второй раз, разъедется
   с полосами, которые стоят на абсолютных координатах от него же. `--tl-shift`
   ставит градиент в фазу с первым днём листа — лист начинается с 1-го числа,
   а не с воскресенья, и без сдвига выходные встали бы посреди недели. */
.tl-ev-row {
  position: relative; border-bottom: 1px solid var(--rule); flex-shrink: 0;
  background-image:
    repeating-linear-gradient(to right,
      var(--surface-2) 0, var(--surface-2) var(--tl-day),
      transparent var(--tl-day), transparent calc(var(--tl-day) * 6),
      var(--surface-2) calc(var(--tl-day) * 6), var(--surface-2) var(--tl-wk)),
    repeating-linear-gradient(to right,
      transparent 0, transparent calc(var(--tl-day) - 1px),
      var(--rule) calc(var(--tl-day) - 1px), var(--rule) var(--tl-day));
  background-position: var(--tl-shift) 0, 0 0;
}
/* Сегодня — одна линия на весь лист: отметка в клетке каждой модели рисовала
   бы то же самое столько раз, сколько строк. */
.tl-today {
  position: absolute; top: 0; bottom: 0; width: 0;
  border-left: 2px solid var(--accent);
  pointer-events: none;
}

/* Event bars */
.tl-ev-bar {
  position: absolute; border-radius: var(--radius-sm);
  padding: 4px 10px;
  font-size: 12px; font-weight: 500;
  color: #fff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: grab; user-select: none;
  transition: filter 0.15s;
}
.tl-ev-bar:hover { filter: brightness(0.92); }
.tl-ev-bar:active { cursor: grabbing; }

/* ── KANBAN VIEW ── */
.kanban-container {
  flex: 1; overflow-x: auto; overflow-y: hidden;
  background: var(--bg);
}
/* Место под полосу прокрутки добирается отступом самой доски, а не гейтером
   контейнера: канбан прокручивается только вбок, вертикальной полосы у него не
   бывает никогда, и `scrollbar-gutter` ему нечего резервировать. А полоса с
   «Вид» и «+ Событие» над ним отбита с запасом, как над месяцем, — без этой
   добавки правый край доски и правый край шапки расходятся ровно на ту
   пятнадцатку, которую issue #152 и просил свести. */
.kanban-board {
  display: flex; gap: 12px;
  padding: 0 calc(var(--pad-x) + var(--sbw, 0px)) var(--pad-x) var(--pad-x);
  height: 100%;
  align-items: flex-start; min-width: min-content;
  box-sizing: border-box;
}
.kb-col {
  width: 280px; flex-shrink: 0;
  display: flex; flex-direction: column;
  background: var(--surface-2);
  border-radius: var(--radius);
  border: none;
  max-height: 100%; overflow: hidden;
  padding: 10px;
  gap: 6px;
}
.kb-col-header {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 4px 6px; flex-shrink: 0;
}
/* Шапка колонки — ручка, за которую доску переставляют (issue #293). Рука та
   же, что у карточки под ней: жест один и тот же, и два разных курсора на
   соседних коробках сообщали бы о разнице, которой нет. Ручкой её называет
   курсор, а не значок ⋮⋮ из диалога «Статусы»: там строка узкая и держать её
   больше не за что, а здесь берут колонку целиком. */
.kb-col-header[draggable="true"] { cursor: grab; }
.kb-col-header[draggable="true"]:active { cursor: grabbing; }
.kb-col.dragging { opacity: 0.35; }
/* Куда встанет колонка — линия у того края, к которому её несут, и того же
   цвета, что вставка в справочнике статусов: жест один, и подсказка к нему
   обязана выглядеть одинаково. Тенью, а не рамкой: рамка раздвинула бы соседние
   колонки на свою толщину ровно в тот момент, когда в одну из них целятся. */
.kb-col.kb-drop-before { box-shadow: -3px 0 0 var(--accent, #2563EB); }
.kb-col.kb-drop-after  { box-shadow:  3px 0 0 var(--accent, #2563EB); }
.kb-col-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.kb-col-title { font-size: 13px; font-weight: 600; flex: 1; color: var(--ink); }
.kb-col-count {
  font-size: 12px; color: var(--muted);
  background: transparent; padding: 0; border-radius: 0;
  font-variant-numeric: tabular-nums;
}
.kb-col-body {
  flex: 1; overflow-y: auto;
  padding: 0;
  display: flex; flex-direction: column; gap: 6px;
  min-height: 80px;
}
.kb-col-body.drag-over {
  background: var(--hover);
  outline: 2px dashed var(--rule-strong); outline-offset: -4px;
  border-radius: var(--radius-sm);
}
.kb-card {
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  cursor: grab;
  transition: border-color .12s, box-shadow .12s;
  flex-shrink: 0;
  position: relative;
}
/* «…» карточки доски — та же кнопка, что на плашке месяца, и потому своих
   размеров у неё здесь нет, только место (issue #270). Плашка месяца — одна
   строка, и кнопка на ней стоит по середине высоты; у карточки доски строк
   три, и середина пришлась бы на имя модели вместо угла, где её ищут. */
.kb-card > .ev-more-btn { top: 6px; right: 6px; transform: none; }
.kb-card:hover > .ev-more-btn,
.kb-card:focus-within > .ev-more-btn { opacity: 1; pointer-events: auto; }
.kb-card:hover { border-color: var(--rule-strong); box-shadow: 0 1px 4px rgba(0,0,0,0.04); }
.kb-card.dragging { opacity: 0.35; }
.kb-card-title {
  font-size: 13px; font-weight: 500; color: var(--ink);
  margin-bottom: 5px; line-height: 1.35;
}
.kb-card-meta { font-size: 11px; color: var(--muted); margin-bottom: 4px; }
.kb-card-tags { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 4px; }
.kb-empty { font-size: 12px; color: var(--muted); padding: 8px 4px; text-align: center; }

/* ── YEAR VIEW ── */
/* Единственный вид с отступом сверху, и это не разнобой (issue #225: «добавь
   отступ сверху»). У месяца, недели и списка первым идёт липкая шапка — своя
   подпись с внутренним отступом, и она же держит содержимое на расстоянии от
   линейки `.view-row`. У года шапки нет вовсе: первое, что стоит под линейкой
   полосы «Вид», — рамка карточки января, то есть вторая горизонтальная линия в
   десяти пикселях от первой. Читается это не «календарь начинается здесь», а
   «карточку обрезало». Сверху ровно `--pad-x`: рамка вида замыкается с четырёх
   сторон, раз ей нечем начаться. */
.year-container {
  flex: 1; overflow-y: auto; scrollbar-gutter: stable;
  padding: var(--pad-x);
  background: var(--bg);
}
.year-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 24px;
}
.year-month {
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 14px;
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
/* Наведение — голубое, тем же `--day-hover`, что у клетки месяца и у столбца
   недели (issue #225: «при наведении на месяц выделяй голубым цветом как на
   остальных видах»). Серая подложка здесь проигрывала по той же причине, по
   какой проиграла там: серым в годовой сетке покрашены праздник (`[data-hol]`)
   и подложки чисел, и двенадцатая карточка, ставшая ещё одним серым, не
   отвечала на вопрос «где рука». Рамка берёт `--day-hover-line` — тот же
   голубой, что обводит клетку месяца изнутри; здесь она уже есть своя, и
   красить её вторым цветом значило бы обвести карточку дважды. */
.year-month:hover { border-color: var(--day-hover-line); background: var(--day-hover); }
.year-month-title {
  font-family: inherit; font-size: 14px; font-weight: 600;
  color: var(--ink); margin-bottom: 10px;
}
/* Единственные семь колонок календаря, которым `auto` в минимуме оставлен, и
   это не пропущенная правка. Правило соседних гридов — «колонка не должна быть
   шириной с то, что кто-то напечатал»: там в клетке лежит название праздника из
   чужого .ics и название съёмки, набранное букером. Здесь в клетке нет ничего,
   кроме числа, которое пишет сам продукт (праздник виден подложкой, а называется
   в `title`), поэтому `auto` значит ровно «шириной в две цифры» — то есть тот
   минимум, ниже которого миниатюре опускаться и нельзя.

   Он же держит и колонки самого года: `.year-grid` — это `repeat(4, 1fr)`, и
   ширина карточки месяца упирается в семь клеток внутри неё. С нулём в минимуме
   двенадцать карточек сжимались до 88–105 пикселей (разной ширины: минимум брался
   уже с названия месяца, а «Март» и «Сентябрь» разной длины), клетка становилась
   десятипиксельной, и двузначные числа налезали друг на друга. Правильный ответ
   для года — не сжиматься, а прокручиваться вбок, как и было. */
.year-mini-headers {
  display: grid; grid-template-columns: repeat(7, 1fr);
  margin-bottom: 4px;
}
.year-mini-headers span {
  text-align: center; font-size: 10px; font-weight: 500;
  color: var(--muted); text-transform: none; letter-spacing: 0;
}
.year-mini-days { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; }
.year-day {
  text-align: center; font-size: 10px; color: var(--muted);
  padding: 2px 0; line-height: 1.7; border-radius: 3px;
  position: relative;
  font-variant-numeric: tabular-nums;
  aspect-ratio: 1;
  display: flex; align-items: center; justify-content: center;
}
/* Сегодняшнее число — чёрное (issue #225: «день показывай чёрным как на
   остальных видах»). Метка «сегодня» одна на продукт: в клетке месяца и в шапке
   недели это `.today-badge`, то есть `--ink` с числом цвета фона, и год был
   последним местом, где она оставалась синей. Сам класс сюда не вешается —
   у метки есть размер (22px и скругление в половину), а в годовой сетке
   «сегодня» не кружок поверх числа, а сама клетка, у которой размер свой.
   Приезжают только цвета.

   Класс удвоен намеренно: `.year-day.has-ev` и `.year-day.has-ev.year-day-draft`
   ниже тоже пишут цвет числа, и с одинарным весом побеждали бы они — то есть
   сегодняшний день с одной съёмкой получал бы `--ink` по `--ink`. До этой
   правки то же место давало тёмное число на синем, и заметно это было ровно
   один день в году. */
.year-day.today.today {
  background: var(--ink); color: var(--bg);
  font-weight: 600;
}
.year-day.has-ev { color: var(--ink); font-weight: 500; }
.year-day.has-ev::after {
  content: ''; position: absolute;
  bottom: 1px; left: 50%; transform: translateX(-50%);
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--dot, var(--accent));
}
.year-day.today.has-ev::after { background: var(--bg); }

/* ── WEEK VIEW ── */
.week-container {
  flex: 1; overflow-y: auto; scrollbar-gutter: stable;
  /* The sheet is dragged sideways to turn the page (`WeekSwipe`): visible would
     let that pull open a horizontal scrollbar, and `pan-y` tells a touchscreen
     the sideways half of the gesture is ours while up and down stays its own. */
  overflow-x: hidden; touch-action: pan-y;
  display: flex; flex-direction: column;
  padding: 0 var(--pad-x) var(--pad-x);
  background: var(--bg);
  /* Лист останавливается на целом часе (issue #223: «этот пустой бокс нужно
     убрать, он не нужен»). Пустой бокс — это хвост часа, оставшийся под липкой
     шапкой: прокрутка на любое число, не кратное высоте часа, оставляет между
     полосой «весь день» и первой подписанной линией узкую пустую ленту в две
     линейки, и читается она не «часть седьмого часа», а «лишняя пустая
     строка» — подписи у неё нет, она уехала под шапку.

     `proximity`, а не `mandatory`: прилипание — это доводка в конце жеста, а
     не запрет остановиться там, где человек остановился. Отступ сверху равен
     высоте самой шапки и задаётся из `02-calendar.js`: он зависит от числа
     дорожек в полосе «весь день», то есть от содержимого недели, и вторым
     написанием здесь был бы лист, прилипающий не туда, куда он думает. */
  scroll-snap-type: y proximity;
}
.wk-inner { display: flex; flex-direction: column; min-height: 100%; }
/* Only while the pointer is down: a grid that says "grab me" all the time reads
   as something other than the seven days you click to add a shoot. */
.week-container.wk-dragging { cursor: grabbing; user-select: none; }
.week-container.wk-dragging .wk-col.wk-hot { background-color: var(--bg); }

/* Строка недели — полоса с часами плюс семь колонок. Полоса вынесена из
   гридов, а не добавлена к ним восьмой колонкой, и это не вкусовщина: семь
   долей календаря обязаны быть семью долями (`calendar-grid.test.js`), а грид
   из восьми треков выпал бы из этой проверки целиком — вместе с обещанием, что
   колонка не растёт под длинное название. */
.wk-row { display: flex; min-width: 0; }
/* Полоса с часами лежит в левой рамке календаря, а не внутри неё: отрицательный
   отступ ровно в её ширину уводит её в отступ `.week-container`, и семь колонок
   начинаются там же, где начинается сетка месяца. Клипается это по padding-box
   контейнера (`overflow-x: hidden`), то есть в боковую панель полоса не
   вылезает. Написано у обеих клеток — шапки и полосы «весь день» — и у самой
   линейки: разъехавшись, они дают три левых края вместо одного. */
.wk-gutter-cell {
  flex: 0 0 var(--wk-gutter-w); margin-left: calc(-1 * var(--wk-gutter-w));
  background: var(--bg);
}
/* Шапка и полоса «весь день» прилипают к верху вместе: между ними нет ничего,
   что имело бы смысл прокручивать, а съёмка без часа, уехавшая вверх при
   прокрутке к вечеру, — это съёмка, о которой к вечеру забыли. */
.wk-top { position: sticky; top: 0; z-index: 6; background: var(--bg); }

/* Рамка слева — по той же причине, что у `.day-headers` в месяце: колонки
   шапки, полос и тела недели обязаны стоять друг под другом, а рамка есть
   только у `.wk-body`. */
.wk-header {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  flex: 1; min-width: 0;
  border-bottom: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
  background: var(--bg);
}
/* Линия справа есть и у седьмого дня (issue #223: «справа не хватает границы,
   полоски»). Три исключения `:last-child { border-right: none }` — здесь, у
   клетки полосы «весь день» и у колонки часов — стояли с тех пор, когда неделя
   рисовалась сама по себе; в сетке месяца такого исключения нет, и `.day-cell`
   последнего столбца свою линию рисует. Рядом с ней неделя выглядела не «без
   лишней рамки», а обрезанной: шесть столбцов кончаются линией, седьмой —
   ничем. Сняты все три сразу и только сразу: этажа у столбца три, и линия у
   одного из них — не край недели, а огрызок. Ширина колонок при этом та же:
   рамка лежит внутри трека грида, а не рядом с ним. */
.wk-day-hd {
  padding: 10px var(--day-pad-x) 8px; text-align: left;
  border-right: 1px solid var(--rule);
}
.wk-today-hd { background: transparent; }
/* Выходной в неделе — та же заливка, что в месяце, и накрывает она весь день
   целиком: шапку, полосу «весь день» и колонку часов. День — это столбец, а
   подкрашенная наполовину суббота читается не как выходной, а как сбой. */
.wk-day-hd.weekend { background: var(--cal-we); }
/* Подпись дня стоит над числом, а не в противоположном углу шапки (issue #206:
   «день недели должен быть над числом»). Число прижато к правому краю дня — как
   в клетке месяца, — и подпись, оставшаяся у левого, читалась подписью к
   соседней колонке, а не к своей: «Чт» над пустым местом, «27» над чужим «Чт».
   Правило про левый край дня (`--day-pad-x`, issue #183) это не отменяет — по
   нему по-прежнему стоит сама коробка шапки, а внутри неё обе строки собраны
   вокруг числа. */
.wk-hd-name {
  font-size: 11px; font-weight: 500;
  letter-spacing: 0; text-transform: none;
  color: var(--muted); margin-bottom: 4px;
  text-align: right;
}
/* Сегодня в шапке недели — чёрным, а не синим (issue #223: «день сегодня
   должен выделяться чёрным как на основном календаре»). Подпись дня идёт за
   числом: синяя «Вт» над чёрным кружком спорила бы с ним о том, чем именно
   отмечен сегодняшний день. */
.wk-today-hd .wk-hd-name { color: var(--ink); }
/* Строка числа устроена как `.day-num` в клетке месяца (issue #166, повторено
   для недели в #189): число у правого края, подпись праздника у левого. Держит
   это растяжимая `.day-hol`, а не `justify-content`: у дня без праздника
   обёртка пустая, но место занимает, и число стоит там же, где у соседа с
   праздником. */
.wk-hd-date {
  display: flex; align-items: center; gap: 5px;
  min-width: 0; overflow: hidden;
  height: 24px; margin-bottom: 4px;
}
.wk-date-num {
  font-size: 18px; font-weight: 600;
  font-family: inherit; color: var(--ink);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}
/* Цвет кружка не написан: он приходит от `.today-badge`, той же метки, что
   стоит в клетке месяца, — то есть чёрным. Здесь остался только размер, и он
   больше месячного намеренно: число дня в шапке недели набрано 18-м кеглем
   против 13-го в клетке, и метка в 22px рядом с ним читалась бы не «сегодня»,
   а «этот день чем-то меньше остальных». */
.wk-today-num {
  width: 26px; height: 26px; font-size: 13px;
  border-radius: 50%; font-weight: 700; flex-shrink: 0;
}
/* Multi-day (all-day) bar row */
.wk-allday {
  position: relative; flex: 1; min-width: 0; flex-shrink: 0;
  border-bottom: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
  background: var(--bg);
  overflow: hidden;
}
/* Подложка полосы: те же семь колонок, что в шапке и в теле. Полосы съёмок
   лежат слоем поверх и считают проценты от всей семёрки, поэтому выходной
   красится клеткой, а не самой полосой. */
.wk-allday-cells {
  position: absolute; inset: 0;
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
}
.wk-allday-cell { border-right: 1px solid var(--rule); }
.wk-allday-cell.weekend { background: var(--cal-we); }
.wk-allday-bar {
  position: absolute; border-radius: var(--radius-sm);
  padding: 3px 8px; font-size: 12px; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer; display: flex; align-items: center; gap: 5px;
  box-shadow: none;
  transition: filter 0.12s;
}
.wk-allday-bar:hover { filter: brightness(0.97); }
.wk-allday-bar.cont-l { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.wk-allday-bar.cont-r { border-top-right-radius: 0; border-bottom-right-radius: 0; }

/* ── Полоса со временем и сетка часов (issue #189) ── */
.wk-body-row { flex: 1; }
.wk-gutter {
  flex: 0 0 var(--wk-gutter-w); margin-left: calc(-1 * var(--wk-gutter-w));
  background: var(--bg);
}
/* Точка, к которой прилипает лист, — начало часа; почему прилипает вообще,
   написано у `.week-container`. */
.wk-hour { height: var(--wk-hour-h); position: relative; scroll-snap-align: start; }
/* Подпись стоит под своей линией, а не по центру часа и не над ним: линия — и
   есть начало часа, а подпись, съехавшая вверх на половину строки, у полуночи
   уезжала бы под прилипшую шапку. Кегль и отбивка подогнаны под ширину рамки
   (`--pad-x`), в которой линейка теперь и живёт: «00:00» обязано помещаться в
   неё целиком, иначе левый край недели куплен обрезанным часом. */
.wk-hour-label {
  position: absolute; top: 2px; right: 4px;
  font-size: 8px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Single-day columns */
/* `minmax(0, 1fr)` по той же причине, что и у `.cal-week`: шапка недели —
   отдельная сетка из семи равных долей, и колонка, раздутая одним длинным
   названием, уводит из-под своего столбца и подпись дня, и полосу
   многодневной съёмки, которая считает свои проценты от ширины недели.
   На узком экране это ещё и вылезало за контейнер. */
.wk-body {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  flex: 1; min-width: 0;
  border-left: 1px solid var(--rule);
}
/* Часовые линии — фон колонки, а не двадцать четыре пустых узла в каждой из
   семи: рисуют они одно и то же, а узлов было бы сто шестьдесят восемь на
   каждую перерисовку недели. Заливка выходного лежит под ними
   (`background-color`), поэтому линии остаются видны и в субботу. */
.wk-col {
  position: relative;
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  padding: 0 var(--day-pad-x); cursor: pointer;
  height: calc(var(--wk-hour-h) * 24);
  transition: background-color .08s;
  background-color: var(--bg);
  background-image: repeating-linear-gradient(
    to bottom, var(--rule) 0 1px, transparent 1px var(--wk-hour-h));
}
.wk-col.weekend { background-color: var(--cal-we); }
.wk-today-col { background-color: transparent; }

/* День под курсором подсвечен весь и голубым (issue #206: «выделение дня при
   наведении мышки должно быть голубым цветом как на основном календаре и должно
   занимать весь столбик»).

   Голубой — тот же `--day-hover`, что у клетки месяца, и по той же причине, по
   какой он там появился: серым здесь покрашены выходной, чужой месяц и их
   сумма, и подсветка пятым оттенком серого сообщала не «здесь рука», а «этот
   день какой-то ещё». Внутренней рамки (`--day-hover-line`) в отличие от месяца
   нет: этажей у столбца три, и обведённые порознь, они дают две лишние
   горизонтальные линии поперёк дня.

   Класс, а не `:hover`: шапка, полоса «весь день» и сетка часов лежат в разных
   строках флекса, и `:hover` до соседней строки не дотягивается — красился
   ровно тот этаж, под которым мышь. Вешает его `Week.hot` (`02-calendar.js`),
   там же написано, почему снимается он со всех сразу. Стоит правило после
   выходного и «сегодня» намеренно: вес у селекторов одинаковый, и спор решает
   порядок. */
.wk-day-hd.wk-hot, .wk-allday-cell.wk-hot, .wk-col.wk-hot {
  background-color: var(--day-hover);
}
/* «+» в шапке дня показывается на весь столбец, а не на свою полоску: мышь в
   сетке часов — это уже «я в этом дне», и кнопка, которую видно только над
   подписью, ищется дольше, чем заводится съёмка. */
.wk-day-hd.wk-hot .day-add-btn { opacity: 1; }
/* Слой карточек — обычный ребёнок колонки, а не `position: absolute; inset: 0`:
   отступ дня (`--day-pad-x`) абсолютным потомкам не достаётся вовсе, и
   карточка вставала бы вплотную к линии дня, при том что плашка в месяце и
   полоса «весь день» отбиты от неё. */
.wk-col-lanes { position: relative; height: 100%; }
/* Карточка стоит по времени: `top` и `height` считаются в `02-calendar.js` тем
   же `--wk-hour-h`, которым нарисованы линии. Минимальная высота — про
   пятнадцать минут: коробка ниже двадцати пикселей не берётся ни мышью, ни
   глазом. */
/* Двумя классами, а не одним: отступы карточке даёт `.ev-card.ev-plain` выше,
   и правило из одного класса до неё не дотягивается — карточка молча осталась
   бы с отступами месяца, где ширина колонки втрое больше. */
.ev-card.wk-ev {
  position: absolute;
  padding: 4px 6px; gap: 2px;
  min-height: 20px;
}
/* Строки статуса и заказчика в карточке больше нет вовсе — ни в широкой, ни в
   узкой (issue #190: «вся остальная информация по наведению»). Правил, которые
   их прятали, здесь поэтому тоже нет: не матчась ни на что, они читались бы
   как работающие.

   Точка статуса стоит внутри названия, а не рядом с ним отдельной колонкой
   флекса, и это про ширину: у карточки, делящей колонку с соседкой, на текст
   остаётся полсотни пикселей, и одиннадцать из них, отнятые точкой у каждой
   строки, разрывают «Кастинг» посередине. Внутри абзаца точка стоит в строке —
   то есть платит за себя один раз, первой строкой, ровно как на полосе «весь
   день» и в клетке месяца. */
.wk-ev .ev-dot {
  display: inline-block; margin-right: 5px; vertical-align: 1px;
}
/* Вторая строка — имя модели, то самое «чья это съёмка», за которым иначе надо
   открывать карточку (issue #190). Одна строка и многоточие: имя длиной с
   «Ekaterina Yasnogorodskaya» в полколонки не помещается никак, а перенесённое
   на три строки оно вытесняет из коробки название, ради которого на календарь и
   смотрят. */
.wk-ev-model {
  font-size: 11px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  line-height: 1.25;
}
/* Короткой карточке хватает одной строки названия. Час — сорок восемь
   пикселей, из них восемь отступы: название, перенесённое на вторую строку,
   выталкивает имя модели за нижний край, а обрезанная снизу строка читается
   поломкой, а не короткой съёмкой. Порог назван в `02-calendar.js` вместе с
   арифметикой. */
.wk-ev-tight .ev-title-main {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* У узкой карточки уходит время: место тут одно, а ответ на «во сколько» уже
   написан слева на линейке и высотой самой коробки. Название такого ответа
   больше нигде не имеет. */
.wk-ev-narrow .ev-time-prefix { display: none; }
.wk-ev-narrow .wk-ev-model { font-size: 10.5px; }
/* В сетке часов у карточки есть высота, и название в ней переносится, а не
   обрезается многоточием: две съёмки на один час делят колонку пополам, и в
   восьмидесяти пикселях от «09:30 Кастинг Dior» оставалось «09:30…» — то есть
   время, которое и так написано слева на линейке, вместо названия, ради
   которого на календарь и смотрят. Обрезка никуда не делась: её держит высота
   коробки, то есть длина съёмки, а не длина строки. */
.wk-ev .ev-title { align-items: flex-start; }
.wk-ev .ev-title-main {
  white-space: normal; overflow: visible; text-overflow: clip;
  overflow-wrap: break-word;
}
/* Полколонки — это полсотни пикселей, и «Кастинг» в них не помещается целым
   словом: браузеру остаётся либо разорвать его посередине, либо выпустить за
   карточку и обрезать молча. Полтора пикселя кегля и пара пикселей отступа
   дешевле обоих ответов — слово встаёт целиком, а мельче соседних такая
   карточка и так честно: места у неё вдвое меньше. */
.ev-card.wk-ev-narrow { padding: 4px 5px; }
.wk-ev-narrow .ev-title-main { font-size: 11px; letter-spacing: -0.015em; }


/* ── LIST VIEW ── */
.list-container {
  flex: 1; overflow-y: auto; scrollbar-gutter: stable;
  padding: 0 var(--pad-x) 80px;
  background: var(--bg);
}
/* Ширины у листа нет (issue #212): строка съёмки идёт от края до края, как и
   правило под шапкой группы. Тысяча сто пикселей оставляли справа полосу
   пустого экрана в треть ширины — и по ней же обрывались линии, из-за чего
   список выглядел не колонкой, а вырезанным куском чужой страницы. Читаемость
   длинной строки держит не обрезанный лист, а то, что в ней стоит: дата у
   левого края, чипы у правого, название между ними. */
.list-group { margin-bottom: 36px; }
.list-group-header {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 0;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--rule);
}
.list-group-name {
  font-family: var(--display);
  font-size: 18px; font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}
/* Кнопка «наверх» у имени группы (issue #262). Видна всегда, а не по наведению,
   как корзина в строке съёмки: корзина отвечает за действие и до руки не нужна,
   а эта ещё и говорит состояние — спрятанная, она сообщала бы «не закреплено» о
   каждой группе разом. Пока не нажата, покрашена цветом линеек: тише имени
   рядом, но темнее фона — по тому же порядку трёх цветов, каким стоит цифра в
   боковой колонке.

   Размер и радиус — те же, что у корзины строки; своих чисел у неё нет, потому
   что два значка одного экрана, нарисованные разными числами, расходятся на
   первой же правке одного из них. */
.list-group-pin {
  width: 26px; height: 26px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--muted-soft);
  cursor: pointer; transition: background .1s, color .1s;
}
.list-group-pin svg { width: 15px; height: 15px; }
.list-group-pin:hover { background: var(--hover); color: var(--ink); }
/* Закреплённая — синим, тем же, каким продукт красит выбранное вообще
   (`--accent`): «нажато» обязано читаться с расстояния, с какого читается имя,
   а не узнаваться наведением. */
.list-group-pin.is-on { color: var(--accent); }
/* Своё правило наведения у нажатой: цвет ей задан классом, и общий `:hover`,
   стоящий выше с тем же весом, ей ничего не даёт — подложка под рукой обязана
   появляться и здесь, иначе кнопка, которая откручивает, выглядит неживой. */
.list-group-pin.is-on:hover { background: var(--hover); }
.list-item {
  display: flex; align-items: center; gap: 12px;
  padding: 8px;
  border-bottom: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  margin: 0 -8px;
  cursor: pointer;
  transition: background .08s;
}
/* Наведение голубое, а не серое (issue #224: «при наведении выделяется
   голубым: как на остальных видах»). Тот же `--day-hover`, что у клетки месяца,
   столбца недели и дня в году, и по той же причине, по какой он появился там: серым в
   списке покрашены чипы граф и подложка соседних экранов, и подсветка пятым
   оттенком серого сообщала не «здесь рука», а «эта строка какая-то ещё».
   Внутренней рамки (`--day-hover-line`) в отличие от месяца нет: строки уже
   разделены линиями, и обведённая строка дала бы две лишние поперёк списка. */
.list-item:hover { background: var(--day-hover); }
.list-item:last-child { border-bottom: none; }
/* Точка статуса — тот же кружок, что стоит на полосе недели (`.ev-dot`), и
   того же размера: два ответа «какой это статус» на одном экране обязаны
   выглядеть одинаково. */
.list-item-dot {
  width: 7px; height: 7px; border-radius: 50%;
  flex-shrink: 0;
}
/* Дата — столбец, по которому список читают сверху вниз; съехавшая на пару
   букв, она перестаёт им быть, поэтому ширина задана, а не набрана содержимым.
   Задана она снизу (`min-width`), с тех пор как в графу встал диапазон
   многодневной съёмки (issue #224). 136px — это ширина самого длинного
   написания, какое букер видит изо дня в день: «26 окт – 2 ноя 2027», сезон в
   Милане с переходом через месяц. Съёмка, переходящая из года в год, в них не
   влезает и раздвинет свою строку сама — это лучше обеих альтернатив, потому
   что обрезанная дата врёт, а колонка шириной в самый редкий случай отнимает
   у названия сорок пикселей во всех остальных. */
.list-item-date {
  flex: 0 0 auto; min-width: 136px;
  font-size: 13px; font-weight: 500;
  color: var(--ink); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.list-item-title { font-size: 14px; color: var(--ink); flex: 1; min-width: 0; }
/* Чипы графы съёмки: заказчик, букер, город. Заливка светлая и одна на все три
   — цветом здесь говорит только точка статуса, а три крашеных чипа рядом с ней
   превращают строку в мозаику (та же причина, по которой `.ev-agency` в
   карточке месяца не чип вовсе). Значок цвета подписи и без своего фона:
   рисунок в строке — это буква, а не кнопка. */
.list-item-tags {
  display: flex; align-items: center; gap: 6px;
  min-width: 0; flex-shrink: 1;
}
.list-tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 8px; border-radius: var(--radius-sm);
  font-size: 12px; font-weight: 500;
  background: var(--surface-2); color: var(--ink-soft);
  max-width: 200px; min-width: 0;
}
.list-tag svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--muted); }
.list-tag-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Кнопка удаления — квадрат со значком (issue #224). Рамки у неё больше нет:
   обведённой она читалась второй кнопкой строки, при том что кнопка в строке
   одна и это сама строка. Красной кнопка тоже не становится заранее — цвет
   опасности приходит под курсором, когда рука уже на ней. */
.list-item-del {
  width: 26px; height: 26px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; border-radius: var(--radius-sm);
  border: 0; background: transparent;
  cursor: pointer; color: var(--muted);
  transition: all 0.15s; opacity: 0;
}
.list-item-del svg { width: 15px; height: 15px; }
.list-item:hover .list-item-del { opacity: 1; }
.list-item-del:hover { background: var(--hover); color: var(--danger); }
.list-empty { font-size: 13px; color: var(--muted); padding: 8px 0; }

/* ── SIDE PANEL ── */
/* Панель занимает всю высоту под шапкой окна, а не только высоту сетки
   (issue #167): она стояла в `.content-area`, то есть начиналась под полосой с
   «Видом» и «+ Событие», и открытая карточка съёмки читалась как подпись к
   календарю, а не как лист. Верх — `--topbar-h`, то есть ровно под строкой с
   «Выйти»: выше стоит то, что относится к продукту целиком, и накрывать его
   карточкой одной съёмки нельзя.

   `absolute` внутри `.main` (у неё `position: relative`), а не место в потоке:
   в потоке она обязана быть соседкой того, у кого отбирает ширину, — то есть
   стоять внутри `.content-area` и начинаться там же, где та. Ширину за неё
   отдаёт `--side-pad` — то же число и то же движение, что у полосы шапки.

   Слой назван числом, потому что теперь она с содержимым пересекается: выше
   липких заголовков сетки (10–15), ниже чата и меню (60 и дальше) — панель
   накрывает календарь, но не то, что стоит поверх продукта целиком.

   Верх — плюс `--panel-top-off`, и на широком окне это ноль. Что за число, см.
   в блоке 1160px ниже. */
.side-panel {
  position: absolute; top: calc(var(--topbar-h) + var(--panel-top-off, 0px));
  right: 0; bottom: 0; z-index: 40;
  width: 0; overflow: hidden;
  border-left: 1px solid transparent; background: var(--bg);
  /* Верх едет тем же движением, что и ширина: раскрытый поиск опускает карточку
     под полосу (см. блок 1360px), и рывком это читалось бы как перерисовка. */
  transition: width .3s cubic-bezier(.5,0,.2,1), border-color .3s cubic-bezier(.5,0,.2,1),
              top .3s cubic-bezier(.5,0,.2,1);
}
/* Сколько ширины забирает открытая панель. Число живёт на `.app`, а не в
   правиле самой панели, потому что спрашивают его двое: панель и полоса шапки
   над сеткой, которая обязана уехать ровно на столько же (см. `.view-row`).
   Два написания одного числа развело бы их на первой же правке ширины.

   Имя — `--panel-w`, и это не вкусовщина: `--side-w` уже занято левой колонкой
   (`:root` в начале файла, 248px), и читает его та же `.app` — своим
   `grid-template-columns`. Правая панель, назвавшаяся тем же именем на том же
   элементе, перебивала левую колонку и растягивала её до полтыщи пикселей на
   каждом экране продукта. Одно имя на два числа — это не тесное соседство, а
   одно из них, стёртое молча.

   Событию хватает колонки: свойства там — «Status», «Date», «Booker», и
   растянутые на полэкрана они превращают панель в поле пустоты справа. */
.app { --panel-w: min(520px, 92vw); }
/* Карточка модели чуть шире событийной, но около трети экрана: модели смотрят
   подряд — открыл одну, вернулся к сетке глазами, открыл следующую, — и панель
   на полэкрана оставляла справа поле пустоты, а от списка почти ничего. */
body.mode-db .app { --panel-w: min(640px, 38vw); }
.side-panel.open { width: var(--panel-w); border-left-color: var(--rule); }
/* Браузер, не понимающий `:has()`, не выполнит и правило, которое освобождает
   панели место, — а панель с тех пор стоит над содержимым и без этого места
   легла бы поверх правых пятисот пикселей календаря. Такому браузеру карточка
   открывается на весь экран, как на телефоне: съёмку он показывает целиком, а
   сетку не портит. Firefox до 121 и Safari до 15.4; проверяется само наличие
   селектора, а не версия. */
@supports not (selector(:has(*))) {
  .side-panel.open {
    position: fixed; inset: 0; z-index: 160;
    width: 100%; border-left: none;
  }
  /* Раз панель здесь во весь экран, конечная мерка содержимого — этот экран, а
     не `--panel-w`: иначе карточка встала бы колонкой в 640px, а справа от неё
     осталось бы пустое поле в половину окна. */
  .side-panel.open .side-panel-inner { width: 100%; }
}
/* Ширина — конечная, а не «сколько у панели сейчас». Панель раскрывается треть
   секунды, и `100%` означало, что карточка пересобирается на каждом кадре этой
   поездки: имя переносится на вторую строку и обратно, вкладки сворачиваются в
   два ряда, свойства встают столбиком, фотография в шапке едет вместе с ними.
   Набранная сразу по конечной мерке, карточка просто выходит из-под края —
   панели `overflow: hidden`, и лишнего не видно. То же и на закрытии.

   Заодно это чинит и мерку имени: `_fitName` меряет коробку заголовка, и
   нулевую ширину «панель ещё едет» ему теперь показать некому. */
.side-panel-inner {
  width: var(--panel-w, 100%); height: 100%; overflow-y: auto;
  padding: 0 0 64px;
  /* Полоса прокрутки прижималась к кресту закрытия и читалась как элемент,
     который «надо убрать»; контент по-прежнему скроллится. */
  scrollbar-width: none;
}
.side-panel-inner::-webkit-scrollbar { display: none; }

/* ── NOTION-STYLE EVENT CARD ── */
.spn { display: flex; flex-direction: column; }

/* Шапка карточки события: название и значки в одной строке (issue #167).
   Полоса кнопок стояла отдельной строкой над названием, и первое, что человек
   читал в открытой съёмке, было «удалить». Значки прижаты к верху названия, а
   не к его середине: название бывает в три строки, и центрированный крест
   уезжал бы от угла тем дальше, чем длиннее съёмка. */
.spn-head {
  display: flex; align-items: flex-start; gap: 6px;
  padding: 10px 12px 22px 24px; flex-shrink: 0;
}
.spn-head .spn-title { flex: 1; min-width: 0; padding: 0; }
.spn-head-acts { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
/* Значки обводкой и одного размера — тот же вид, что у левого меню продукта.
   `.spn-close-btn` рядом — крест на карточке модели, правила у них одни. */
.spn-icon-act, .spn-close-btn {
  background: none; border: none; cursor: pointer;
  color: var(--muted); padding: 6px; border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  transition: background .1s, color .1s;
}
.spn-icon-act:hover, .spn-close-btn:hover { background: var(--surface-2, #EFEEEC); color: var(--text); }

/* Title */
/* Заголовок события — шрифтом всего остального продукта (issue #179). Засечками
   он стоял как «имя работы, а не подпись к полю», и в одиночку внутри панели это
   читалось; читается он, однако, не в одиночку, а рядом с левой колонкой, шапкой
   календаря и собственными подписями граф, и единственная засечная строка на
   экране выглядела занесённой с другого экрана, а не выделенной.

   Размер — `--t-display`, тот же токен, которым набраны заголовки продукта, а не
   своё число рядом с ним: «немного побольше» просили один раз, а расходятся два
   написания одного кегля на каждой следующей правке типографики. Тридцати
   пикселей, которые тут стояли, в шкале нет вовсе — это и был след того, что
   заголовок жил сам по себе.

   Вес 600, а не 400: у засечного лица толщина штриха своя, и перенесённая
   числом в Inter она даёт не «крупно», а «бледно». Отступ под заголовком
   держит `.spn-head` — 22px вместо 14: просили и его, и стоит он там, потому
   что внутри шапки `padding` заголовка обнулён (значки в той же строке). */
.spn-title {
  font-family: var(--sans); font-size: var(--t-display); font-weight: 600;
  line-height: 1.2; padding: 0 24px 14px; color: var(--text);
  outline: none; word-break: break-word;
}
/* Серая заливка под заголовком отвечает на «сюда можно нажать», и вопрос этот
   живёт ровно до нажатия (issue #247). В редактировании на него уже ответили:
   стоит каретка, набранное выделяется синим — а заливка под всей строкой в этот
   момент читается как выделенный фрагмент, которого никто не выделял.

   `:not(:focus)` у hover не украшение: курсор после щелчка остаётся над
   заголовком, и без этой оговорки серое держалось бы весь набор — то есть
   ровно тогда, когда его и просили убрать. */
.spn-title-editable:hover:not(:focus) { background: var(--surface-2); border-radius: 4px; }

/* Properties grid */
.spn-props { display: flex; flex-direction: column; padding: 0 20px; }
.spn-row {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 5px 4px; border-bottom: none; min-height: 32px;
  border-radius: 4px;
}
.spn-row:hover { background: var(--surface-2); }
/* В форме нового события строка под курсором не подсвечивается (issue #228).
   В карточке серая полоса отвечает на «куда нажимать, чтобы поправить»: там
   строка и есть кнопка, значение написано текстом и правится по щелчку. В
   форме нажимать никуда не нужно — каждая графа уже открыта полем, — и та же
   полоса говорила о готовности к правке там, где правка и так идёт. */
.add-spn .spn-row:hover { background: none; }
.spn-label {
  width: 130px; flex-shrink: 0; font-size: 13px; color: var(--muted);
  padding-top: 4px; font-weight: 400;
}
.spn-val {
  flex: 1; font-size: 13px; color: var(--text); display: flex;
  align-items: center; flex-wrap: wrap; gap: 5px; padding-top: 2px;
}
/* Графа, которая сама и есть кнопка (issue #244). Рука — то, чем такая графа
   отличается от подписи до того, как по ней нажали (тот же довод, что у
   `.spn-select` и `.spn-job-pick`); подсветку под курсором держит `.spn-row`,
   и второй, своей, здесь быть не должно — полоса внутри полосы читается как
   вложенное поле.

   `min-height` — потому что пустая графа высотой в ноль не нажимается ничем:
   `.spn-row` держит свои 32 пикселя, но `align-items: flex-start` не растягивает
   по ним значение. Число — те же 22, что остаются от строки за вычетом её
   отступов, то есть графа занимает строку целиком, как и выглядит. */
.spn-val-add { cursor: pointer; min-height: 22px; }
.spn-muted { color: var(--muted); font-size: 13px; }
.spn-empty { color: var(--muted); font-size: 13px; }

/* «Свободна ли ты?» — the asking and the answer, inside the event panel. */
.spn-avail { font-size: 13px; }
.spn-avail-answer { font-weight: 500; }
.spn-avail.avail-free .spn-avail-answer { color: #1E6640; }
.spn-avail.avail-busy .spn-avail-answer { color: #991B1B; }
.spn-avail-note { margin-top: 3px; color: var(--ink); opacity: .85; }
.spn-avail-meta { margin-top: 3px; color: var(--muted); font-size: 12px; }
.spn-avail-link { display: flex; gap: 4px; align-items: center; margin-top: 6px; }
.spn-avail-input { flex: 1; min-width: 0; padding: 5px 8px; font-size: 12px;
  border: 1px solid var(--rule, rgba(55,53,47,0.12)); border-radius: 6px;
  background: var(--surface, #fff); color: var(--ink); }
.spn-avail-btn { margin-top: 4px; padding: 5px 10px; font-size: 13px; cursor: pointer;
  border: 1px solid var(--rule, rgba(55,53,47,0.12)); border-radius: 6px;
  background: var(--surface, #fff); color: var(--ink); font-family: inherit; }
.spn-avail-btn:hover { background: var(--hover, #EFEEEC); }
/* Работа в учёте, заведённая из этого события. Кнопка, а не ссылка: ведёт она
   на другой экран того же приложения, адреса у раздела учёта работ нет. */
.spn-work-link { padding: 0; font-size: 13px; cursor: pointer; text-align: left;
  border: 0; background: none; color: var(--ink); font-family: inherit;
  border-bottom: 1px solid var(--rule, rgba(55,53,47,0.24)); }
.spn-work-link:hover { color: var(--accent); }
.spn-icon-btn { flex: none; width: 26px; height: 26px; cursor: pointer; font-size: 12px;
  border: 1px solid var(--rule, rgba(55,53,47,0.12)); border-radius: 6px;
  background: var(--surface, #fff); color: var(--ink); }
.spn-icon-btn:hover { background: var(--hover, #EFEEEC); }
.spn-icon-danger:hover { background: #FEE2E2; color: #991B1B; }

/* Status tag with dot */
.spn-status-tag {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 12px; border-radius: 20px;
  font-size: 13px; font-weight: 500; cursor: pointer;
  transition: filter 0.15s; user-select: none;
}
.spn-status-tag:hover { filter: brightness(0.94); }
.spn-status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }

/* Date display chip + inline edit inputs */
.spn-date-chip {
  font-size: 13px; color: var(--text);
  border-radius: 4px; padding: 2px 4px; display: inline-block;
}
.spn-date-chip-edit { cursor: pointer; }
.spn-date-chip-edit:hover { background: var(--bg); }
/* Время рисуется той же подписью, что и дата: строка над строкой, одинаковые
   поля по щелчку. Класс свой, а не общий с датой: подписи стоят рядом, и одно
   правило на обе означало бы, что правку одной строки видно на другой. */
.spn-time-chip {
  font-size: 13px; color: var(--text);
  border-radius: 4px; padding: 2px 4px; display: inline-block;
}
/* Время не назначено — графа пуста (issue #182), и попасть в неё всё-таки
   надо: подпись без текста шириной в ноль не нажимается ничем. Поэтому пустая
   держит ширину сама, и подсветка под курсором показывает, куда именно
   целиться. Тому, кто событий не правит, её не рисуют вовсе — там нажимать
   нечего. */
.spn-chip-empty { min-width: 64px; min-height: 17px; }

.spn-dates { gap: 6px; align-items: center; }
.spn-date-in {
  border: none; background: none; outline: none; font-family: inherit;
  font-size: 13px; color: var(--text); padding: 2px 4px; cursor: pointer;
  border-radius: 4px; transition: background 0.1s;
}
.spn-date-in:hover, .spn-date-in:focus { background: var(--bg); }
.spn-arrow { color: var(--muted); font-size: 12px; }

/* Tag inputs */
.spn-tags-wrap { display: flex; align-items: center; flex-wrap: wrap; gap: 5px; }
/* Тег набран тем же кеглем, что значение графы рядом (issue #179). Двенадцать
   пикселей здесь были попыткой сказать, что тег — не текст, а метка; сказано
   это уже заливкой и скруглением, а кеглем получилось сказать другое: «Editorial»
   в строке «Тип работы» выглядел мельче, чем «Пусто» в строке под ней, то есть
   заполненная графа читалась тише пустой. Число не своё, а то же, что у
   `.spn-val` и `.spn-empty`: три написания одного кегля в одном блоке разъедутся
   на первой же правке. */
.spn-tag {
  display: inline-flex; align-items: center; gap: 3px;
  background: var(--bg); border-radius: 4px; padding: 2px 7px;
  font-size: 13px; color: var(--text);
}
.spn-tag-del {
  background: none; border: none; cursor: pointer; font-size: 13px;
  color: var(--muted); padding: 0; line-height: 1; display: flex; align-items: center;
}
.spn-tag-del:hover { color: var(--danger); }
/* ── «…» у тега в графе (issue #290) ──
   Те же числа и то же правило появления, что у «…» строки выпадающего списка
   (`.spn-opt-dots`) и строки в колонке слева: видны по наведению и по фокусу с
   клавиатуры. Меньше на шесть пикселей — кнопка живёт внутри чипа высотой в
   строку, а не в строке меню.

   Место под себя точки держат всегда (`opacity`, а не `display`), и это здесь
   не мелочь: тег, растущий под курсором, двигал бы соседние теги той же графы —
   попасть во второго клиента можно было бы только со второй попытки. Тем же
   правилом и по той же причине не дёргается плитка модели (issue #277).

   `pointer-events` снимается вместе с прозрачностью: невидимая кнопка, которую
   всё-таки можно нажать, — это чип, отвечающий меню на щелчок мимо. */
.spn-tag-dots {
  width: 16px; height: 16px; padding: 0; flex: none;
  border: none; background: transparent; cursor: pointer;
  border-radius: var(--radius-sm, 4px);
  color: inherit;
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none;
  transition: background .1s, opacity .1s;
}
.spn-tag-dots svg { width: 13px; height: 13px; }
.spn-tag:hover .spn-tag-dots, .spn-tag:focus-within .spn-tag-dots,
.spn-status-tag:hover .spn-tag-dots, .spn-status-tag:focus-within .spn-tag-dots {
  opacity: .75; pointer-events: auto;
}
.spn-tag-dots:hover { opacity: 1; background: var(--rule, rgba(55,53,47,.12)); }
/* На тач-экране наведения нет вовсе, и спрятанные точки были бы кнопкой,
   которую нельзя нажать, — та же развилка и то же решение, что у «…» строки
   списка. А поскольку убрать тег больше нечем (крестика нет), здесь это не
   удобство, а единственная дверь. */
@media (hover: none) {
  .spn-tag-dots { opacity: .75; pointer-events: auto; }
}
/* Заполненная графа со списком: та же рука, что у пустой (`.spn-val-add`), но
   без её высоты — чип внутри графу уже растянул. Класс свой, потому что вопросы
   разные: `add` говорит «сюда можно завести значение» и у графы с одним тегом
   врал бы (issue #244), `pick` — «здесь открывается список», и это правда для
   графы и с тегом, и без (issue #290). */
.spn-val-pick { cursor: pointer; }
/* Поля ввода в графах «Клиент» и «Букер» больше нет, а с ним ушли и два
   правила: то, что растягивало его по строке (issue #182), и то, что гасило
   браузерную стрелку у `input[list]` (issue #205). Обе графы открывают теперь
   такое же меню, что и «Тип работы» (issue #268), — а у меню карточки ни
   своего поля в строке, ни чужой стрелки нет. */

/* Text inputs in rows */
.spn-text-in {
  border: none; background: none; outline: none; font-family: inherit;
  font-size: 13px; color: var(--text); width: 100%; padding: 2px 4px;
  border-radius: 4px; transition: background 0.1s;
}
.spn-text-in:hover, .spn-text-in:focus { background: var(--bg); }
.spn-text-in::placeholder { color: var(--muted); }
/* Клиент — единственное поле-список среди полей-строк, и `spn-text-in` снимает
   рамку со всех одинаково: получается значение, с виду не отличимое от
   соседнего «Empty», которое просто написано. Рука — то, чем список отличается
   от строки до того, как по нему нажали. */
.spn-select { cursor: pointer; }

/* ── Тип работы: цветной лейбл (issue #194) ──
   Оттенок приходит числом (`--jt-h`, считает `jobTypeHue` в 03-events.js),
   градиент собирается здесь. Так потому, что имя типа набирает человек, а
   `style="…"` — то самое место, где набранное становится разметкой: числу
   пролезть туда нечем. Заодно тёмная тема остаётся вопросом CSS, а не второй
   палитрой в браузерном коде: те же градусы, другая светлота.

   Кегль и скругление — от соседнего `.spn-tag`: цветной лейбл это тот же тег,
   только узнаваемый в списке раньше, чем прочитанный. Своё число здесь было бы
   третьим написанием одного кегля в одном блоке. */
.spn-job-tag {
  background: linear-gradient(135deg, hsl(var(--jt-h) 62% 92%), hsl(calc(var(--jt-h) + 26) 62% 85%));
  color: hsl(var(--jt-h) 42% 28%);
}
body.dark .spn-job-tag {
  background: linear-gradient(135deg, hsl(var(--jt-h) 34% 26%), hsl(calc(var(--jt-h) + 26) 34% 21%));
  color: hsl(var(--jt-h) 55% 86%);
}
/* Чип, открывающий свой же список: рука — то, чем выбор отличается от подписи
   до того, как по нему нажали (тот же довод, что у `.spn-select`). */
.spn-job-pick { cursor: pointer; }
.spn-job-pick:hover { filter: brightness(0.96); }
.spn-job-dot {
  width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0;
  background: linear-gradient(135deg, hsl(var(--jt-h) 62% 78%), hsl(calc(var(--jt-h) + 26) 62% 64%));
}
.spn-opt-on { font-weight: 500; }
.spn-opt-none { color: var(--muted); cursor: default; }
/* Строка, которой нельзя воспользоваться, под курсором не отзывается — и
   сказать это надо двумя классами, а не одним: `.spn-status-opt:hover` стоит
   ниже по файлу и при равном весе побеждает, то есть «Все модели уже на
   съёмке» подсвечивалось бы наравне с выбираемым именем. */
.spn-status-opt.spn-opt-none:hover { background: none; }

/* ── Значок в графе (issue #194) ──
   Кнопка без кнопки: ни рамки, ни заливки, ни подложки — только рисунок,
   16×16 и цветом `--ink-soft`, теми же числами, какими нарисованы значки
   левого меню (`.nav-item .nav-ic`). Подсветка появляется под курсором, и
   она же — единственный ответ «сюда можно нажать»: постоянная подложка
   вернула бы кнопку, которую issue просил убрать. */
.spn-ic-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; flex: none; padding: 0;
  background: none; border: none; border-radius: 4px; cursor: pointer;
  color: var(--ink-soft, #5A584F); transition: background 0.1s, color 0.1s;
}
.spn-ic-btn:hover { background: var(--bg); color: var(--ink, #37352F); }
.spn-ic-btn:disabled { opacity: .45; cursor: default; }
.spn-ic { width: 16px; height: 16px; }
body.dark .spn-ic-btn:hover { background: rgba(255,255,255,0.06); }
/* Адрес, набранный в графе «Веб», — ссылка и выглядит ссылкой. */
.spn-link { color: var(--accent, #2383E2); text-decoration: none; }
.spn-link:hover { text-decoration: underline; }
/* Поле и значок в одной графе: поле забирает строку, значок стоит справа от
   него и не ужимается (`.spn-text-in` объявлена на всю ширину). */
.spn-custom-val .spn-text-in { flex: 1; min-width: 0; }

/* Add property button */
.spn-add-prop {
  margin: 6px 20px 0; padding: 5px 8px; background: none; border: none; cursor: pointer;
  font-family: inherit; font-size: 13px; color: var(--muted);
  border-radius: 4px; text-align: left; transition: background 0.1s; width: calc(100% - 40px);
  display: flex; align-items: center; gap: 6px;
}
.spn-add-prop:hover { background: var(--bg); color: var(--text); }

.spn-divider { height: 1px; background: var(--border); margin: 20px 24px 0; }

/* Description */
.spn-desc-section { padding: 18px 24px 0; }
/* Высоту описания ставит `SidePanel.growDesc` — по тексту, но не меньше, чем
   до низа панели (issue #205); он же прячет прокрутку, которой после этого
   нечего показывать. Своей прокрутки поле здесь не теряет, и это нарочно: у
   замера есть случай, когда он не состоялся (панель ещё не раскрыта, мерить
   нечего), и текст, обрезанный правилом CSS, пропал бы в таком случае молча —
   а прокрутка всего лишь вернёт то, на что жаловались. */
.spn-desc {
  /* Блок, а не строка: `textarea` стоит на базовой линии, и под ним остаётся
     место под хвосты букв — четыре пикселя, которых не видно, пока поле не
     подобрано по высоте вплотную к низу панели. Тогда они и оказываются той
     самой прокруткой, которую здесь убирают: панель едет ровно на них. */
  display: block;
  width: 100%; min-height: 120px; border: none; background: none; outline: none; resize: none;
  font-family: inherit; font-size: 14px; line-height: 1.65; color: var(--text);
  padding: 4px; border-radius: 4px; transition: background 0.1s;
}
.spn-desc:hover, .spn-desc:focus { background: var(--bg); }
.spn-desc::placeholder { color: var(--muted); }
.spn-desc-ro { font-size: 14px; line-height: 1.65; color: var(--text); padding: 4px; }

/* ── Форма нового события ──
   Та же разметка .spn, что у карточки съёмки (issue #168), и с issue #243 — в
   той же коробке: форма открывается боковой панелью, а не окном по центру.
   Своих отступов у неё поэтому не осталось вовсе — их несут те же spn-блоки,
   что и в карточке.

   Ширины здесь тоже больше нет, и это главное, что даёт переезд. Просили
   «шире на 1,5 см» (issue #228), стояло `.modal-ev { width: 460px }` — и не
   работало: `.modal` объявлена ниже по файлу тем же весом и побеждала, форма
   всё это время была 420px, а число 460 не значило ничего и врало всякому, кто
   его читал. Лечилось это селектором из двух классов и числом 477. Теперь
   ширину задаёт панель (`--panel-w`, 520px) — одна на карточку и на форму, и
   разъехаться им больше негде. */
/* Пустой заголовок подсказывает, что он и есть поле названия, — тем же
   способом, что поле чата (data-placeholder): у contenteditable нет своего. */
.add-spn .spn-title:empty::before {
  content: attr(data-placeholder); color: var(--muted); pointer-events: none;
}
/* Отступ у кнопок и ошибки свой: графы отбиты `.spn-props` (20px), заголовок и
   описание — 24px, и ряд «Отмена · Добавить», вставший по краю граф, читался бы
   сдвинутым относительно всего, что над ним. */
.add-spn .modal-error { padding: 0 24px; }
.add-spn .modal-actions { padding: 0 24px; }
/* Список участников подписан наведением, а не строкой в самом списке (issue
   #182): «Добавить…» внутри него было тем же лишним словом, что и в соседних
   графах. Ширина поэтому по содержимому — пустой список, растянутый на
   `width: 100%` от `.spn-text-in`, оставил бы в графе полтораста пикселей
   пустого места, читающихся как незаполненное значение. */
.spn-part-add { width: auto; max-width: 150px; }
/* Файл принимает вся рамка события: подсветка — единственный ответ на
   «а сюда можно бросить?», пока файл ещё в руке. Рамка — панель целиком, а не
   разметка формы: пустое место под «Добавить» это та же открытая форма, и
   отпущенный там файл не должен проваливаться мимо. */
.side-panel-inner.drag-over { outline: 2px dashed var(--accent, #3B82F6); outline-offset: -6px; }
.ev-drop-hint { font-size: 12px; }
/* Поле своей графы в меню «+» */
.spn-field-new { padding: 6px 10px; border-top: 1px solid var(--border); }
.spn-field-new-in {
  width: 100%; border: none; background: none; outline: none;
  font-family: inherit; font-size: 13px; color: var(--text); padding: 2px;
}
.spn-field-new-in::placeholder { color: var(--muted); }
.spn-custom-val { display: flex; align-items: center; gap: 4px; }

/* Status dropdown menu */
.spn-status-menu {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.12);
  z-index: 500; min-width: 170px; overflow: hidden;
}
.spn-status-opt {
  display: flex; align-items: center; gap: 8px; padding: 8px 14px;
  font-size: 13px; cursor: pointer; transition: background 0.1s;
}
/* Подсветка под курсором — `--hover`, а не `--bg` (issue #265: «при
   перемещении мышки по участникам должен подсвечиваться тот, на которого
   наведена мышка»). Правило здесь стояло и раньше, но в светлой теме `--bg` и
   `--surface` — оба чистый белый, а меню нарисовано на `--surface`: подсветка
   красилась фоном в фон. Меню участников от этого читалось как список без
   ответа на мышь — по десятку имён человек ведёт курсор вслепую и попадает не
   в ту строку. `--hover` для того и заведён, им подсвечиваются строки левого
   меню и списка моделей, и он разный в обеих темах. */
.spn-status-opt:hover { background: var(--hover, #EFEEEC); }
/* «＋ Новый статус…» — не шестой статус, а дверь, и выглядеть как строка списка
   она не должна: линия сверху отделяет её от того, что выбирают, кегль и цвет
   те же, что у «⚙ Поправить список» в пикере тегов. Кружка цвета у неё нет —
   он бы обещал, что цвет уже выбран. */
.spn-opt-new {
  border-top: 1px solid var(--border); font-size: 12px; color: var(--muted);
}
.spn-opt-new:hover { color: var(--text); }

/* ── «…» у строки списка (issue #267) ──
   Имя занимает строку целиком, чтобы точки встали у правого края и не ездили
   вслед за длиной слова: столбец кнопок, выровненный текстом, ровен ровно у
   того, у кого мы его смотрели. */
.spn-opt-name { flex: 1; min-width: 0; }
/* Появляются по наведению и по фокусу с клавиатуры — теми же числами и тем же
   правилом, что «…» строки в колонке слева. Постоянные точки у каждой строки
   превратили бы список из восьми типов работы в список из восьми кнопок. */
.spn-opt-dots {
  width: 22px; height: 22px; padding: 0; flex: none;
  border: none; background: transparent; cursor: pointer;
  border-radius: var(--radius-sm, 4px);
  color: var(--muted, #9B9A93);
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none;
  transition: background .1s, color .1s, opacity .1s;
}
.spn-opt-dots svg { width: 15px; height: 15px; }
.spn-status-opt:hover .spn-opt-dots,
.spn-status-opt:focus-within .spn-opt-dots,
.spn-opt-open .spn-opt-dots { opacity: 1; pointer-events: auto; }
.spn-opt-dots:hover { background: var(--rule, rgba(55,53,47,.12)); color: var(--ink, #37352F); }
/* Строка, под которой раскрыты «Редактировать» и «Удалить», остаётся
   подсвеченной и когда курсор ушёл на них: два пункта под ней — её
   продолжение, и строка, погасшая над открытым меню, читается как чужая. */
.spn-opt-open { background: var(--hover, #EFEEEC); }
/* Две строки под своей: отступ ставит их под именем, а не под кружком цвета, —
   так видно, что они про строку выше, а не шестой тип работы. */
.spn-opt-sub {
  padding-left: 32px; font-size: 12px; color: var(--muted, #9B9A93);
}
.spn-opt-sub:hover { color: var(--text); }
.spn-opt-danger { color: var(--danger, #C4453C); }
.spn-opt-danger:hover { background: var(--danger-bg, rgba(196,69,60,.10)); color: var(--danger, #C4453C); }

/* На тач-экране наведения нет вовсе, и спрятанные точки были бы кнопкой,
   которую нельзя нажать, — та же развилка и то же решение, что у «…» строки
   в колонке слева. */
@media (hover: none) {
  .spn-opt-dots { opacity: 1; pointer-events: auto; }
}

/* ── MODALS ── */
.modal-overlay {
  display: none; position: fixed; inset: 0;
  background: rgba(15,15,15,0.30); z-index: 400;
  align-items: center; justify-content: center;
  animation: modal-fadein .15s;
}
@keyframes modal-fadein { from { opacity: 0; } to { opacity: 1; } }
.modal-overlay.open { display: flex; }
.modal {
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 22px 24px;
  width: 420px; max-width: 92vw;
  max-height: 90vh; overflow-y: auto;
  overscroll-behavior: contain;
  box-shadow: 0 8px 32px rgba(0,0,0,0.18);
  position: relative;
  animation: modal-rise .18s;
}
@keyframes modal-rise { from { transform: translateY(8px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
.modal h3 {
  font-family: var(--display);
  font-size: 22px; font-weight: 700; line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-bottom: 4px;
}
.modal-date {
  font-size: 13px; color: var(--muted);
  margin-bottom: 18px;
}
.modal-form { display: flex; flex-direction: column; gap: 12px; }
.modal-form label {
  font-size: 12px; font-weight: 500;
  letter-spacing: 0; color: var(--muted);
  text-transform: none; display: block; margin-bottom: 4px;
}
.modal-form input, .modal-form select {
  width: 100%;
  padding: 7px 10px;
  border: 1px solid var(--rule-strong);
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--radius-sm);
  font-family: inherit; font-size: 14px;
  outline: none;
  transition: border-color .1s, box-shadow .1s;
  height: 32px;
}
.modal-form input:focus, .modal-form select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(35,131,226,0.18);
}
.modal-form-row { display: flex; gap: 10px; }
.modal-form-row > div { min-width: 0; }
.modal-hint { color: var(--muted); font-weight: 400; font-size: 11px; }
.modal-tags-wrap {
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 6px 8px;
  border: 1px solid var(--rule-strong);
  background: var(--surface);
  border-radius: var(--radius-sm);
  min-height: 32px; align-items: center;
}
.modal-tags { display: contents; }
.modal-tag {
  display: inline-flex; align-items: center; gap: 4px;
  background: var(--hover); color: var(--ink);
  border-radius: 4px; padding: 2px 6px;
  font-size: 12px; line-height: 1.4;
}
.modal-tag-del {
  background: transparent; border: 0; color: var(--muted);
  cursor: pointer; padding: 0 2px; font-size: 14px; line-height: 1;
}
.modal-tag-del:hover { color: var(--ink); }
.modal-tag-input {
  flex: 1; min-width: 100px;
  border: 0 !important; background: transparent !important;
  padding: 2px 0 !important; height: 22px !important;
  outline: none;
}
.modal-tag-input:focus { box-shadow: none !important; }

/* ── Create-able tag picker (event form: Type of job / agency / booker) ── */
.tagpick { position: relative; }
/* Рамки у графы нет — ни своей, ни браузерной (issue #228). Форма собрана из
   той же разметки, что карточка события, а в карточке не обведено ни одно поле:
   значение стоит рядом с подписью, а не в коробке. Здесь рамок было даже две,
   одна в другой: своя у коробки и родная у `input` — правила поля были написаны
   под `.modal-form`, которого в этой форме нет, селектор не доставал, и поле
   оставалось браузерным. Отступы сняты по той же причине: восемь пикселей слева
   отбивали значение этой графы от значений соседних. */
.tagpick-box {
  display: flex; flex-wrap: wrap; gap: 5px; align-items: center;
  min-height: 22px;
}
.tagpick-chips { display: contents; }
/* Кегль тот же, что у значения графы рядом и у подписи слева от неё (13px,
   issue #194: «шрифт полей должен соответствовать шрифтам заголовков по
   размеру и стилю»). Двенадцать пикселей делали заполненную графу тише
   пустой — ровно то, что чинил issue #179 в панели события; форма собрана из
   той же разметки и обязана читаться так же. */
.tagpick-chip {
  display: inline-flex; align-items: center; gap: 4px;
  background: var(--hover); color: var(--ink);
  border-radius: 4px; padding: 2px 7px; font-size: 13px; line-height: 1.4;
}
.tagpick-x { background: transparent; border: 0; color: var(--muted); cursor: pointer; padding: 0 2px; font-size: 14px; line-height: 1; }
.tagpick-x:hover { color: var(--ink); }
/* Поле занимает графу до правого края — тем же правилом и по той же причине,
   по какой его занимала графа открытой карточки (issue #182): щелчок по пустому
   месту графы попадает в само поле, поэтому подпись «Выбрать или создать…»
   внутри поля больше не нужна. Кегль и лицо — как у значения соседней графы: без них поле
   набиралось браузерным шрифтом и выглядело занесённым с чужого экрана. */
.tagpick-input {
  flex: 1; min-width: 90px; width: auto;
  border: 0; background: transparent; padding: 2px 4px; height: 22px; box-shadow: none;
  outline: none; font-family: inherit; font-size: 13px; color: var(--text);
}
.tagpick-input:focus { box-shadow: none; border: 0; }
.tagpick-menu {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 30;
  max-height: 220px; overflow-y: auto;
  background: var(--bg); border: 1px solid var(--rule-strong); border-radius: var(--radius-sm);
  box-shadow: 0 8px 24px rgba(0,0,0,.12); padding: 4px;
}
.tagpick-menu.hidden { display: none; }
.tagpick-opt { padding: 6px 8px; border-radius: 4px; font-size: 13px; cursor: pointer; }
.tagpick-opt:hover { background: var(--hover); }
.tagpick-create { color: var(--accent); }
.tagpick-manage {
  margin-top: 4px; padding: 6px 8px; border-top: 1px solid var(--rule);
  font-size: 12px; color: var(--muted); cursor: pointer;
}
.tagpick-manage:hover { color: var(--ink); }

/* ── Event notes textarea + file attachments ── */
.modal-textarea {
  width: 100%; padding: 8px 10px; min-height: 84px; resize: vertical;
  border: 1px solid var(--rule-strong); background: var(--surface); color: var(--ink);
  border-radius: var(--radius-sm); font-family: inherit; font-size: 14px; line-height: 1.5; outline: none;
}
.modal-textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(35,131,226,0.18); }
.ev-files-list { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.ev-file-chip, .spn-file-chip {
  display: inline-flex; align-items: center; gap: 4px;
  background: var(--hover); border-radius: 4px; padding: 2px 6px; font-size: 12px; max-width: 100%;
}
/* В панели события чип файла — такой же тег, как «Editorial» строкой выше, и
   кегль у него тот же (issue #179). Правило общее с модалом заведения события,
   а число разное: в модале чипы стоят под полем ввода, а не в графе, и
   равняться им не на что. */
.spn-file-chip { font-size: 13px; }
/* Имя файла обрезается, а целиком его называет `title` (issue #194): договор
   со сканом занимал графу целиком, а прочитать в нём надо было первые слова.
   Потолок в пикселях, а не в символах: узкое имя не обязано растягивать чип до
   потолка, а широкое обязано остановиться там, где ещё влезает сосед. */
.ev-file-chip a, .spn-file-chip a { color: var(--ink); text-decoration: none; display: inline-flex; align-items: center; gap: 4px; min-width: 0; }
.ev-file-chip .spn-file-name, .spn-file-chip .spn-file-name {
  max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ev-file-chip a:hover, .spn-file-chip a:hover { text-decoration: underline; }
.ev-file-x { background: transparent; border: 0; color: var(--muted); cursor: pointer; padding: 0 2px; font-size: 14px; line-height: 1; }
.modal-actions { display: flex; align-items: center; gap: 8px; margin-top: 14px; justify-content: flex-end; }
/* margin-top: 0 — базовый .btn-primary несёт отступ формы логина, и в ряду с
   «Отменой» он опускал «Добавить» на шесть пикселей ниже соседа. */
.modal-actions .btn-primary { flex: 0 0 auto; width: auto; margin-top: 0; padding: 7px 14px; height: 32px; font-size: 13px; }
.modal-actions .btn-cancel { padding: 7px 14px; height: 32px; }
.btn-cancel {
  background: var(--bg);
  color: var(--ink-soft);
  border: 1px solid var(--rule-strong);
  padding: 7px 14px;
  font-family: inherit;
  font-size: 13px; font-weight: 500;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background .1s;
}
.btn-cancel:hover { background: var(--hover); color: var(--ink); }
.modal-error {
  font-size: 12px; color: var(--danger);
  min-height: 18px; margin-top: 6px;
}
.detail-close, .modal-close {
  position: absolute; top: 12px; right: 12px;
  background: transparent; border: none;
  cursor: pointer;
  font-size: 16px; color: var(--muted);
  width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
}
.detail-close:hover, .modal-close:hover { background: var(--hover); color: var(--ink); }
.modal-title {
  font-size: 18px; font-weight: 700; line-height: 1.2;
  letter-spacing: -0.01em; color: var(--ink);
  margin-bottom: 14px;
}
.modal-input {
  width: 100%; padding: 7px 10px;
  border: 1px solid var(--rule-strong);
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--radius-sm);
  font-family: inherit; font-size: 14px;
  outline: none; height: 32px;
}
.modal-input:focus { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(35,131,226,0.18); }

/* ── USERS MODAL ── */
/* Шапка со счётчиком отделена линией и не уезжает: список сотрудников и форма
   под ним прокручиваются вместе, а «Пользователи · 6 сотрудников» должно
   оставаться на месте — по нему и понимают, сколько мест занято. */
.users-modal {
  width: 620px; padding: 0;
  max-height: 86vh; display: flex; flex-direction: column;
}
.users-head {
  flex-shrink: 0; display: flex; align-items: flex-start; gap: 12px;
  padding: 18px 22px 14px; border-bottom: 1px solid var(--rule);
}
.users-head-t { flex: 1; min-width: 0; }
.users-modal h3 { margin-bottom: 0; font-size: 19px; }
.users-count { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.users-body { flex: 1; overflow-y: auto; padding: 10px 14px 18px; }
.users-eyebrow {
  font-size: 11px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 10px;
}

.user-row {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 8px; border-radius: var(--radius-sm);
}
.user-row:hover { background: var(--surface-2); }
.user-row-avatar {
  width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; color: #fff; font-weight: 600;
}
.user-row-info { flex: 1; min-width: 0; }
/* Обрезается так же, как логин под ним. Без этого длинное имя не сжимало
   строку, а вылезало из своей ячейки и рисовалось поверх чипа роли — то есть
   ровно поверх ответа на вопрос, кому что видно. */
.user-row-name {
  font-size: 13.5px; font-weight: 500; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.user-row-login {
  font-size: 12px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Роль — чип, а не подпись под именем: в строке её ищут глазами, чтобы понять,
   кому что видно, и цвет здесь несёт то же, что нёс раньше цветной текст. */
.user-role-chip {
  flex-shrink: 0; padding: 3px 9px; border-radius: var(--radius-sm);
  background: var(--surface-2); font-size: 11.5px; font-weight: 500;
}
.user-row-actions { display: flex; gap: 4px; flex-shrink: 0; }
.user-row-actions .icon-btn {
  width: auto; height: auto;
  padding: 4px 9px;
  border: 1px solid var(--rule, var(--border));
  border-radius: var(--radius-sm, 5px);
  background: transparent;
  font-size: 11px; line-height: 1.4;
  color: var(--muted);
  white-space: nowrap;
}
.user-row-actions .icon-btn:hover { background: var(--hover); color: var(--ink, var(--text)); }
.user-row-actions .icon-btn.danger:hover {
  background: var(--hover); color: var(--danger, #D95F5F);
  border-color: var(--danger, #D95F5F);
}
/* Форма нового сотрудника: поля парами, потому что их шесть и в столбик они
   растягивают модал вдвое. Подписи в самих полях — каждое называется одним
   словом, и строка «Логин» над полем шириной в это же слово ничего не добавляет. */
#add-user-form { padding: 0 8px; }
.nu-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.nu-grid input, .nu-grid select {
  width: 100%; height: 34px; padding: 0 10px;
  border: 1px solid var(--rule-strong);
  background: var(--surface); color: var(--ink);
  border-radius: var(--radius-sm);
  font-family: inherit; font-size: 13px; outline: none;
  transition: border-color .15s, box-shadow .15s;
}
.nu-grid input:focus, .nu-grid select:focus {
  border-color: var(--accent); box-shadow: var(--focus-ring);
}
.nu-color-label { margin: 14px 0 8px; font-size: 12px; color: var(--muted); }
.nu-actions { display: flex; align-items: center; gap: 10px; margin-top: 16px; }
.nu-create { width: auto; height: 34px; margin-top: 0; padding: 0 16px; font-size: 13px; }
.nu-hint { font-size: 12px; color: var(--muted); }
/* Та же подпись, но отдельной строкой под сеткой полей, а не в ряду кнопок. */
#nu-role-hint { margin-top: 8px; line-height: 1.45; }

.color-grid { display: flex; gap: 7px; flex-wrap: wrap; margin-top: 6px; }
.color-swatch {
  width: 22px; height: 22px; border-radius: 50%;
  cursor: pointer;
  box-shadow: none;
  transition: transform .12s, box-shadow .12s;
}
.color-swatch:hover { transform: scale(1.15); }
/* Кольцо, а не рамка: рамка съедает два пикселя самого цвета, и выбранный
   кружок оказывается меньше остальных. */
.color-swatch.selected { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ink); }
.form-divider { height: 1px; background: var(--rule); margin: 10px 8px; }
.sub-label {
  font-size: 12px; color: var(--muted);
  font-weight: 500; margin-bottom: 4px;
}

/* Одноразовая ссылка приглашения: адрес и кнопка «Скопировать» в одной
   строке. `.btn-primary` здесь перебивается — сама по себе она во всю ширину
   и с отступом сверху, а тут стоит рядом с полем. */
/* `.sub-label` объявлен в файле дважды, и побеждает второй — с `uppercase`.
   Для ярлыка вроде «Цвет» это верно, а здесь классом набраны два предложения:
   кому ссылка, до какого числа действует и что показана она один раз. Капсом
   это перестаёт читаться ровно там, где читать обязательно. Правим по месту —
   у класса есть законные употребления, и он не наш. */
#nu-invite .sub-label {
  text-transform: none; letter-spacing: 0; font-size: 12.5px; line-height: 1.45;
}

.invite-copy { display: flex; align-items: stretch; gap: 8px; margin-top: 6px; }
.invite-copy input { flex: 1; min-width: 0; }
.invite-copy .btn-primary {
  width: auto; margin-top: 0; padding: 0 14px; flex-shrink: 0;
}

/* ── Permissions panel ── */
.perm-panel {
  margin: 0 0 8px 42px;
  padding: 10px 12px;
  background: var(--bg); border-radius: 8px;
  border: 1px solid var(--border);
}
.perm-role-row {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 8px;
}
.perm-label { font-size: 12px; color: var(--muted); white-space: nowrap; }
.perm-select, .perm-input {
  flex: 1; padding: 4px 8px; border: 1px solid var(--border);
  border-radius: 6px; font-family: inherit;
  font-size: 12px; background: var(--surface); color: var(--text); outline: none;
}
.perm-input:focus { border-color: var(--accent); }
.perm-err { font-size: 11px; color: var(--danger, #D95F5F); margin: -4px 0 8px; }
.perm-err:empty { display: none; }
.perm-checks { display: flex; flex-direction: column; gap: 6px; }
.perm-check {
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; color: var(--text); cursor: pointer;
}
.perm-check input { cursor: pointer; accent-color: #2563EB; }
.perm-self { font-size: 11px; color: var(--muted); font-weight: 400; }
.sub-label { font-size: 11px; color: var(--muted); margin-bottom: 6px; font-weight: 500; letter-spacing: 0.5px; text-transform: uppercase; }

/* ── Event tooltip ── */
.ev-tooltip {
  position: fixed; z-index: 600;
  background: #1C1C1C; color: #F0F0F0;
  border-radius: 8px; padding: 10px 14px;
  font-family: inherit;
  min-width: 160px; max-width: 240px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.28);
  pointer-events: none;
  opacity: 0; visibility: hidden;
  transition: opacity 0.12s;
}
.ev-tooltip.visible { opacity: 1; visibility: visible; }
.ev-tt-title { font-weight: 600; font-size: 13px; margin-bottom: 7px; color: #fff; }
.ev-tt-meta { font-size: 11.5px; color: rgba(255,255,255,0.6); margin-bottom: 3px; }
/* Подписей у строк больше нет (issue #264): единственными, кто их носил, были
   клиент, контрагент, букер и тип работы, а первые трое стали одной строкой
   через запятую и четвёртый ушёл совсем. Стиль убран вместе с ними — правило
   без единого носителя выглядит как место, куда подписи можно вернуть, не
   спросив, зачем их убирали. */
/* Кружок статуса — элемент, а не символ «●»: у текстового кружка ширина и
   вертикаль берутся из шрифта, а он на трёх системах разный. */
.ev-tt-dot {
  display: inline-block; width: 7px; height: 7px;
  border-radius: 50%; margin-right: 6px; vertical-align: middle;
}

/* ── Inline quick create ── */
.quick-create-wrap {
  padding: 3px 2px;
}
.quick-create-in {
  width: 100%; padding: 5px 8px;
  border: 1.5px solid rgba(55,53,47,0.25); border-radius: 6px;
  font-family: inherit; font-size: 13px;
  color: var(--text); background: #fff;
  outline: none; box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.quick-create-in::placeholder { color: var(--muted); }

/* ── Save toast ── */
/* Потолок ширины — ради длинных сообщений, которых до issue #273 не было:
   тост про скопированную ссылку на календарь договаривает то, что раньше стояло
   подписью под самой ссылкой, и без потолка растягивался чёрной полосой во весь
   низ экрана. Коротким подписям потолок ничего не делает — они и так у́же. */
.save-toast {
  position: fixed; bottom: 24px; right: 24px;
  max-width: min(420px, calc(100vw - 48px));
  background: #1A1A1A; color: #fff;
  padding: 8px 16px; border-radius: 8px;
  font-size: 13px; line-height: 1.45; font-family: inherit;
  opacity: 0; transform: translateY(8px);
  transition: opacity 0.18s, transform 0.18s;
  pointer-events: none; z-index: 1000;
}
.save-toast.visible { opacity: 1; transform: translateY(0); }

/* ── Month picker ── */
.month-picker {
  position: fixed; z-index: 600;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 16px 14px;
  box-shadow: 0 8px 32px rgba(0,0,0,0.14);
  width: 224px;
}
.mp-year-row {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 12px;
}
.mp-year { font-family: var(--display); font-size: 18px; }
.mp-nav-btn {
  background: none; border: none; cursor: pointer;
  color: var(--muted); font-size: 20px; line-height: 1;
  padding: 2px 8px; border-radius: 4px; transition: background 0.1s;
}
.mp-nav-btn:hover { background: var(--bg); color: var(--text); }
.mp-months { display: grid; grid-template-columns: repeat(4, 1fr); gap: 3px; }
.mp-month-btn {
  padding: 7px 4px; border: none; background: none; cursor: pointer;
  font-family: inherit; font-size: 12px; color: var(--text);
  border-radius: 6px; transition: background 0.1s; text-align: center;
}
.mp-month-btn:hover { background: var(--bg); }
.mp-month-btn.active { background: var(--text); color: #fff; font-weight: 600; }

/* ── Dark theme ── */
body.dark {
  --bg: #1E1E1E;
  --surface: #262626;
  --border: rgba(255,255,255,0.08);
  --text: #E8E8E5;
  --muted: #7A7A76;
}
/* Пастель статуса на тёмном фоне светится; в noir от неё остаётся полоса
   слева и подмешанный в подложку тон — тот же смысл, та же тройка цветов. */
body.dark .ev-card,
body.dark .cal-bar {
  background: color-mix(in oklch, var(--ev-accent, #888) 26%, #232323);
  color: var(--ink);
}
body.dark .ev-card:hover,
body.dark .cal-bar:hover { filter: brightness(1.14); }
/* В тёмной теме «серее фона» не бывает — набросок отличается тем, что он
   тусклее и холоднее соседей, а не светлее. Пунктир и точка статуса те же:
   форма рамки читается одинаково в обеих темах, ради чего она и выбрана. */
body.dark, [data-theme="noir"] {
  --unconf-fill:  #202020;
  --unconf-hover: #282828;
  --unconf-line:  #4A4A46;
  --unconf-ink:   #9A9A94;
}
body.dark .ev-card.ev-unconfirmed,
body.dark .cal-bar.ev-unconfirmed { background: var(--unconf-fill); color: var(--unconf-ink); }
body.dark .tl-ev-bar { opacity: 0.88; }
body.dark .ev-title { color: #E0E0DC; }
body.dark .day-cell { background: var(--surface); }
/* Подсветка дня в тёмной теме — тоже голубая, токенами выше. Строка осталась
   на месте, потому что она сильнее по специфичности, чем правило в общем
   блоке, и без неё тёмная тема продолжала бы красить серым. */
body.dark .day-cell:hover { background: var(--day-hover); }
body.dark .day-cell.other-month { background: var(--bg); }
body.dark .day-cell.selected { background: #323232; }
body.dark .day-cell,
body.dark .cal-week,
body.dark .day-headers,
body.dark .day-header { border-color: rgba(255,255,255,0.05); }
body.dark .day-num { color: #888; }
body.dark .login-card,
body.dark .modal { background: var(--surface); }
body.dark .modal-form input,
body.dark .modal-form select,
body.dark .search-input,
body.dark .quick-create-in { background: #333; border-color: rgba(255,255,255,0.1); color: var(--text); }
body.dark .spn-title,
body.dark .spn-desc,
body.dark .spn-desc-ro,
body.dark .spn-text-in { color: var(--text); }
body.dark .spn-desc:hover,
body.dark .spn-desc:focus,
body.dark .spn-text-in:hover,
body.dark .spn-text-in:focus { background: rgba(255,255,255,0.04); }
body.dark .week-event { filter: brightness(0.8) saturate(0.9); }
body.dark .mp-month-btn.active { background: #E8E8E5; color: #1E1E1E; }
#pw-modal-inner { width: 320px; max-width: 100%; }
body.dark .spn-status-menu {
  background: var(--surface); border-color: var(--border);
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}

/* spinner */
.spinner {
  display: inline-block; width: 14px; height: 14px;
  border: 2px solid rgba(255,255,255,0.4); border-top-color: #fff;
  border-radius: 50%; animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ══ HAMBURGER & BACKDROP ══ */
/* Сворачивают панель кнопкой в ней самой (`.side-collapse`), поэтому в шапке
   гамбургер остаётся только на то время, пока панель свёрнута: развернуть её
   обратно больше нечем. На телефоне панель — накладной слой, там он нужен
   всегда, и это прописано ниже в мобильном блоке. */
.hamburger-btn {
  display: none;
  width: 36px; height: 36px; flex-shrink: 0;
  align-items: center; justify-content: center;
  background: none; border: none;
  cursor: pointer; font-size: 18px; color: var(--muted);
  border-radius: 6px; transition: color 0.15s, background 0.15s;
}
.hamburger-btn:hover { background: var(--bg); color: var(--text); }
.app.sidebar-hidden .hamburger-btn { display: flex; }
/* Подложка — устройство накладной панели, и показывается только там, где
   панель накладная. Класс с неё снимает щелчок, а не изменение ширины окна:
   открыв панель на телефоне и растянув окно до десктопа, человек получал
   продукт, целиком закрытый затемнением, — и снимался он «пустым» щелчком
   неизвестно куда. Правило переехало в мобильный блок, а не обросло
   обработчиком resize: на десктопе подложке нечего закрывать. */
.sidebar-backdrop {
  display: none; position: fixed; inset: 0;
  background: rgba(0,0,0,0.35); z-index: 149;
}

/* Desktop: hide mobile-only elements */
.vt-short { display: none; }
.day-dots { display: none !important; }

/* ── Day Sheet (тап по дню на мобильном) ── */
.day-sheet { display: none; position: fixed; inset: 0; z-index: 300; }
.day-sheet.visible { display: block; }
.ds-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.4); }
.ds-panel {
  position: absolute; bottom: 0; left: 0; right: 0;
  background: var(--surface); border-radius: 18px 18px 0 0;
  padding-bottom: env(safe-area-inset-bottom, 0px);
  max-height: 78vh; display: flex; flex-direction: column;
  box-shadow: 0 -4px 32px rgba(0,0,0,0.15);
}
.ds-handle {
  width: 40px; height: 4px; background: var(--border);
  border-radius: 2px; margin: 12px auto 4px; flex-shrink: 0;
}
.ds-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 20px 12px; border-bottom: 1px solid var(--border); flex-shrink: 0;
}
.ds-date { font-family: var(--display); font-size: 19px; }
.ds-add-btn {
  padding: 0 18px; height: 40px; background: var(--text); color: #fff;
  border: none; border-radius: 8px; font-family: inherit;
  font-size: 14px; font-weight: 500; cursor: pointer;
}
.ds-list { overflow-y: auto; -webkit-overflow-scrolling: touch; flex: 1; padding: 4px 0 8px; }
.ds-item {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 20px; min-height: 60px; cursor: pointer;
}
.ds-item:active { background: var(--bg); }
.ds-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.ds-item-body { flex: 1; min-width: 0; }
.ds-item-title {
  font-size: 15px; font-weight: 500; color: var(--text); margin-bottom: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ds-item-meta { font-size: 12px; color: var(--muted); }
.ds-arrow { color: var(--muted); font-size: 20px; flex-shrink: 0; }
.ds-empty { padding: 28px 20px; text-align: center; font-size: 15px; color: var(--muted); }
body.dark .ds-panel { background: var(--surface); }

/* ══ Заголовок месяца — второй строкой (≤ 1100px) ══
   «Сентябрь 2026» с «‹ Сегодня ›» занимает в полосе три сотни пикселей, и
   вместе с часами, «Пользователями» и «Выйти» перестаёт помещаться примерно с
   1035px — а `.main` режет лишнее по overflow, то есть «Выйти» не переносится,
   а пропадает совсем. Ниже этой ширины заголовок занимает свою строку, как и
   до переезда наверх: полоса становится выше, зато из неё ничего не исчезает.
   Отступ слева добиваем до `--pad-x`, чтобы месяц стоял над первым столбцом
   календаря, а не левее его. */
/* ══ Тесно: карточка события встаёт под полосой, а не рядом с ней ══ */
/* Панель тянется от шапки окна до низа (issue #167), то есть стоит справа и от
   полосы «Вид · лупа · … · + Событие» тоже. Полоса ужимается до предела и
   дальше не ужимается, и предел у неё разный: свёрнутой ей нужно около 600
   пикселей от левого края экрана, с раскрытым поиском — 825. Панель шириной 520
   садится на них сверху, и «+ Событие», главная кнопка продукта, перестаёт
   нажиматься вовсе: щелчок попадает в название съёмки.

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

   Сколько это «под полосой», меряет браузер (`--viewrow-h`, `04-ui.js`): у
   полосы бывает вторая строка, а вне календаря её нет вовсе. Запасное число —
   на тот час, пока замер не приехал. */
@media (max-width: 1160px) {
  .app { --panel-top-off: var(--viewrow-h, 53px); }
}
/* Раскрытое поле поиска — состояние, а не ширина: оно занимает полосу целиком и
   отпускает её обратно, поэтому спрашивается классом. Ниже 1000px поле и так
   накрывает полосу собой (см. блок 1000px), а панель там под полосой уже по
   правилу выше. */
@media (max-width: 1360px) {
  .app:has(.view-row.search-open) { --panel-top-off: var(--viewrow-h, 53px); }
}

@media (max-width: 1100px) {
  /* Полоса становится выше — и полоса логотипа растёт вместе с ней, иначе две
     линии встают ступенькой. Отсюда же фиксированная высота вместо `auto`:
     сайдбару нечем узнать, во что развернулась шапка. */
  :root { --topbar-h: 76px; }
  .topbar { flex-wrap: wrap; }
  /* Левый отступ даёт шапка (`--pad-x`), и своего у заголовка здесь нет:
     шесть пикселей поверх общего отступа ставили месяц правее края сетки
     ровно на эти шесть. */
  .topbar .screen-head,
  .topbar .page-head {
    order: 2; flex-basis: 100%;
    padding: 0 0 8px; gap: 8px; flex-wrap: wrap;
  }
}

/* ══ MEDIUM (≤ 1000px) — free up topbar room before mobile layout ══ */
/* The sidebar is still shown down to 768px, so it eats width and the topbar
   would otherwise overflow. Drop the non-essential clock + search here. */
@media (max-width: 1000px) {
  /* Compact the clock to a tappable icon (date is already in the page head);
     tapping it opens the world-clocks menu. */
  .now-chip .cap, .now-chip .pulse { display: none; }
  .now-chip .now-ico { display: block; }
  /* Полоса узкая, и всплывающий список часов, выровненный по правому краю
     чипа, упирался бы в край экрана. Прибиваем к окну, а не к чипу. */
  .world-clocks-pop {
    position: fixed; top: 62px; right: 10px; left: auto; min-width: 172px;
  }
  /* Подсказка про ⌘K обещает клавиатуру, которой на этой ширине обычно нет.
     Место под неё в поле освобождается вместе с ней — иначе текст запроса
     обрывался бы за полсотни пикселей до края. */
  .kbd-hint { display: none; }
  .search-input { padding-right: 34px; }
  /* Expanded search: overlay the whole view row with a full-width field.
     На широком экране развёрнутое поле просто встаёт в ряд рядом с лупой —
     здесь ряду не хватает ширины на поле и вкладки разом. */
  .view-row.search-open .search-wrap {
    position: absolute; inset: 0;
    padding: 6px 10px; gap: 4px;
    background: var(--bg, #FFFFFF); z-index: 40;
  }
  .view-row.search-open .search-input {
    display: block; width: 100%; flex: 1 1 auto;
  }
  /* The rest of the row (view toggles, +Событие) would otherwise
     show through the overlay's own gaps and padding. */
  .view-row.search-open > *:not(.page-head-actions),
  .view-row.search-open .page-head-actions > *:not(.search-wrap) {
    visibility: hidden;
  }
}

/* ══ MOBILE (≤ 768px) ══ */
@media (max-width: 768px) {

  /* Fast tap, no double-tap zoom */
  button, a, [onclick] { touch-action: manipulation; }
  /* Fill the screen edge-to-edge — the 16px desktop frame wastes space here. */
  body { overflow-x: hidden; padding: 0; }
  .app { border: 0; border-radius: 0; }

  /* Клетка на телефоне у́же вчетверо, и восемь пикселей с каждой стороны
     съедали бы её содержимое. Число здесь своё, а читателей у него столько же:
     подпись колонки, клетка, колонка недели и полосы над ними. */
  :root { --pad-x: 18px; --day-pad-x: 3px; }

  /* App grid: collapse sidebar column — sidebar is position:fixed overlay */
  .app { grid-template-columns: 1fr !important; }
  .main { min-width: 0; }

  /* ── Sidebar → fixed 80% overlay ── */
  .sidebar {
    position: fixed; top: 0; bottom: 0;
    left: calc(-80vw - 4px); width: 80vw; max-width: 300px;
    z-index: 150; transition: left 0.25s ease; box-shadow: none;
  }
  .sidebar.mobile-open { left: 0; box-shadow: 4px 0 28px rgba(0,0,0,0.22); }
  .sidebar-backdrop.visible { display: block; }
  /* `sidebar-hidden` — состояние десктопа: там панель держит колонку, и её
     сворачивают, чтобы вернуть место. Здесь колонки нет вовсе, панель
     накладная, и нулевая ширина отсюда означала бы кабинет без навигации:
     свернул на ноутбуке, открыл с телефона — гамбургер есть, а открывает он
     панель шириной в ноль. `toggleSidebar()` этот класс на телефоне не
     трогает намеренно: вернувшись за ноутбук, человек ждёт панель такой,
     какой её там оставил. */
  .app.sidebar-hidden .sidebar {
    width: 80vw; padding: 0 0 0 8px;
    border-right-color: var(--rule, rgba(55,53,47,0.09));
    visibility: visible;
  }
  .hamburger-btn { display: flex !important; }

  /* ── Topbar ── icon-only actions ── */
  /* Перенос заголовка второй строкой сделан выше, с 1100px; здесь остаётся
     только сжать саму полосу и добить отступ месяца до `--pad-x`. */
  .topbar { padding: 6px 10px; gap: 4px; height: auto; min-height: 48px; }
  .topbar-spacer { flex: 1; }
  .topbar .page-head { padding: 2px 0 8px 8px; }
  /* Панель здесь накладная и уезжает за край экрана: её линию продолжать
     нечему, поэтому высота полосы логотипа от высоты шапки больше не зависит. */
  .brand-row { height: 52px; }
  .search-input { display: none; }
  .user-badge { display: none; }
  /* Compact "Пользователи" / "Выйти" */
  .tb-btn { padding: 4px 8px; font-size: 12px; flex-shrink: 0; }
  .tb-btn.icon { width: 36px; height: 36px; }
  #users-btn { padding: 4px 6px; font-size: 11px; }

  /* ── Page head (month title) ── */
  .page-head-title { gap: 6px; flex-wrap: wrap; }
  .page-head-title h1,
  .screen-head { font-size: 21px; letter-spacing: -0.01em; }
  .page-head-title .yr { font-size: 21px; }
  .today-btn { height: 32px; padding: 0 10px; font-size: 12px; }
  /* Search and «+ Событие» don't fit next to the view toggles on a
     phone-width row — drop them to a row of their own rather than letting
     them overflow past the edge of the screen, unreachable. */
  .view-row { flex-wrap: wrap; gap: 8px; }
  .page-head-actions { flex-basis: 100%; justify-content: flex-end; }

  .vt-btn { flex-shrink: 0; padding: 8px 10px; font-size: 12px; white-space: nowrap; }
  /* Наведения здесь нет вовсе, а открепить вид надо уметь и с телефона —
     та же причина, по которой `.list-item-del` тут показан всегда. Раз крестик
     виден всегда, то и подпись стоит отъехавшей всегда: центрированная, она
     легла бы хвостом прямо под него. */
  .vt-pin-off { opacity: 1; }
  .vt-pin-go { transform: translateX(-10px); }
  /* Меню на телефоне держится в пределах экрана: обе кнопки стоят у краёв
     полосы, и выпасть за них ему некуда. */
  .cal-pop { max-width: calc(100vw - 24px); }

  /* ── Calendar — make sure it fills width ── */
  .cal-container { padding: 0 8px 8px; }
  .cal-grid { width: 100%; }
  .day-headers { padding: 0; }

  /* ── Month calendar — dots mode ── */
  .day-cell { min-height: 52px; padding: 4px var(--day-pad-x) 5px; gap: 0; }
  .day-num { height: 22px; font-size: 12px; }
  .day-add-btn { display: none; }
  .ev-card { display: none; }
  /* Клетка на телефоне — пятьдесят два пикселя и режим точек: подпись праздника
     туда не помещается по той же причине, по которой туда не помещается
     карточка съёмки. В неделе и в году она остаётся.

     Прятать что-то ещё вслед за ней больше не нужно: подпись стоит на строке с
     числом, своей строки под ней нет, и полосы съёмок начинаются под числом
     независимо от того, есть в неделе праздник или нет. Пустая обёртка
     `.day-hol` при этом остаётся — она и держит число у правого края. */
  .holiday-chip { display: none; }
  .day-dots {
    display: flex !important; flex-wrap: wrap;
    gap: 3px; padding: 2px 1px 0;
  }
  .day-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }

  /* ── Week view — swipe navigation ── */
  /* Сетки часов на телефоне нет, и это не урезанный вид, а тот же, что был:
     семь колонок с точками, из которых открывается лист дня. Двадцать четыре
     часа в колонку шириной в палец не помещаются вовсе, а полоса с часами
     отняла бы у семи дней восьмую часть экрана ради подписей, под которыми
     ничего не нарисовано. */
  .week-container { overflow: hidden; touch-action: pan-y; }
  .wk-inner { min-width: unset; }
  .wk-allday-row, .wk-gutter, .wk-gutter-cell { display: none; }
  .wk-day-hd { padding: 6px var(--day-pad-x) 5px; }
  .wk-hd-name { font-size: 9px; }
  .wk-hd-date { height: auto; margin-bottom: 2px; }
  .wk-date-num { font-size: 18px; }
  .wk-today-num { width: 28px; height: 28px; font-size: 13px; }
  .wk-col {
    padding: 5px var(--day-pad-x); min-height: 120px; height: auto;
    cursor: pointer; background-image: none;
  }
  .wk-col-lanes { display: none; }
  .wk-col .day-dots { display: flex !important; padding: 0; }

  /* ── Year view ── */
  .year-container { padding: 12px; }
  .year-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }

  /* ── Kanban ── */
  .kanban-container { overflow-x: auto; }
  .kanban-board { padding: 12px; gap: 10px; }
  .kb-col { width: 240px; }

  /* ── List view ── */
  .list-container { padding: 14px var(--pad-x) 80px; }
  .list-group { margin-bottom: 28px; }
  .list-group-header { padding: 10px 4px; }
  .list-item { flex-wrap: wrap; gap: 8px; padding: 12px 4px; margin: 0; }
  .list-item-date { min-width: 0; font-size: 12px; }
  .list-item-title { font-size: 15px; flex-basis: 100%; order: -1; }
  /* Чипы переезжают на свою строку целиком: на телефоне рядом с датой их
     помещается один, а обрезанные до многоточия «Кли…» и «Пар…» не отвечают
     ни на один вопрос, ради которого их сюда поставили. */
  .list-item-tags { flex-basis: 100%; flex-wrap: wrap; }
  .list-item-del { opacity: 1; width: 40px; height: 40px; }

  /* ── Side panel → full screen с кнопкой Назад ── */
  /* Панель здесь накладная (`position: fixed`) — ширины у сетки она не
     отбирает, значит и полосе над сеткой двигаться некуда. Ноль стоит у самого
     числа, а не у правила шапки: «сколько забирает панель» — один вопрос, и
     ответ на него на телефоне просто другой. Второй селектор — потому что
     `body.mode-db .app` весит больше `.app` и внутри media-запроса тоже. */
  .app, body.mode-db .app { --panel-w: 0px; }
  .side-panel.open {
    position: fixed; inset: 0; z-index: 160;
    width: 100% !important; border-left: none;
  }
  .side-panel-inner { width: 100% !important; }
  .spn-close-btn, .spn-icon-act { width: 44px; height: 44px; }
  .spn-title { font-size: 22px; padding: 0 16px 12px; }
  .spn-head { padding: 8px 8px 10px 16px; }
  .spn-head .spn-title { padding: 0; }
  .spn-props { padding: 0 12px; }
  .spn-row { min-height: 48px; padding: 6px 4px; }
  .spn-label { width: 110px; }
  /* На телефоне значки возвращаются себе строкой над именем. Палец требует
     кнопок в 44px, и вместе с фотографией в 64px они забирают у имени столько,
     что на экране в 390px от него остаётся треть — а имя тут набирается
     подгонкой кегля (`_fitName`) и просто уезжает в многоточие. Полоса значков,
     убранная на большом экране (issue #296), там не показывала ничего — а здесь
     она и есть то место, благодаря которому имя помещается целиком.

     Липкость с шапки здесь снята: в две строки она занимает пятую часть
     телефонного экрана, и держать её всё время прокрутки дороже, чем вернуться
     к кресту наверх. Карточка съёмки в этой же панели свою шапку на телефоне
     тоже не держит. */
  .mdb-head { position: static; flex-wrap: wrap; row-gap: 4px; padding-top: 4px; }
  .mdb-head .spn-head-acts { order: -1; flex-basis: 100%; justify-content: flex-end; margin-left: 0; }

  /* ── Модалки → полный экран снизу ── */
  .modal-overlay { align-items: flex-end; }
  .modal {
    width: 100vw !important; max-width: 100vw !important;
    border-radius: 18px 18px 0 0 !important;
    max-height: 92dvh; overflow-y: auto;
    padding: 20px 16px 20px;
  }
  /* iOS Safari: padding-bottom игнорируется в overflow:auto — используем ::after */
  .modal::after { content: ''; display: block; height: env(safe-area-inset-bottom, 0px); }
  .users-modal { width: 100vw !important; max-width: 100vw !important; }

  /* font-size ≥ 16px — iOS Safari не зумит поле при фокусе */
  input, select, textarea { font-size: 16px !important; }
  .modal-form input, .modal-form select { padding: 13px 14px; min-height: 48px; }
  .login-field input { min-height: 48px; padding: 12px 14px; }
  .btn-primary, .btn-enter { min-height: 50px; font-size: 15px; }
  .btn-cancel { min-height: 50px; padding: 0 20px; }

  /* Цветовые свотчи — крупнее */
  .color-swatch { width: 32px; height: 32px; }
  .color-grid { gap: 10px; }
  /* Пары полей на телефоне становятся столбиком, иначе в каждом помещается
     половина слова. */
  .nu-grid { grid-template-columns: 1fr; }
  .users-modal { width: 100%; }

  /* Пикер месяца */
  .month-picker { width: calc(100vw - 24px); left: 12px !important; }

  /* iOS Safari: экран входа скроллится, когда клавиатура закрывает форму. */
  .login-screen { overflow-y: auto; -webkit-overflow-scrolling: touch; }

  /* Тост */
  .save-toast { bottom: calc(16px + env(safe-area-inset-bottom, 0px)); right: 16px; }
}

/* ══ Убираем hover на тач-устройствах ══ */
@media (hover: none) {
  .ev-card:hover { box-shadow: 0 1px 2px rgba(0,0,0,0.06); border-color: #E8E8E8; }
  .day-cell:hover { background: var(--surface); }
  .day-cell.other-month:hover { background: #F9F9F7; }
  body.dark .day-cell:hover { background: var(--surface); }
  body.dark .day-cell.other-month:hover { background: var(--bg); }
  .model-item:hover { background: transparent; }
  .model-item.active { background: var(--bg); }
  .kb-card:hover { box-shadow: none; border-color: var(--border); }
  .year-month:hover { box-shadow: none; border-color: var(--border); background: var(--bg); }
  .topbar-btn:hover { background: none; color: var(--muted); }
  .nav-btn:hover { background: none; color: var(--muted); }
  .today-btn:hover { background: none; }
  .tl-ev-bar:hover { opacity: 1; transform: none; }
  .vt-btn:hover { background: none; color: var(--muted); }
  .vt-btn.active { background: var(--surface); color: var(--text); }
}

/* ══ DARK + MOBILE ══ */
body.dark .sidebar.mobile-open { box-shadow: 4px 0 28px rgba(0,0,0,0.5); }

/* ══ CHAT (handoff v5 — floating dock) ══ */
/* Local pad var for chat surfaces (dock uses 18px, full-page uses --pad-x) */
.chat-panel { --chat-pad-x: 18px; }

.chat-fab {
  position: fixed; bottom: 24px; right: 24px; z-index: 70;
  width: 52px; height: 52px; border-radius: 50%;
  background: var(--ink); color: var(--bg);
  border: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  box-shadow: var(--shadow-menu);
  transition: filter 0.15s, transform 0.18s cubic-bezier(.22,.61,.36,1), opacity 0.15s;
}
.chat-fab:hover { filter: brightness(1.12); transform: scale(1.07); }
.chat-fab svg { width: 22px; height: 22px; }
body.chat-open .chat-fab { transform: scale(.92); opacity: .9; }
body.chat-open .chat-fab .chat-unread { display: none; }
.chat-unread {
  display: none; position: absolute; top: -3px; right: -3px;
  min-width: 19px; height: 19px; border-radius: 10px;
  background: var(--accent); color: #fff;
  font-size: 11px; font-weight: 700;
  align-items: center; justify-content: center; padding: 0 5px;
  border: 2px solid var(--bg);
}

/* Dock panel */
.chat-panel {
  position: fixed; bottom: 88px; right: 24px; z-index: 69;
  width: min(540px, calc(100vw - 40px)); height: min(480px, calc(100vh - 130px));
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--radius); box-shadow: var(--shadow-panel);
  display: flex; overflow: hidden;
  opacity: 0; pointer-events: none;
  transform: translateY(16px) scale(.97); transform-origin: bottom right;
  transition: opacity .22s cubic-bezier(.22,.61,.36,1), transform .22s cubic-bezier(.22,.61,.36,1);
}
.chat-panel.visible {
  opacity: 1; pointer-events: all; transform: none;
  animation: dockRise .22s cubic-bezier(.22,.61,.36,1);
}
@keyframes dockRise { from { opacity: 0; transform: translateY(16px) scale(.97); } to { opacity: 1; transform: none; } }

/* Threads column */
.chat-sidebar {
  width: 194px; flex-shrink: 0; border-right: 1px solid var(--rule);
  background: var(--surface-2);
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--bg);
}
.chat-conv-list {
  flex: 1; overflow-y: auto;
  padding: 8px; display: flex; flex-direction: column; gap: 1px;
}
.chat-conv-item {
  display: flex; align-items: center; gap: 11px;
  padding: 9px; border-radius: var(--radius-sm); cursor: pointer;
  transition: background 0.1s; border: none; background: transparent;
  width: 100%; text-align: left;
}
.chat-conv-item:hover { background: var(--surface-2); }
.chat-conv-item.active { background: var(--hover); }
.chat-conv-avatar {
  width: 36px; height: 36px; border-radius: 50%; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 600; color: #fff; position: relative;
}
.chat-conv-avatar.group {
  background: #8BA89B;
  font-size: 14px;
}
/* Group member-count badge (.grp) — bottom-right pip on avatar */
.chat-conv-avatar .grp {
  position: absolute; right: -2px; bottom: -2px;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--bg); color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  font-size: 9px; border: 1.5px solid var(--surface-2);
}
.chat-conv-info { flex: 1; min-width: 0; }
.chat-conv-name-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.chat-conv-name {
  font-size: 13.5px; font-weight: 600; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.chat-conv-time { font-size: 11px; color: var(--muted); flex-shrink: 0; }
.chat-conv-last {
  font-size: 12px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px;
}
.chat-conv-badge {
  min-width: 18px; height: 18px; border-radius: 9px;
  background: var(--accent); color: #fff;
  font-size: 11px; font-weight: 600; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center; padding: 0 5px;
}
/* ── Поддержка в списке чатов ──
   Переписка с нами лежит в том же списке, что и чаты команды, потому что это
   такая же переписка: разделены они заголовком, а не экраном. Разница ровно
   одна и она в аватаре — у обращения на том конце не коллега. */
.chat-conv-sep {
  font-size: 10.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); padding: 10px 9px 4px;
}
.chat-conv-list > .chat-conv-sep:first-child { padding-top: 4px; }
/* Заголовок «Поддержка» складывает раздел, заголовок «Команда» — нет, и разная
   разметка у них именно поэтому: кнопка там, где есть что нажать. Отступы
   переезжают на саму кнопку, иначе подсветка при наведении обрывается по
   границе текста и висит посреди колонки. */
.chat-conv-sep.fold { padding: 0; }
.chat-conv-list > .chat-conv-sep.fold:first-child .chat-sep-btn { padding-top: 4px; }
.chat-sep-btn {
  width: 100%; display: flex; align-items: center; gap: 4px;
  border: none; background: transparent; cursor: pointer;
  font: inherit; font-size: 10.5px; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; text-align: left;
  color: var(--muted); padding: 10px 9px 4px;
  border-radius: var(--radius-sm); transition: color .1s;
}
.chat-sep-btn:hover { color: var(--ink); }
/* Стрелка не разрежена, в отличие от слова рядом: у одного знака межбуквенный
   интервал — это сдвиг вправо, а не разрядка. */
.chat-sep-chev { letter-spacing: 0; font-size: 11px; width: 9px; flex-shrink: 0; }
.chat-sep-label { flex-shrink: 0; }
/* Цифра прижата к правому краю: свёрнутый раздел — единственное место, где
   видно, что нам ответили, и она читается там же, где счётчики строк. */
.chat-sep-btn .chat-conv-badge { margin-left: auto; letter-spacing: 0; }
.chat-conv-avatar.sup { background: var(--accent); font-size: 11px; letter-spacing: .02em; }
/* «Новое обращение» — не чат, а дверь: пунктир вместо заливки, чтобы строка
   не читалась как переписка, которой ещё нет. */
.chat-conv-avatar.sup-new {
  background: transparent; color: var(--muted);
  border: 1px dashed var(--rule-strong); font-size: 15px; font-weight: 400;
}

/* Статус обращения — чья очередь отвечать. Цветом, а не значком: строка списка
   узкая, и слово «ждёт ответа» в ней и так занимает половину. */
.chat-sup-state { font-weight: 600; }
.chat-sup-state.wait { color: #B45309; }
.chat-sup-state.done { color: #2E7D32; }
.chat-sup-state.mute { color: var(--muted); }
body.dark .chat-sup-state.wait { color: #E0A106; }
body.dark .chat-sup-state.done { color: #6FBF73; }

.chat-avatar-sup { background: var(--accent); font-size: 9px; }
/* Текст обращения переносится как есть: перенос строки, которым человек
   разделил «что делал» и «что увидел», — часть сообщения, а не разметка. */
.chat-msg-sup .chat-text { white-space: pre-wrap; }

.chat-sup-top, .chat-sup-note, .chat-sup-intro {
  font-size: 12px; color: var(--muted); text-align: center;
}
.chat-sup-top { display: flex; flex-direction: column; gap: 2px; }
.chat-sup-intro {
  display: flex; flex-direction: column; gap: 6px; margin: auto;
  max-width: 340px; text-align: center; line-height: 1.5;
}
.chat-sup-intro b { font-size: 14px; color: var(--ink); }
.chat-sup-btn {
  background: none; border: 1px solid var(--rule-strong); border-radius: 999px;
  padding: 4px 12px; font-size: 11.5px; font-family: inherit;
  color: var(--muted); cursor: pointer;
}
.chat-sup-btn:hover { color: var(--ink); border-color: var(--ink); }

/* Поле темы и строка отказа живут над строкой ввода и появляются только когда
   есть что сказать: пустая полоса над полем — это отступ, который выглядит
   как ошибка вёрстки. */
.chat-sup-subject-row { display: none; padding: 10px var(--chat-pad-x) 0; }
.chat-sup-subject-row.open { display: block; }
.chat-sup-subject-row .chat-input { width: 100%; }
.chat-sup-say {
  padding: 8px var(--chat-pad-x) 0; font-size: 12px; color: var(--danger);
}
.chat-sup-say:empty { display: none; }

/* ── Поиск над списком ──
   Одно поле на два вопроса — названия чатов и текст сообщений, — поэтому и
   стоит оно над списком целиком, а не внутри одного из разделов. */
.chat-search-row { position: relative; padding: 6px 8px 0; flex-shrink: 0; }
.chat-search {
  width: 100%; padding: 6px 24px 6px 9px;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink);
  font-family: inherit; font-size: 12.5px;
}
.chat-search:focus { outline: none; border-color: var(--rule-strong); }
.chat-search-clear {
  display: none; position: absolute; right: 13px; top: 50%; transform: translateY(-25%);
  border: none; background: transparent; cursor: pointer;
  color: var(--muted); font-size: 11px; padding: 2px 4px; font-family: inherit;
}
.chat-search-clear.open { display: block; }
.chat-search-clear:hover { color: var(--ink); }
/* У найденного сообщения аватара нет: чат один и тот же на несколько строк, и
   пять одинаковых кружков подряд — это не подсказка, а вертикальная линия. */
.chat-hit .chat-conv-info { padding-left: 2px; }

/* Закреплённый чат помечен в строке, а не только положением: разделы
   складываются и прокручиваются, и «почему он наверху» должно читаться из
   самой строки. */
.chat-conv-pin { font-size: 10px; opacity: .75; flex-shrink: 0; }
/* Упоминание — не просто цифра побольше. Тот же кружок, но с собакой и другим
   цветом: «вам написали» и «вас позвали» человек различает раньше, чем читает. */
.chat-conv-badge.mention { background: var(--ink); }
body.dark .chat-conv-badge.mention { background: var(--accent); }

.chat-new-row {
  display: flex; gap: 6px; padding: 8px;
  border-top: 1px solid var(--rule); flex-shrink: 0;
}
.chat-new-btn {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 5px;
  padding: 7px 6px; border-radius: var(--radius-sm); border: 1px solid var(--rule-strong);
  background: var(--surface); cursor: pointer; font-size: 12px; color: var(--ink-soft);
  transition: background 0.1s, color .1s;
}
.chat-new-btn:hover { background: var(--surface-2); color: var(--ink); }

/* Conversation pane */
.chat-view { flex: 1; display: flex; flex-direction: column; min-width: 0; position: relative; background: var(--bg); }
.chat-header {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; border-bottom: 1px solid var(--rule); flex-shrink: 0;
}
.chat-back-btn {
  display: none; background: none; border: none; cursor: pointer;
  font-size: 22px; color: var(--muted); padding: 0 4px; line-height: 1;
}
.chat-title-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.chat-title {
  font-size: 15px; font-weight: 600; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chat-view-sub {
  font-size: 12px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: none;
}
.chat-menu-btn {
  background: none; border: none; cursor: pointer; flex-shrink: 0;
  color: var(--muted); font-size: 18px; padding: 4px 8px; line-height: 1;
  border-radius: var(--radius-sm); transition: background 0.1s, color .1s;
  letter-spacing: 1px;
}
.chat-menu-btn:hover { background: var(--hover); color: var(--ink); }
.chat-close-btn {
  background: none; border: none; cursor: pointer; flex-shrink: 0;
  color: var(--muted); font-size: 16px; padding: 4px 8px;
  border-radius: var(--radius-sm); transition: background 0.1s, color .1s;
}
.chat-close-btn:hover { background: var(--hover); color: var(--ink); }
/* «Развернуть» выглядит как соседи по шапке и стоит рядом с ними: это не
   действие над чатом, а размер окна, в котором его читают. */
.chat-expand-btn {
  background: none; border: none; cursor: pointer; flex-shrink: 0;
  color: var(--muted); font-size: 14px; padding: 4px 8px; font-family: inherit;
  border-radius: var(--radius-sm); transition: background 0.1s, color .1s;
}
.chat-expand-btn:hover { background: var(--hover); color: var(--ink); }
/* Развёрнутая панель — почти весь экран, но всё ещё док: она остаётся окном
   поверх продукта, а не становится восьмым разделом. Колонка списка при этом
   растёт вдвое — ради неё всё и затевалось: в 194 пикселях имя обрезано на
   середине, а превью помещается в четыре слова. */
.chat-panel.expanded {
  width: min(1040px, calc(100vw - 40px));
  height: min(760px, calc(100vh - 130px));
}
.chat-panel.expanded .chat-sidebar { width: 300px; }
/* Разворачивать нечего там, где панель и так во весь экран. */
@media (max-width: 768px) { .chat-expand-btn { display: none; } }
.chat-dropdown {
  position: absolute; top: 50px; right: 16px; z-index: 210;
  background: var(--bg); border: 1px solid var(--rule);
  border-radius: var(--radius); box-shadow: var(--shadow-menu);
  min-width: 200px; padding: 4px; display: none;
}
.chat-dropdown.open { display: block; }
.chat-dd-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px; border-radius: var(--radius-sm); cursor: pointer;
  font-size: 13px; color: var(--ink); border: none; background: none;
  width: 100%; text-align: left; transition: background 0.1s;
}
.chat-dd-item:hover { background: var(--hover); }
.chat-dd-item.danger { color: var(--danger); }
.chat-dd-item.danger:hover { background: var(--hover); }
.chat-dd-sep { height: 1px; background: var(--rule); margin: 4px 0; }

/* Messages area */
.chat-messages {
  flex: 1; overflow-y: auto; padding: 20px var(--chat-pad-x);
  display: flex; flex-direction: column; gap: 12px;
}
.chat-msg {
  display: flex; gap: 9px; align-items: flex-end;
  max-width: 74%; margin-top: 0;
  animation: msgIn .26s cubic-bezier(.22,.61,.36,1) backwards;
}
@keyframes msgIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.chat-msg.same-author { margin-top: -6px; }
.chat-msg.mine { align-self: flex-end; flex-direction: row-reverse; }
.chat-avatar {
  width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 600; color: #fff; align-self: flex-end;
}
.chat-msg.same-author .chat-avatar { visibility: hidden; }
.chat-bubble { display: flex; flex-direction: column; gap: 2px; min-width: 0; max-width: 100%; }
.chat-msg.mine .chat-bubble { align-items: flex-end; }
.chat-name {
  font-size: 11px; color: var(--ink-soft); padding: 0 4px 0 4px;
  font-weight: 600;
}
.chat-text {
  background: var(--surface-2); color: var(--ink);
  border-radius: 14px;
  padding: 8px 13px; font-size: 13.5px; line-height: 1.45;
  word-break: break-word;
}
.chat-msg.mine .chat-text {
  background: var(--ink); color: var(--bg);
  border-radius: 14px;
}
.chat-msg.same-author .chat-text,
.chat-msg.mine.same-author .chat-text { border-radius: 14px; }
.chat-time { font-size: 10px; color: var(--muted); padding: 0 4px; margin-top: 3px; text-align: right; }
.chat-msg.mine .chat-time { color: color-mix(in oklch, var(--bg) 60%, transparent); }
.chat-empty { font-size: 13px; color: var(--muted); text-align: center; margin: auto; padding: 20px; }

/* Input bar */
.chat-input-row {
  /* flex-end, а не center: поле растёт под текст, и кнопки, стоящие по центру
     пятистрочного поля, уезжают на его середину. Отправка живёт у последней
     строки, потому что там же курсор. */
  display: flex; align-items: flex-end; gap: 7px;
  padding: 12px var(--chat-pad-x);
  border-top: 1px solid var(--rule); flex-shrink: 0;
}
.chat-input {
  flex: 1; padding: 9px 16px; border-radius: 18px;
  border: 1px solid var(--rule-strong); background: var(--surface);
  font-size: 14px; color: var(--ink); outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
  font-family: inherit; line-height: 1.4;
}
/* ── Поле сообщения ──
   contenteditable, поэтому высоту держит здесь CSS, а не JS: раньше её считала
   `_grow()` из `scrollHeight`, и функции этой не стало вовсе. Пять строк —
   тот же потолок, что был: дальше поле отъедает переписку, ради которой панель
   и открыта. `pre-wrap` — чтобы перенос строки в набранном был виден так же,
   как он виден в пузыре. */
#chat-input {
  white-space: pre-wrap; overflow-wrap: anywhere;
  min-height: 38px; max-height: 118px; overflow-y: auto;
}
/* Подсказка в пустом поле: у div нет placeholder, а `:empty` у него честный —
   как только появится хоть один узел, надпись уходит. */
#chat-input:empty::before {
  content: attr(data-placeholder); color: var(--muted); pointer-events: none;
}
/* ── Ссылка внутри поля ──
   Одна буква, а не текст: `contenteditable=false` не даёт поставить внутрь
   курсор, а Backspace над ней уносит её целиком (Chat._eatToken). Выглядит она
   тем же, чем станет в пузыре, — в этом вся её работа. */
.chat-tok {
  display: inline-flex; align-items: center; gap: 2px;
  margin: 0 1px; padding: 0 2px 0 7px; border-radius: 10px;
  background: var(--surface-2); border: 1px solid var(--rule-strong);
  font-size: 12px; line-height: 18px; vertical-align: -3px;
  white-space: nowrap; max-width: 100%; user-select: none;
}
.chat-tok.gone { border-style: dashed; color: var(--muted); }
.chat-tok-body { overflow: hidden; text-overflow: ellipsis; }
.chat-tok-x {
  flex-shrink: 0; cursor: pointer; color: var(--muted);
  font-size: 10px; line-height: 1; padding: 3px 4px; border-radius: 50%;
}
.chat-tok-x:hover { color: var(--danger); background: var(--hover); }
/* Скрепка и решётка — не главное действие, поэтому без заливки: главное здесь
   одно, и оно справа. */
.chat-attach-btn, .chat-ref-btn {
  flex-shrink: 0; width: 30px; height: 30px; border-radius: 50%;
  border: none; background: transparent; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted); font-family: inherit; font-size: 15px; padding: 0;
  transition: background .1s, color .1s;
}
.chat-attach-btn:hover, .chat-ref-btn:hover { background: var(--hover); color: var(--ink); }
.chat-ref-btn { font-weight: 700; font-size: 14px; }
.chat-input:focus { border-color: var(--accent); box-shadow: var(--focus-ring); }
.chat-send-btn {
  width: 38px; height: 38px; border-radius: 50%;
  background: var(--ink); color: var(--bg);
  border: none; cursor: pointer; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  transition: filter 0.15s;
}
.chat-send-btn:hover { filter: brightness(1.12); }
.chat-send-btn svg { width: 17px; height: 17px; }

@media (prefers-reduced-motion: reduce) {
  .chat-panel, .chat-msg, .chat-fab { animation: none !important; transition-duration: .001ms !important; }
}
.chat-msg-actions {
  display: none; flex-direction: row; gap: 2px;
  align-self: center; flex-shrink: 0;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 16px; padding: 2px;
  box-shadow: 0 2px 6px rgba(0,0,0,0.06);
}
.chat-msg:hover .chat-msg-actions { display: flex; }
.chat-msg-act {
  background: none; border: none; cursor: pointer;
  width: 24px; height: 24px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; color: var(--muted); transition: background 0.1s;
}
.chat-msg-act:hover { background: var(--bg); color: var(--text); }
.chat-msg-act-del:hover { background: var(--hover); color: var(--danger); }
.chat-view { position: relative; }

/* Reply quote inside bubble */
.chat-reply-quote {
  border-left: 2px solid var(--text); padding: 3px 8px; margin-bottom: 4px;
  background: rgba(0,0,0,0.04); border-radius: 4px;
  cursor: pointer; transition: background 0.1s;
  max-width: 200px;
}
.chat-reply-quote:hover { background: rgba(0,0,0,0.07); }
.chat-msg.mine .chat-reply-quote { border-left-color: var(--surface); background: rgba(255,255,255,0.12); }
.chat-reply-quote .chat-reply-name { font-size: 11px; font-weight: 600; opacity: 0.85; }
.chat-reply-quote .chat-reply-text { font-size: 11px; opacity: 0.7; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Reply bar above input */
/* ── Прикреплённое, но не отправленное ──
   Файл уже на томе, а сообщения ещё нет: полоса над полем и есть эта разница. */
.chat-attach-bar {
  display: none; align-items: center; gap: 8px;
  padding: 8px var(--chat-pad-x) 0;
  font-size: 12px; color: var(--ink-soft);
}
.chat-attach-bar.open { display: flex; }
.chat-attach-thumb { width: 30px; height: 30px; object-fit: cover; border-radius: 5px; flex-shrink: 0; }
.chat-attach-ico { font-size: 15px; flex-shrink: 0; }
.chat-attach-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-attach-size { color: var(--muted); font-size: 11px; flex-shrink: 0; }
.chat-attach-x {
  border: none; background: transparent; cursor: pointer; color: var(--muted);
  font-size: 12px; padding: 2px 4px; font-family: inherit; flex-shrink: 0;
}
.chat-attach-x:hover { color: var(--danger); }


/* ── Подсказка над полем ──
   Всплывает, а не раздвигает панель: список из шести имён, поднявший поле
   ввода на треть экрана, уводит из виду то, на что человек отвечает. */
.chat-compose { position: relative; flex-shrink: 0; }
.chat-suggest {
  display: none; position: absolute; left: var(--chat-pad-x); right: var(--chat-pad-x);
  bottom: 100%; margin-bottom: 6px; z-index: 5;
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-menu);
  overflow: hidden; max-height: 200px; overflow-y: auto;
}
.chat-suggest.open { display: block; }
.chat-sugg-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 7px 10px; border: none; background: transparent; cursor: pointer;
  text-align: left; font-family: inherit; font-size: 13px; color: var(--ink);
}
.chat-sugg-item:hover, .chat-sugg-item.active { background: var(--hover); }
.chat-sugg-av {
  width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0; color: #fff;
  display: inline-flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 600;
}
.chat-sugg-title { flex-shrink: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-sugg-sub { margin-left: auto; color: var(--muted); font-size: 11px; flex-shrink: 0; }
/* Строка о том, чего в списке нет. Не кнопка и выглядит не как кнопка: выбрать
   её нельзя, а строка, похожая на остальные, — это щелчок, который ничего не
   делает. Прилипла к низу коробки намеренно: в коробку помещается пять строк
   из двенадцати, и сказанное про обрезание под последней из них видно только
   тому, кто и так догадался прокрутить. */
.chat-sugg-more {
  position: sticky; bottom: 0;
  padding: 6px 10px; font-size: 11px; color: var(--muted);
  border-top: 1px solid var(--rule); background: var(--surface-2);
}

/* ── Вложение в пузыре ──
   Картинку смотрят, файл забирают: два разных вида у двух разных желаний. */
.chat-att-img { display: block; margin-bottom: 4px; }
.chat-att-img img {
  display: block; max-width: 100%; max-height: 260px;
  border-radius: 8px; object-fit: cover;
}
.chat-att-file {
  display: flex; align-items: center; gap: 7px; margin-bottom: 4px;
  padding: 7px 9px; border-radius: 7px;
  background: var(--surface-2); color: inherit; text-decoration: none; font-size: 12.5px;
}
.chat-msg.mine .chat-att-file { background: rgba(255,255,255,.16); }
.chat-att-file:hover { text-decoration: underline; }
.chat-att-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-att-size { font-size: 11px; opacity: .7; flex-shrink: 0; }

/* ── Ссылка на объект продукта ──
   Карточка, а не подчёркнутое слово: у неё есть подпись, и именно подпись
   отвечает на вопрос «эта съёмка или соседняя». */
.chat-ref {
  display: inline-flex; flex-direction: column; align-items: flex-start; gap: 1px;
  max-width: 100%; margin: 2px 0; padding: 5px 9px;
  border: 1px solid var(--rule-strong); border-radius: 7px;
  background: var(--surface); color: var(--ink);
  font-family: inherit; font-size: 12.5px; text-align: left; cursor: pointer;
  vertical-align: middle;
}
button.chat-ref:hover { border-color: var(--ink); }
.chat-msg.mine .chat-ref { background: rgba(255,255,255,.14); border-color: rgba(255,255,255,.3); color: inherit; }
.chat-ref-kind { font-size: 10px; text-transform: uppercase; letter-spacing: .05em; opacity: .65; }
.chat-ref-title { font-weight: 600; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-ref-sub { font-size: 11px; opacity: .7; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Недоступное и ещё не пришедшее — не кнопки: щёлкать по ним некуда, и
   выглядеть кнопкой они не должны. */
.chat-ref.pending, .chat-ref.gone {
  display: inline-block; cursor: default; color: var(--muted);
  border-style: dashed; font-size: 12px;
}

/* Упоминание — своё имя выделено сильнее чужого: в чате из шести человек
   вопрос не «кого назвали», а «назвали ли меня». */
.chat-mention { font-weight: 600; opacity: .85; }
/* Заливка задана прозрачностью акцента, а не отдельным токеном: тот же цвет
   лежит и на светлой теме, и на тёмной, а имени для «акцент, разбавленный до
   подложки» в палитре нет. */
.chat-mention.me { background: rgba(35,131,226,.16); border-radius: 4px; padding: 0 3px; opacity: 1; }
body.dark .chat-mention.me { background: rgba(91,160,224,.26); }
.chat-msg.mine .chat-mention.me { background: rgba(255,255,255,.22); }

/* «изменено» — тем же кеглем, что время, и рядом с ним: это факт о сообщении,
   а не пометка редактора. */
.chat-edited { font-size: 10.5px; opacity: .6; font-style: italic; }

.chat-reply-bar {
  display: none; align-items: center; gap: 8px;
  padding: 8px 12px; border-top: 1px solid var(--border);
  background: var(--bg);
}
.chat-reply-bar.open { display: flex; }
.chat-reply-bar-info { flex: 1; min-width: 0; border-left: 2px solid var(--text); padding-left: 8px; }
.chat-reply-bar .chat-reply-name { font-size: 11px; font-weight: 600; }
.chat-reply-bar .chat-reply-text { font-size: 11px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-reply-cancel {
  background: none; border: none; cursor: pointer; color: var(--muted);
  font-size: 14px; padding: 4px 6px; border-radius: 4px;
}
.chat-reply-cancel:hover { background: var(--surface); color: var(--text); }

/* Reactions */
.chat-reactions { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 3px; }
.chat-reaction {
  background: var(--bg); border: 1px solid var(--border);
  border-radius: 12px; padding: 2px 7px; font-size: 11px;
  cursor: pointer; transition: background 0.1s, border-color 0.1s;
  display: inline-flex; align-items: center; gap: 3px;
  color: var(--text);
}
.chat-reaction:hover { background: var(--surface); }
.chat-reaction.mine { background: var(--text); color: var(--surface); border-color: var(--text); }
.chat-msg.mine .chat-reactions { justify-content: flex-end; }

/* Emoji picker popup */
.chat-emoji-pop {
  position: fixed; z-index: 250;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 20px; padding: 4px 6px;
  display: flex; gap: 2px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.12);
}
.chat-emoji-btn {
  background: none; border: none; cursor: pointer;
  width: 30px; height: 30px; border-radius: 50%;
  font-size: 17px; transition: transform 0.1s, background 0.1s;
  display: flex; align-items: center; justify-content: center;
}
.chat-emoji-btn:hover { background: var(--bg); transform: scale(1.18); }

/* Read checkmarks */
.chat-meta { display: flex; align-items: baseline; gap: 4px; padding: 0 10px; justify-content: flex-end; }
.chat-msg.mine .chat-meta { justify-content: flex-end; }
.chat-msg:not(.mine) .chat-meta { justify-content: flex-start; }
.chat-time { font-size: 10px; color: var(--muted); padding: 0; }
.chat-read { font-size: 11px; color: var(--muted); letter-spacing: -2px; }
.chat-msg.mine .chat-read { color: rgba(255,255,255,0.65); }

/* Flash on jump-to-message */
.chat-msg-flash .chat-text { animation: chat-flash 1.2s ease; }
@keyframes chat-flash {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  30% { box-shadow: 0 0 0 4px rgba(59,130,246,0.4); }
}

/* Hide FAB while chat panel is open */
body.chat-open .chat-fab { display: none; }
.chat-date-divider {
  text-align: center; font-size: 11px; color: var(--muted); position: relative; margin: 4px 0;
}
.chat-date-divider::before, .chat-date-divider::after {
  content: ''; position: absolute; top: 50%; width: 28%; height: 1px; background: var(--border);
}
.chat-date-divider::before { left: 0; }
.chat-date-divider::after { right: 0; }

/* New DM/Group modal */
.chat-new-overlay { z-index: 300; }
.chat-new-modal { width: 320px; max-height: 420px; overflow-y: auto; }
.chat-new-user-list { display: flex; flex-direction: column; gap: 2px; margin-top: 10px; max-height: 220px; overflow-y: auto; }
.chat-new-user-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: 8px; cursor: pointer;
  transition: background 0.1s; user-select: none;
}
.chat-new-user-item:hover { background: var(--bg); }
.chat-new-user-item.selected { background: var(--bg); }
.chat-new-check {
  width: 18px; height: 18px; border-radius: 50%; border: 2px solid var(--border);
  flex-shrink: 0; display: flex; align-items: center; justify-content: center;
  transition: background 0.1s, border-color 0.1s;
}
.chat-new-user-item.selected .chat-new-check { background: var(--text); border-color: var(--text); color: var(--surface); font-size: 11px; }

@media (max-width: 768px) {
  /* FAB — чуть выше нижней безопасной зоны */
  .chat-fab {
    bottom: calc(18px + env(safe-area-inset-bottom, 0px));
    right: 18px;
    width: 50px; height: 50px;
  }
  .chat-fab svg { width: 20px; height: 20px; }
  .chat-fab .chat-unread { top: -4px; right: -4px; }

  /* Док → bottom-sheet на всю ширину */
  .chat-panel {
    --chat-pad-x: 14px;
    right: 0; bottom: 0; left: 0;
    width: 100vw; height: 88dvh;
    max-width: 100vw;
    border-radius: 16px 16px 0 0;
    border-bottom: none;
    border-left: none; border-right: none;
    flex-direction: column;
    transform-origin: bottom center;
  }
  .chat-panel.visible { animation: dockRiseMobile .24s cubic-bezier(.22,.61,.36,1); }
  @keyframes dockRiseMobile {
    from { opacity: 0; transform: translateY(24px); }
    to   { opacity: 1; transform: none; }
  }

  /* Список тредов = весь экран; пейн уезжает поверх */
  .chat-sidebar { width: 100%; height: 100%; border-right: none; }
  .chat-conv-list { padding: 6px; }
  .chat-conv-item { padding: 10px 9px; }
  /* Заголовок раздела здесь ещё и кнопка, и нажимают её пальцем: ширина у неё
     во всю колонку, а высоты в двадцать семь пикселей для пальца нет. Растёт
     отступ, а не буквы — надпись остаётся надписью. */
  .chat-sep-btn { padding: 13px 9px 7px; }
  .chat-conv-avatar { width: 40px; height: 40px; font-size: 13px; }
  .chat-conv-name { font-size: 14.5px; }
  .chat-conv-last { font-size: 12.5px; }

  .chat-view {
    position: absolute; inset: 0;
    background: var(--bg);
    transform: translateX(100%);
    transition: transform .24s cubic-bezier(.22,.61,.36,1);
    z-index: 2;
  }
  .chat-view.mobile-open { transform: translateX(0); }
  .chat-back-btn { display: flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 50%; transition: background .12s; }
  .chat-back-btn:hover, .chat-back-btn:active { background: var(--hover); color: var(--ink); }

  /* Шапка диалога — крупнее под палец */
  .chat-header { padding: 10px 12px; gap: 8px; }
  .chat-title { font-size: 15.5px; }
  .chat-view-sub { font-size: 12px; }
  .chat-menu-btn, .chat-close-btn { width: 36px; height: 36px; padding: 0; display: inline-flex; align-items: center; justify-content: center; }

  /* Сообщения — больше пузырь, безопасные отступы */
  .chat-messages { padding: 14px var(--chat-pad-x); gap: 10px; }
  .chat-msg { max-width: 86%; }
  .chat-text { font-size: 14px; padding: 9px 13px; }

  /* Инпут — поверх клавиатуры, без зума iOS */
  .chat-input-row {
    padding: 10px var(--chat-pad-x) calc(10px + env(safe-area-inset-bottom, 0px));
  }
  .chat-input { font-size: 16px !important; padding: 10px 14px; }
  .chat-send-btn { width: 42px; height: 42px; }
  .chat-send-btn svg { width: 18px; height: 18px; }
}

/* ── Models DB v4 — мобильные доводки ── */
@media (max-width: 768px) {
  /* Toolbar was overflowing: the left group (view toggle + «Колонки» + search)
     and the right one («…» + ＋Модель) together exceed the phone width and
     overlapped. Let the row wrap so the action buttons fall to a second line
     instead of stacking. */
  .mdb-toolbar {
    padding: 12px var(--pad-x) 8px;
    flex-wrap: wrap; row-gap: 8px;
  }
  .mdb-toolbar-left { flex: 1 1 auto; }
  #mdb-apps-slot { flex-shrink: 0; }
  .mdb-wrap { padding: 4px var(--pad-x) 24px; }
  .mdb-grid { gap: 12px; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
  .mdb-initial { font-size: 52px; top: 6px; right: 12px; }
  .mdb-body { padding: 10px 12px 12px; }
  .mdb-card-name { font-size: 14px; }
  .mdb-agency { font-size: 11px; }
  .mdb-chip { font-size: 10.5px; padding: 2px 7px; }
}
@media (max-width: 480px) {
  .mdb-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .mdb-initial { font-size: 46px; }
  .mdb-card-name { font-size: 13.5px; }
}
@media (max-width: 360px) {
  .mdb-grid { grid-template-columns: 1fr; }
  .mdb-cover { aspect-ratio: 3 / 2; }
}

/* ── Brand button & sidebar nav ── */
.brand.brand-btn {
  background: transparent;
  border: 0;
  width: 100%;
  text-align: left;
  font: inherit;
}
.brand.brand-btn:hover { background: var(--hover, #EFEEEC); }

.nav-section {
  display: flex; flex-direction: column;
  margin: 2px 0 6px;
  padding: 0 6px;
}
.nav-item {
  display: flex; align-items: center; gap: 8px;
  width: 100%;
  padding: 0 8px;
  height: var(--side-item-h, 28px);
  border: 0; background: transparent;
  text-align: left;
  font: inherit;
  font-size: 14px;
  color: var(--ink-soft, #5A584F);
  border-radius: var(--radius-sm, 4px);
  cursor: pointer;
  transition: background .08s;
  text-decoration: none;   /* see the note above: these entries are links */
}
.nav-item:hover { background: var(--hover, #EFEEEC); }
.nav-item.active { background: var(--hover, #EFEEEC); color: var(--ink, #37352F); font-weight: 500; }
.nav-item-ico {
  width: 16px;
  display: inline-flex;
  align-items: center; justify-content: center;
  color: var(--muted, #9B9A93);
  font-size: 12px;
}
.nav-item.active .nav-item-ico { color: var(--ink, #37352F); }
.nav-item-label { flex: 1; }

/* ── Models DB view ── */
/* ══ MODELS DB — card grid (handoff v4) ══ */
/* Прокручивается весь экран, а не одна сетка карточек под тулбаром. Дорожка
   бегунка начиналась там, где начинался список, — то есть на полторы сотни
   пикселей ниже верхней границы содержимого, — и обрубок полосы посреди экрана
   читался как чужой элемент рядом с боковой панелью, которая тянется до самого
   верха. Это и обвели в issue #273.

   Тулбар от переезда внутрь прокрутки не уехал: он `position: sticky` и стоит
   там же, где стоял. Зато `scrollbar-gutter` теперь один на весь экран, и
   правый край тулбара совпадает с правым краем сетки сам — не потому, что в
   двух местах написано одно и то же число (`--sbw`), а потому, что место под
   полосу держит их общий контейнер. Тот разъезд чинили в issue #272, и здесь
   он перестал быть возможным. */
.mdb-container {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  overflow-y: auto; overflow-x: hidden; scrollbar-gutter: stable;
  background: var(--surface, #fff);
  position: relative;
}
.mdb-wrap {
  padding: 4px var(--pad-x, 32px) var(--pad-x, 32px);
}
/* Wraps instead of letting the action buttons ride over the left group once the
   open side panel squeezes the list column.

   Липкий, потому что прокручивается теперь весь экран (см. `.mdb-container`):
   без этого «+ Модель», поиск и воронка уезжали бы вверх на первом же провороте
   ростера. Заливка обязательна — под тулбаром проезжают карточки. Слой ниже
   выпадающих меню (30) и выше самой сетки.

   Отступ справа — просто `--pad-x`. Тут стояло `--pad-x` плюс ширина полосы
   прокрутки: тулбар не прокручивался, сетка под ним прокручивалась, и без
   добавки «Заявки» стояли на пятнадцать пикселей правее правого края последней
   фотографии (issue #272). Теперь оба внутри одной прокрутки, место под полосу
   держит контейнер, и добавка сдвигала бы тулбар на те же пятнадцать пикселей в
   обратную сторону. */
.mdb-toolbar {
  position: sticky; top: 0; z-index: 4;
  background: var(--surface, #fff);
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px var(--pad-x, 32px) 10px;
  flex-shrink: 0;
  flex-wrap: wrap; gap: 8px 12px;
}
.mdb-toolbar-left { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1 1 auto; }
#mdb-apps-slot { flex-shrink: 0; }
.mdb-view-toggle {
  display: flex; gap: 2px; flex-shrink: 0;
  padding: 2px;
  background: var(--surface-2, #F7F6F3);
  border-radius: var(--radius, 6px);
}
.mdb-view-btn {
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 24px;
  border: 0; border-radius: 4px;
  background: transparent;
  color: var(--muted, #9B9A93);
  cursor: pointer;
  transition: background .12s, color .12s;
}
.mdb-view-btn svg { fill: currentColor; }
.mdb-view-btn:hover { color: var(--ink, #37352F); }
.mdb-view-btn.active {
  background: var(--surface, #fff);
  color: var(--ink, #37352F);
  box-shadow: 0 1px 2px rgba(55,53,47,.10);
}

/* List view */
.mdb-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--rule, rgba(55,53,47,.09));
  border-radius: var(--radius, 6px);
  background: var(--surface, #fff);
}
.mdb-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.mdb-table th {
  text-align: left; font-weight: 600;
  font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted, #9B9A93);
  background: var(--surface-2);
  padding: 9px 14px;
  border-bottom: 1px solid var(--rule, rgba(55,53,47,.09));
  white-space: nowrap;
}
.mdb-table td {
  padding: 8px 14px;
  border-bottom: 1px solid var(--rule, rgba(55,53,47,.06));
  color: var(--ink, #37352F);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.mdb-table tbody tr:last-child td { border-bottom: 0; }
/* Trailing spacer soaks up leftover width so the real columns pack tightly to
   the left instead of the name column ballooning with empty space. */
.mdb-table .mdb-col-sp { width: 100%; padding: 0; border-bottom: 0; }
.mdb-row { cursor: pointer; transition: background .12s; }
.mdb-row:hover { background: var(--hover, #FAF9F7); }
.mdb-row.active { background: var(--surface-2, #F2F1EE); }
.mdb-row-name { display: flex; align-items: center; gap: 9px; font-weight: 500; }
.mdb-row-thumb { flex-shrink: 0; }
.mdb-row-img, .mdb-row-ini {
  width: 26px; height: 26px; border-radius: 50%;
  object-fit: cover; display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 600;
}
.mdb-col-email a, .mdb-col-mobile a { color: var(--muted, #6B6862); text-decoration: none; }
.mdb-col-email a:hover, .mdb-col-mobile a:hover { color: var(--ink, #37352F); text-decoration: underline; }

/* Copy shortcuts inside a table row: icon-only, revealed on hover. */
.mdb-row-nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.mdb-row-copy {
  display: inline-flex; gap: 4px; margin-left: 2px;
  opacity: 0; transition: opacity .12s;
}
.mdb-row:hover .mdb-row-copy,
.mdb-row-copy:focus-within { opacity: 1; }
/* Touch devices never hover — keep the copy buttons permanently visible there. */
@media (hover: none) {
  .mdb-row-copy { opacity: 1; }
}
.mdb-copy-btn-sm {
  padding: 4px; gap: 0;
  background: var(--surface-2, #F2F1EE);
  box-shadow: none;
}
.mdb-copy-btn-sm:hover { background: var(--surface, #fff); }

/* Column picker */
.mdb-cols-wrap { position: relative; display: flex; }
.mdb-cols-btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 9px;
  border: 1px solid var(--rule, rgba(55,53,47,.09));
  border-radius: var(--radius, 6px);
  background: var(--surface, #fff);
  color: var(--muted, #6B6862);
  font: inherit; font-size: 12px;
  cursor: pointer;
  white-space: nowrap;
}
.mdb-cols-btn svg { fill: currentColor; }
.mdb-cols-btn:hover { color: var(--ink, #37352F); background: var(--hover, #EFEEEC); }
.mdb-cols-menu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 30;
  min-width: 170px;
  padding: 6px;
  background: var(--surface, #fff);
  border: 1px solid var(--rule, rgba(55,53,47,.12));
  border-radius: 8px;
  box-shadow: 0 8px 28px rgba(0,0,0,.13);
}
.mdb-cols-menu.hidden { display: none; }
.mdb-cols-item {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border-radius: 5px;
  font-size: 13px; color: var(--ink, #37352F);
  cursor: pointer;
}
.mdb-cols-item:hover { background: var(--hover, #F7F6F3); }
.mdb-cols-item input { cursor: pointer; margin: 0; }

/* URL field with an "open" affordance */
.mdb-linkfield { display: flex; align-items: center; gap: 4px; }
.mdb-linkfield .mdb-input { flex: 1; min-width: 0; }
.mdb-link-open {
  flex-shrink: 0;
  padding: 4px 6px; border-radius: 4px;
  color: var(--muted, #9B9A93);
  text-decoration: none; font-size: 13px;
}
.mdb-link-open:hover { background: var(--hover, #EFEEEC); color: var(--ink, #37352F); }
.mdb-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
  gap: 14px;
}
.mdb-empty { padding: 24px; color: var(--muted, #9B9A93); font-size: 13px; }
.mdb-empty-val { color: var(--muted, #9B9A93); }

/* Card */
.mdb-card {
  background: var(--surface, #fff);
  border: 1px solid var(--rule, rgba(55,53,47,.09));
  border-radius: var(--radius, 6px);
  overflow: hidden;
  cursor: pointer;
  transition: border-color .14s, box-shadow .14s, transform .14s;
  display: flex; flex-direction: column;
}
.mdb-card:hover {
  border-color: var(--rule-strong, rgba(55,53,47,.16));
  box-shadow: 0 4px 18px rgba(55,53,47,.07);
  transform: translateY(-2px);
}
.mdb-card.active {
  border-color: var(--ink, #37352F);
  box-shadow: 0 4px 18px rgba(55,53,47,.10);
}
/* Ширина плитки: узкая тянущаяся колонка и плитка ровно в её минимум.

   Тянуть плитку на всю колонку (`1fr` от 248px) нельзя: ширина списка не
   константа — открытая карточка модели забирает треть экрана, колонок остаётся
   вдвое меньше, и оставшиеся делят между собой всё освободившееся место. Снимки
   в списке вырастали в ответ на открытие карточки, к их масштабу отношения не
   имеющее: 262px → 320px даже после того, как потолок поставили в 320.

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

   Потолок на плитке (240px) размах уменьшил, но само движение осталось, и
   ровно оно в issue #277. Панель едет треть секунды, ширина списка проезжает за
   это время все промежуточные значения, и на каждой смене числа колонок плитка
   срывается с нижнего края вилки на верхний: на ноутбуке 1266px это
   224 → 200 → 240 → 200 → 240 → 222 за триста миллисекунд. Фотография дважды
   дёргается, пока карточка открывается, — а виноват в этом не её масштаб, а
   вилка, внутри которой плитке разрешили гулять.

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

   Число написано один раз: минимум колонки и ширина плитки — это одна величина,
   и разъехавшись на полшага они вернули бы ту же вилку.

   Телефона это не касается: там панель открывается на весь экран
   (см. `.side-panel.open` в блоке 768px), сетки за ней нет вовсе, а
   единственная колонка обязана занимать всю ширину. */
@media (min-width: 769px) {
  .mdb-grid { --mdb-tile: 200px; grid-template-columns: repeat(auto-fill, minmax(var(--mdb-tile), 1fr)); }
  .mdb-card { width: var(--mdb-tile); }
}
.mdb-cover {
  aspect-ratio: 4 / 5;
  position: relative;
  display: flex; align-items: flex-end;
  padding: 12px;
  background: linear-gradient(160deg, var(--mc-light, #F5EDE0), var(--mc, #C8A882));
  overflow: hidden;
}
.mdb-cover-img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  z-index: 0;
}
.mdb-initial {
  font-family: var(--serif, 'DM Serif Display', Georgia, serif);
  font-size: 64px; line-height: .8;
  color: rgba(20, 18, 14, .55);
  position: absolute; top: 8px; right: 14px;
  pointer-events: none;
  z-index: 1;
}
.mdb-ph {
  position: absolute; inset: 0;
  background-image: repeating-linear-gradient(135deg, transparent 0 9px, rgba(255,255,255,.18) 9px 10px);
  pointer-events: none;
  z-index: 1;
}
.mdb-status {
  position: relative; z-index: 2;
}
/* «Параметры» и «Контакты» кладут в буфер то, что букер каждый день
   перепечатывает в переписку с клиентом, поэтому они стоят на самой плитке, а
   не в карточке: за ними не идут вглубь, ими пользуются на бегу.

   На обложке — молочная подложка: под ними бывает лицо, и сплошная плашка
   закрыла бы ровно то, ради чего плитку и открыли. В тулбаре карточки те же
   две кнопки выглядят как соседние, обычной кнопкой. */
.mdb-copy-bar {
  position: absolute; top: 8px; left: 8px; right: 8px;
  display: flex; flex-wrap: wrap; gap: 6px;
  z-index: 3;
}
.mdb-copy-btn {
  display: inline-flex; align-items: center; gap: 5px;
  height: 24px; padding: 0 9px;
  border: 1px solid var(--rule); border-radius: var(--radius-sm, 4px);
  background: var(--surface);
  color: var(--ink-soft);
  font: inherit; font-size: 11.5px; font-weight: 500;
  line-height: 1;
  cursor: pointer;
  transition: background .12s, color .12s;
}
.mdb-copy-bar .mdb-copy-btn {
  border-color: transparent;
  background: rgba(255,255,255,.82);
  color: #37352F;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.mdb-copy-btn svg { fill: currentColor; opacity: .65; }
.mdb-copy-btn:hover { background: var(--hover); color: var(--ink); }
.mdb-copy-bar .mdb-copy-btn:hover { background: #fff; }
.mdb-body { padding: 12px 14px 14px; }
.mdb-card-name {
  font-size: 15px; font-weight: 600;
  color: var(--ink, #37352F);
  letter-spacing: -0.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mdb-agency {
  font-size: 12px; color: var(--muted, #9B9A93);
  margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
a.mdb-email-link {
  display: block; text-decoration: none; cursor: pointer;
  color: var(--muted, #9B9A93);
}
a.mdb-email-link:hover { color: var(--ink, #37352F); text-decoration: underline; }
.mdb-meta {
  display: flex; flex-wrap: wrap;
  gap: 5px 6px;
  margin-top: 11px;
}
/* The status chip is a button when the user can change it. `font: inherit`
   strips the UA button font, but it also inherits the cover's base size — so
   this rule must precede .mdb-chip, whose font-size wins the cascade and keeps
   both flavours of the chip the same size. */
.mdb-status-chip { cursor: pointer; border: 0; font: inherit; }
.mdb-chip {
  font-size: 11px; color: var(--ink-soft, #5A584F);
  background: var(--surface-2, #F7F6F3);
  padding: 2px 8px;
  border-radius: var(--radius-sm, 4px);
  font-variant-numeric: tabular-nums;
}
/* Card stats: plain text, no grey boxes. */
.mdb-meta { gap: 4px 12px; }
.mdb-meta .mdb-chip {
  background: none; padding: 0;
  color: var(--muted, #9B9A93); font-size: 12px;
}

/* ── MODELS DB — profile editor (rendered inside side-panel) ── */
.mdb-detail { padding: 0; overflow: visible; }
.mdb-detail-inner { max-width: 1080px; margin: 0 auto; padding: 0 34px 40px; }

/* Фотография, имя и значки — одной строкой в самом верху панели, тем же
   устройством, что и шапка карточки съёмки (`.spn-head`): две карточки,
   открывающиеся в одном и том же месте экрана, начинаются одинаково.

   Отдельной полосой над именем значки стояли с issue #167, но после issue #273
   в ней остались только «…» и крест — а имя из-за неё начиналось на семьдесят
   пикселей ниже верхнего края панели, и больше эта полоса не показывала ничего.
   Про эту пустоту и issue #296.

   Липкость строка забрала себе у прежней полосы, и по той же причине: анкета
   длинная, и крест обязан быть под рукой на любой её высоте. Заодно на любой её
   высоте видно, чья карточка открыта.

   Значки прижаты к верху строки, а не к её середине: рядом с фотографией в
   64px центрированный крест уехал бы от угла панели вниз — то есть ровно туда,
   откуда его сейчас поднимают. `margin-left: auto` держит их у правого края и
   тогда, когда имя короткое: без него они встают вплотную к имени, и панель
   выглядит незакрываемой. */
.mdb-head {
  position: sticky; top: 0; z-index: 5;
  display: flex; align-items: center; gap: 18px;
  background: var(--bg);
  padding: 12px 0 10px;
}
.mdb-head .spn-head-acts { align-self: flex-start; margin-left: auto; }
/* Avatar and its add/remove button stack in a column so "Убрать фото" sits
   directly under the photo instead of floating next to the name. */
.mdb-avatar-col { display: flex; flex-direction: column; align-items: center; gap: 6px; flex-shrink: 0; }
.mdb-avatar {
  position: relative;
  width: 64px; height: 64px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 20px; font-weight: 600;
  flex-shrink: 0; overflow: hidden;
}
.mdb-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mdb-avatar[onclick] { cursor: pointer; }
.mdb-avatar-cam {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 18px; background: rgba(0,0,0,0.42); color: #fff;
  opacity: 0; transition: opacity .15s;
}
.mdb-avatar[onclick]:hover .mdb-avatar-cam { opacity: 1; }
/* Empty avatar keeps a faint camera cue so it reads as "tap to add" — hover
   isn't available on touch devices. Avatars that already have a photo stay clean
   until hovered. */
.mdb-avatar[onclick]:not(.has-img) .mdb-avatar-cam { opacity: 0.5; background: rgba(0,0,0,0.28); }
.mdb-avatar.has-img:not(:hover) .mdb-avatar-cam { opacity: 0; }
.mdb-avatar-remove {
  width: fit-content;
  background: none; border: none; padding: 0;
  font-size: 11px; color: var(--muted, #8B8680); cursor: pointer;
  text-decoration: underline;
}
.mdb-avatar-remove:hover { color: var(--danger, #C0392B); }
/* Имя набрано тем же лицом и кеглем, что и заголовок карточки события в этой
   же панели (`.spn-title`): две карточки, открывающиеся в одном и том же месте
   экрана разными шрифтами, читаются как два разных продукта. Засечное лицо
   стояло здесь до issue #274, и оно же ломало имя надвое — «Ameli», а под ним
   «Magadova»: у панели ширина в треть экрана, а у `DM Serif Display` — своя,
   заметно большая, ширина знака.

   Вес 600, а не 400, по той же причине, что записана у `.spn-title`: толщина
   штриха засечного лица, перенесённая в Inter числом, даёт не «крупно», а
   «бледно».

   Одну строку держат три вещи сразу, и убрать нельзя ни одну: `nowrap`
   запрещает перенос; `min-width: 0` разрешает флексу сжать заголовок — без
   него `nowrap` делает минимальной шириной всё имя целиком, и оно выталкивает
   из шапки аватар; многоточие дорисовывает остаток у имён, которым не хватило
   даже сжатого кегля (см. `_fitName` в `06-models.js`). */
.mdb-name {
  font-family: var(--sans);
  font-size: var(--t-display, 32px); font-weight: 600; line-height: 1.2; margin: 0;
  color: var(--ink, #37352F);
  letter-spacing: -0.01em;
  /* Основа нулевая, а не по содержимому: имя стоит в строке, которой на
     телефоне разрешено переноситься (блок 720px), и заголовок шириной в свой
     текст уезжал бы на строку под фотографию вместо того, чтобы сжаться рядом
     с ней. Флекс переносит раньше, чем сжимает. */
  flex: 1 1 0;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The name is editable in place: an input that reads as the heading until touched. */
.mdb-name-input {
  flex: 1 1 160px;
  min-width: 0;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  padding: 2px 6px;
  margin-left: -6px;
  outline: none;
}
.mdb-name-input:hover { border-color: var(--border, #E9E8E5); }
.mdb-name-input:focus { border-color: var(--ink, #37352F); background: var(--surface, #fff); }

.mdb-select { cursor: pointer; appearance: auto; }
.mdb-select.mdb-empty-val { color: var(--muted, #9B9A93); }

/* Анкета в одну колонку. Две читаются зигзагом: сетка раскладывает поля по
   строкам, и «Рост» оказывается справа от «Статуса», а не под ним, — глазу
   приходится держать в голове, где закончилась левая половина. */
.mdb-props {
  display: flex;
  flex-direction: column;
  max-width: 620px;
}
.mdb-prop {
  display: grid;
  grid-template-columns: minmax(110px, 140px) 1fr;
  align-items: start;
  gap: 10px;
  padding: 3px 0;
  min-height: 36px;
  border-bottom: 1px solid var(--rule);
}
.mdb-prop-l {
  color: var(--muted, #9B9A93);
  font-size: 13px;
  padding-top: 8px;
}
.mdb-prop-v { font-size: 13.5px; color: var(--ink, #37352F); min-width: 0; overflow-wrap: anywhere; }
/* Значения стоят одной колонкой, и три строки из двадцати — «Years old» и два
   скана паспорта — не поля, а текст. Отступ у них тот же, что у поля рядом
   (рамка плюс padding), иначе анкета, которая теперь вся живая (issue #297),
   разъезжается по левому краю на эти самые три строки. */
.mdb-val { display: inline-block; padding: 6px 9px; }
.mdb-input {
  width: 100%;
  border: 1px solid transparent;
  background: transparent;
  padding: 6px 8px;
  border-radius: 4px;
  font: inherit;
  color: inherit;
  outline: none;
  font-size: 14px;
}
.mdb-input:hover { border-color: var(--border, #E9E8E5); }
.mdb-input:focus { border-color: var(--ink, #37352F); background: var(--surface, #fff); }
.mdb-textarea { resize: vertical; min-height: 32px; line-height: 1.4; }

.mdb-file { display: flex; align-items: center; gap: 8px; padding: 2px 0; }
.mdb-file-link {
  padding: 6px 4px;
  color: var(--ink, #37352F);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.mdb-file-link:hover { color: var(--accent, #C8A882); }
/* Ссылка на скан — та же колонка значений анкеты, что и поля выше, поэтому и
   отступ тот же (см. `.mdb-val`). Правило узкое: этой же ссылкой набраны файлы
   в документообороте и финансах, где колонки свои. */
.mdb-props .mdb-file-link { padding-left: 9px; }
.mdb-file-actions { display: inline-flex; gap: 4px; margin-left: auto; }

/* Compact square icon buttons (passport replace/delete, share copy/revoke) that
   sit tight to the right of a link instead of full-width text buttons. */
.mdb-icon-btn {
  flex-shrink: 0;
  width: 26px; height: 26px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border, #E9E8E5); border-radius: 6px;
  background: var(--surface, #fff); cursor: pointer;
  font-size: 13px; line-height: 1; color: var(--ink, #37352F);
}
.mdb-icon-btn:hover { background: var(--hover, #EFEEEC); }
.mdb-icon-danger:hover { border-color: var(--danger, #C0392B); color: var(--danger, #C0392B); }

/* Крест карточки модели рисуется вместе с `.spn-icon-act` — правила у них одни
   (см. блок карточки события выше). */

/* Метки режима правки и полосы «Отмена · Сохранить» здесь больше нет: анкета
   правится по полю (issue #297), и сохранять нечего — уход из поля и есть
   сохранение. */
.mdb-btn {
  padding: 6px 12px;
  border: 1px solid var(--border, #E9E8E5);
  background: var(--surface, #fff);
  border-radius: 6px;
  font-size: 13px;
  cursor: pointer;
  white-space: nowrap;
}
.mdb-btn:hover { background: var(--hover, #EFEEEC); }
.mdb-btn:disabled { opacity: .6; cursor: default; }
.mdb-btn-danger { color: var(--danger, #C0392B); }
.mdb-btn-primary {
  background: var(--ink, #37352F);
  border-color: var(--ink, #37352F);
  color: #fff;
  padding: 8px 20px;
}
.mdb-btn-primary:hover { background: #000; }
.mdb-btn-primary:disabled:hover { background: var(--ink, #37352F); }

/* Поле с готовой публичной ссылкой. Рамка вокруг него на карточке модели
   разобрана (issue #273, действия уехали в меню «…»), но само поле осталось —
   его показывает пакет (`.pkg-share`, 15-packages.js), и правило у них одно. */
.mdb-share-input {
  flex: 1 1 auto; min-width: 0;
  padding: 6px 10px; font-size: 13px;
  border: 1px solid var(--rule-strong, #E9E8E5); border-radius: 6px;
  background: var(--surface, #fff); color: var(--ink);
}

/* ── Books / materials (shareable galleries) ── */
/* Линии сверху у секции нет (issue #299): раньше она отбивала буки от анкеты,
   когда те лежали в её низу, а теперь это своя вкладка, и рисовалась линия в
   двадцати пикселях под подчёркиванием ряда вкладок — две черты подряд, между
   которыми ничего. */
.mdb-books { margin: 14px 4px 8px; }
.mdb-books-loading { font-size: 13px; color: var(--muted); }
.mdb-books-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
.mdb-books-h { font-size: 12px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }

/* Пустая вкладка буков — одна строка словами, без пунктирной коробки
   `.mdb-books-empty` (issue #299): коробка стоит прямо над такой же пунктирной
   кнопкой «＋», и две рамки подряд читаются как два места, куда можно нажать,
   хотя нажимается только нижняя. */
.mdb-books-none { font-size: 13px; color: var(--muted); margin-bottom: 10px; }

/* «Новый бук» — пустое место под последним буком, а не кнопка в заголовке:
   рамка той же ширины, что и карточки над ней, и пунктиром, потому что это
   место, которое можно занять, а не действие, которое от тебя ждут. Внутри
   один плюс по центру (issue #299): подпись повторяла то же, что и он сам, а
   полоса во всю ширину и так читается как «сюда можно дописать». */
.mdb-book-newcard {
  display: flex; align-items: center; justify-content: center; width: 100%;
  padding: 14px 12px; margin-bottom: 12px;
  border: 1px dashed var(--rule-strong, #E9E8E5); border-radius: var(--radius, 8px);
  background: transparent; color: var(--ink, #37352F); font: inherit; font-size: 14px;
  cursor: pointer; transition: background 120ms, border-color 120ms;
}
.mdb-book-newcard:hover { background: var(--hover, #EFEEEC); border-color: var(--accent, #C8A882); }
.mdb-book-newcard-plus { font-size: 18px; line-height: 1; color: var(--muted, #787066); }
.mdb-books-empty { font-size: 13px; color: var(--muted); line-height: 1.5;
  padding: 14px; border: 1px dashed var(--rule-strong, #E9E8E5); border-radius: var(--radius, 8px); }

.mdb-book { border: 1px solid var(--rule-strong, #E9E8E5); border-radius: var(--radius, 8px);
  padding: 12px; margin-bottom: 12px; background: var(--surface, #fff); }
.mdb-book-top { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 10px; }
/* Точки стоят в правом верхнем углу рамки (issue #276) — там же, где «…»
   карточки модели и карточки съёмки. Прижаты к верху, а не к центру строки:
   в правке на месте названия стоит поле ввода, и кнопка, выровненная по его
   середине, съезжала бы вниз ровно на разницу их высот. */
.mdb-book-more {
  flex: none; width: 26px; height: 26px; padding: 0; margin: 1px 0 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 6px; background: transparent;
  color: var(--muted, #9B9A93); cursor: pointer; transition: background 120ms, color 120ms;
}
.mdb-book-more:hover { background: var(--hover, #EFEEEC); color: var(--ink, #37352F); }
.mdb-book-title { flex: 1 1 auto; min-width: 0; font-size: 15px; font-weight: 600;
  padding: 5px 8px; border: 1px solid transparent; border-radius: 6px; background: transparent; color: var(--ink); }
.mdb-book-title:hover { border-color: var(--rule-strong, #E9E8E5); }
.mdb-book-title:focus { outline: none; border-color: var(--accent, #C8A882); background: var(--surface, #fff); }
.mdb-book-title-static { flex: 1 1 auto; min-width: 0; font-size: 15px; font-weight: 600;
  padding: 5px 2px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mdb-book-empty-note { font-size: 12px; color: var(--muted); padding: 2px 2px 8px; }

.mdb-book-thumbs { display: grid; grid-template-columns: repeat(auto-fill, minmax(66px, 1fr));
  gap: 6px; margin-bottom: 10px; }
.mdb-book-thumb { position: relative; aspect-ratio: 3 / 4; border-radius: 6px; overflow: hidden;
  background: var(--hover, #EFEEEC); }
.mdb-book-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mdb-book-thumb-del { position: absolute; top: 2px; right: 2px; width: 18px; height: 18px;
  border: 0; border-radius: 50%; background: rgba(0,0,0,0.62); color: #fff; font-size: 13px;
  line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity 120ms; }
.mdb-book-thumb:hover .mdb-book-thumb-del { opacity: 1; }

.mdb-book-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.mdb-book-chip { display: inline-flex; align-items: center; gap: 4px; max-width: 100%;
  padding: 3px 4px 3px 9px; font-size: 12px; border: 1px solid var(--rule-strong, #E9E8E5);
  border-radius: 14px; background: var(--surface-2, #F7F6F3); }
.mdb-book-chip a { color: var(--ink); text-decoration: none; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; max-width: 200px; }
.mdb-book-chip a:hover { text-decoration: underline; }
.mdb-book-chip-del { border: 0; background: none; color: var(--muted); cursor: pointer;
  font-size: 15px; line-height: 1; padding: 0 2px; }
.mdb-book-chip-del:hover { color: #C0392B; }

.mdb-book-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.mdb-book-add { padding: 5px 11px; font-size: 12.5px; border: 1px solid var(--rule-strong, #E9E8E5);
  border-radius: 6px; background: var(--surface, #fff); color: var(--ink); cursor: pointer;
  transition: background 120ms; }
.mdb-book-add:hover { background: var(--hover, #EFEEEC); }
/* «Готово» стоит в том же ряду, что «＋ Фото», и им же гасится правка: пункт
   меню — дверь, а не единственный выход. */
.mdb-book-done { margin-left: auto; }

/* Правящийся бук отмечен рамкой, а не только крестиками на фотографиях: буков
   на вкладке полдюжины, и «где я сейчас правлю» должно читаться с одного
   взгляда, а не по наличию мелких кнопок. */
.mdb-book.editing { border-color: var(--accent, #C8A882); }

.mdb-book-sharebar { border-top: 1px dashed var(--rule, rgba(55,53,47,0.09)); padding-top: 10px; }
.mdb-book-shared { font-size: 12px; color: var(--muted, #787066); }
a.mdb-icon-btn { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; }

.mdb-book-views-btn { margin-top: 8px; padding: 2px 0; border: 0; background: none;
  font: inherit; font-size: 12px; color: var(--muted, #787066); cursor: pointer; }
.mdb-book-views-btn:hover { color: var(--ink); }
.mdb-book-views-none { margin-top: 8px; font-size: 12px; color: var(--muted, #787066); }
.mdb-book-views[hidden] { display: none; }
.mdb-book-views { margin-top: 6px; max-height: 220px; overflow-y: auto; }
.mdb-book-views-row { display: flex; gap: 10px; justify-content: space-between;
  padding: 3px 0; font-size: 12px; color: var(--muted, #787066); }
/* The address is the only way to tell one client from another, so it must not
   be the thing that wraps when the row is narrow. */
.mdb-book-views-ip { flex: none; font-variant-numeric: tabular-nums; }
.mdb-book-views-more { opacity: 0.7; }

@media (max-width: 720px) {
  /* Сетка карточек берёт значения из mobile-блока выше; здесь — только панель профиля */
  .mdb-prop { grid-template-columns: 1fr; gap: 2px; }
  .mdb-name { font-size: 22px; }
  .mdb-book-thumbs { grid-template-columns: repeat(auto-fill, minmax(56px, 1fr)); }
}

/* ── Plavnost: view enter + card stagger (handoff Part 5) ── */
@keyframes viewIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.view-anim { animation: viewIn .32s cubic-bezier(.22,.61,.36,1); }

@keyframes evIn {
  from { opacity: 0; transform: translateY(6px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
.stagger-ev { opacity: 0; animation: evIn .34s cubic-bezier(.22,.61,.36,1) forwards; }

@media (prefers-reduced-motion: reduce) {
  .view-anim, .stagger-ev { animation: none !important; opacity: 1 !important; transform: none !important; }
  .side-panel, .app, .view-row, .content-area { transition-duration: .001ms !important; }
}

/* ── Wiki view ── */
.wiki-container {
  display: grid;
  grid-template-columns: 320px 1fr;
  height: 100%;
  min-height: 0;
  overflow: hidden;
  background: var(--surface);
}
.wiki-list {
  border-right: 1px solid var(--rule);
  overflow-y: auto;
  padding: 12px 8px;
  background: var(--surface-2);
}
.wiki-list-head {
  padding: 4px 6px 12px;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 10px;
}
.wiki-new-btn {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--rule-strong);
  background: var(--surface);
  border-radius: var(--radius);
  font: inherit;
  font-size: 13px;
  color: var(--ink);
  cursor: pointer;
  text-align: left;
}
.wiki-new-btn:hover { background: var(--hover); }

.wiki-cats { display: flex; flex-direction: column; gap: 14px; }
.wiki-cat-h {
  display: flex; align-items: center; gap: 6px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  padding: 4px 8px 4px 6px;
  font-weight: 600;
  cursor: pointer;
  border-radius: var(--radius);
  user-select: none;
}
.wiki-cat-h:hover { background: var(--hover); color: var(--ink); }
.wiki-cat-h:hover .wiki-cat-plus { opacity: 1; }
.wiki-cat-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wiki-cat-caret {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; font-size: 14px;
  color: var(--ink);
  font-weight: 900;
  transition: transform 120ms;
  flex-shrink: 0;
}
.wiki-cat-caret.open { transform: rotate(90deg); }
.wiki-cat-caret:hover { background: var(--hover); border-radius: 3px; }
.wiki-cat-h.active { background: var(--surface-2, #F2F1EE); color: var(--ink); }

/* Section-as-page: table of contents */
.wiki-toc-wrap { margin-top: 8px; }
.wiki-toc { list-style: none; margin: 8px 0 0; padding: 0; }
.wiki-toc ul { list-style: none; margin: 2px 0 2px 18px; padding: 0; border-left: 1px solid var(--rule, rgba(55,53,47,.09)); }
.wiki-toc li { margin: 1px 0; }
.wiki-toc-link {
  display: inline-block; padding: 5px 8px; border-radius: var(--radius, 6px);
  color: var(--accent, #2383E2); cursor: pointer; font-size: 15px; line-height: 1.35;
  text-decoration: none;
}
.wiki-toc-link:hover { background: var(--hover); text-decoration: underline; }
.wiki-toc-empty { color: var(--muted); font-size: 14px; padding: 8px 0; }
.wiki-cat-plus {
  border: 0; background: transparent;
  width: 22px; height: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius);
  cursor: pointer;
  color: var(--ink);
  font-size: 20px; line-height: 1;
  font-weight: 900;
  padding: 0;
  opacity: 0.65;
  flex-shrink: 0;
  transition: background 120ms, color 120ms, opacity 120ms;
}
.wiki-cat-plus:hover {
  background: var(--ink);
  color: var(--surface);
  opacity: 1;
}
.wiki-cat-empty {
  font-size: 12px;
  color: var(--muted-soft);
  padding: 4px 10px;
}
.wiki-row {
  display: flex; align-items: center; gap: 6px;
  width: 100%; padding: 5px 10px;
  border: 0; background: transparent;
  border-radius: var(--radius);
  cursor: pointer;
  text-align: left; font: inherit;
  margin-bottom: 1px;
  color: var(--ink);
}
.wiki-row:hover { background: var(--hover); }
.wiki-row.active { background: var(--hover); font-weight: 600; }
.wiki-row-title {
  font-size: 13px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  flex: 1;
}

/* Per-row tools: add a sub-page, or grab the row to move it. */
.wiki-row-plus, .wiki-row-grip {
  flex-shrink: 0;
  opacity: 0;
  border: 0; background: transparent; padding: 0;
  width: 16px; height: 16px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted);
  border-radius: 3px;
  transition: opacity .12s, background .12s, color .12s;
}
.wiki-row-plus { font-size: 14px; line-height: 1; cursor: pointer; }
.wiki-row-grip { font-size: 11px; cursor: grab; letter-spacing: -1px; }
.wiki-row:hover .wiki-row-plus,
.wiki-row:hover .wiki-row-grip { opacity: .65; }
.wiki-row-plus:hover, .wiki-row-grip:hover { opacity: 1 !important; background: var(--rule); color: var(--ink); }
.wiki-row.dragging { opacity: .45; }

/* Drop feedback: a line beside the row, or a frame around it when nesting. */
.wiki-row.drop-into { box-shadow: inset 0 0 0 2px var(--accent, #C8A882); background: var(--hover); }
.wiki-row.drop-before, .wiki-row.drop-after { position: relative; }
.wiki-row.drop-before::before, .wiki-row.drop-after::after {
  content: ''; position: absolute; left: 6px; right: 6px; height: 2px;
  background: var(--accent, #C8A882); border-radius: 2px;
}
.wiki-row.drop-before::before { top: -1px; }
.wiki-row.drop-after::after { bottom: -1px; }
.wiki-cat-h.drop-into { box-shadow: inset 0 0 0 2px var(--accent, #C8A882); }
.wiki-caret {
  border: 0; background: transparent;
  width: 16px; height: 16px;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  color: var(--muted);
  font-size: 10px;
  transition: transform 120ms;
  flex-shrink: 0;
  padding: 0;
}
.wiki-caret:hover { color: var(--ink); }
.wiki-caret.open { transform: rotate(90deg); }
.wiki-caret-spacer { width: 16px; height: 16px; flex-shrink: 0; }
.wiki-shared-dot {
  color: var(--accent, #2563EB);
  font-size: 10px;
  line-height: 1;
}

/* Breadcrumbs above the title */
.wiki-crumbs {
  font-size: 12px;
  color: var(--muted);
  margin: 0 0 10px;
  display: flex; flex-wrap: wrap; align-items: center;
}
.wiki-crumb {
  color: var(--muted);
  cursor: pointer;
  text-decoration: none;
}
.wiki-crumb:hover { color: var(--ink); text-decoration: underline; }
.wiki-crumb-sep { margin: 0 6px; opacity: 0.5; }

/* Children list (auto overview at bottom of parent article) */
.wiki-children {
  margin-top: 40px;
  padding-top: 24px;
  border-top: 1px solid var(--rule);
}
.wiki-children-h {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  margin-bottom: 12px;
}
.wiki-children-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 8px;
}
.wiki-child-card {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  text-align: left;
  padding: 12px 14px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
  transition: background 120ms;
}
.wiki-child-card:hover { background: var(--hover); }
.wiki-child-title { font-weight: 500; }

/* Share block */
.wiki-share-block {
  margin-top: 40px;
  padding: 16px 18px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface-2, var(--surface));
}
.wiki-share-h {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  margin-bottom: 10px;
}
.wiki-share-row {
  display: flex; gap: 6px; flex-wrap: wrap;
}
.wiki-share-input {
  flex: 1; min-width: 240px;
  padding: 7px 10px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
}
.wiki-share-hint {
  font-size: 12px;
  color: var(--muted);
  margin-top: 8px;
}
.wiki-share-warn {
  font-size: 12px;
  color: var(--danger);
  margin-top: 8px;
}
.wiki-shared-dot.dead { color: var(--muted); }

.wiki-detail {
  overflow-y: auto;
  padding: 32px 48px 64px;
}
.wiki-detail-inner { max-width: 760px; margin: 0 auto; }
.wiki-empty { padding: 24px; color: var(--muted); font-size: 13px; }

.wiki-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  margin-bottom: 18px;
}
.wiki-meta { display: flex; align-items: center; gap: 10px; }
.wiki-cat-tag {
  display: inline-block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  padding: 2px 8px;
  border: 1px solid var(--rule);
  border-radius: 999px;
}
.wiki-date { font-size: 12px; color: var(--muted); }
.wiki-actions { display: flex; gap: 6px; }
.wiki-btn {
  padding: 6px 12px;
  border: 1px solid var(--rule-strong);
  background: var(--surface);
  border-radius: var(--radius);
  font: inherit;
  font-size: 13px;
  color: var(--ink);
  cursor: pointer;
}
.wiki-btn:hover { background: var(--hover); }
.wiki-btn-primary {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--surface);
}
.wiki-btn-primary:hover { background: var(--ink-soft); border-color: var(--ink-soft); }
.wiki-btn-danger { color: var(--danger); border-color: var(--rule-strong); }
.wiki-btn-danger:hover { background: var(--hover); }

.wiki-title {
  font-family: var(--serif);
  font-size: 44px;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin: 0 0 24px;
  font-weight: 400;
}

.wiki-body {
  font-size: 15px;
  line-height: 1.7;
  color: var(--ink);
}
.wiki-body h1, .wiki-body h2, .wiki-body h3,
.wiki-body h4, .wiki-body h5, .wiki-body h6 {
  font-family: var(--serif);
  color: var(--ink);
  font-weight: 400;
  line-height: 1.25;
  margin: 28px 0 10px;
  letter-spacing: -0.01em;
}
.wiki-body h1 { font-size: 30px; }
.wiki-body h2 { font-size: 24px; }
.wiki-body h3 { font-size: 19px; }
.wiki-body h4 { font-size: 16px; }
.wiki-body p { margin: 10px 0; }
.wiki-body img, .wiki-quill .ql-editor img {
  max-width: 100%; height: auto;
  border-radius: var(--radius-md);
  display: block; margin: 14px 0;
}

/* ── Free photo board (view mode) ── */
.wiki-body .wiki-board { position: relative; width: 100%; max-width: 100%; margin: 18px 0; }
.wiki-body .wiki-board img {
  position: absolute; margin: 0; max-width: none; height: auto;
  border-radius: var(--radius-md);
}

/* ── Free photo board (editor) ── */
.wiki-body ul, .wiki-body ol { margin: 10px 0 10px 22px; }
.wiki-body li { margin: 4px 0; }
.wiki-body a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.wiki-body strong { font-weight: 600; }
.wiki-body em { font-style: italic; }
.wiki-body code {
  font-family: var(--mono);
  font-size: 0.88em;
  background: var(--surface-2);
  padding: 1px 5px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--rule);
}
.wiki-body pre {
  background: var(--surface-2);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 12px 14px;
  overflow-x: auto;
  margin: 14px 0;
}
.wiki-body pre code {
  background: transparent;
  border: 0;
  padding: 0;
  font-size: 13px;
}
.wiki-body blockquote {
  border-left: 3px solid var(--rule-strong);
  padding: 4px 0 4px 14px;
  margin: 14px 0;
  color: var(--ink-soft);
  font-style: italic;
}
.wiki-body hr {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: 24px 0;
}
.wiki-body img { max-width: 100%; border-radius: var(--radius); }

/* Editor */
.wiki-editor .wiki-input {
  width: 100%;
  border: 1px solid var(--rule-strong);
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--radius);
  padding: 8px 12px;
  font: inherit;
  font-size: 14px;
  outline: none;
}
.wiki-editor .wiki-input:focus { border-color: var(--ink); }
.wiki-cat-select { width: auto; min-width: 160px; padding: 6px 10px; font-size: 13px; }
.wiki-title-input {
  font-family: var(--serif);
  font-size: 28px;
  padding: 10px 12px;
  margin-bottom: 14px;
}
.wiki-content-input {
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.6;
  min-height: 460px;
  resize: vertical;
}
.wiki-hint {
  margin-top: 10px;
  font-size: 12px;
  color: var(--muted);
}

/* ── Wiki: list head with two buttons ── */
.wiki-new-btn-secondary {
  background: var(--surface, #fff);
  color: var(--ink);
  border: 1px solid var(--rule-strong);
}
.wiki-new-btn-secondary:hover { background: var(--hover); }
.wiki-cat-lock { font-size: 11px; opacity: 0.6; }

/* ── Wiki sections manager modal ── */
.wiki-sections-overlay {
  display: none;
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.4);
  z-index: 500;
  align-items: flex-start; justify-content: center;
  padding: 60px 16px 24px;
  overflow-y: auto;
}
.wiki-sections-overlay.open { display: flex; }
.wiki-sections-modal {
  width: 100%; max-width: 640px;
  background: var(--surface, #fff);
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: 12px;
  box-shadow: 0 16px 48px rgba(0,0,0,0.28);
  padding: 22px 24px 20px;
}
.wiki-sec-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 14px;
}
.wiki-sec-title { font-size: 18px; font-weight: 600; }
.wiki-sec-list { display: flex; flex-direction: column; gap: 8px; }
.wiki-sec-row {
  display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: 12px;
  padding: 10px 12px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface-2, var(--surface));
}
.wiki-sec-row-editing {
  display: block;
  border-color: var(--rule-strong);
}
.wiki-sec-label-strong { font-weight: 600; margin-bottom: 10px; }
.wiki-sec-label { font-weight: 500; }
.wiki-sec-mode { font-size: 12px; color: var(--muted); }
.wiki-sec-actions { display: flex; gap: 6px; }
.wiki-sec-lock { opacity: 0.6; }
.wiki-sec-editor { display: flex; flex-direction: column; gap: 12px; }
.wiki-sec-field label {
  display: block; font-size: 12px; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 6px;
}
.wiki-sec-vis { display: flex; flex-direction: column; gap: 6px; font-size: 14px; }
.wiki-sec-vis label { display: inline-flex; align-items: center; gap: 8px; text-transform: none; letter-spacing: 0; color: var(--ink); margin: 0; }
.wiki-sec-members {
  display: flex; flex-direction: column; gap: 4px;
  max-height: 220px; overflow-y: auto;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 8px;
  background: var(--surface);
}
.wiki-sec-member {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 6px; border-radius: var(--radius);
  font-size: 13px; color: var(--ink);
  cursor: pointer;
}
.wiki-sec-member:hover { background: var(--hover); }
.wiki-sec-member-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.wiki-sec-empty { color: var(--muted); font-size: 12px; padding: 6px; }
.wiki-sec-actions-row { display: flex; justify-content: flex-end; gap: 6px; }
.wiki-sec-footer { margin-top: 14px; }
.wiki-sec-new { width: 100%; }

/* ── Quill WYSIWYG (Wiki editor) ── */
.wiki-quill { background: var(--surface); position: relative; }

/* draggable images inside the editor */
.wiki-quill .ql-editor img { cursor: grab; }
.wiki-quill .ql-editor img:active { cursor: grabbing; }
/* File links are movable too — same grab affordance as photos. */
.wiki-quill .ql-editor a[href^="/uploads/"] { cursor: grab; }
.wiki-quill .ql-editor .wiki-dragging { opacity: .4; }
body.wiki-drag-move, body.wiki-drag-move * { cursor: grabbing !important; user-select: none !important; }
/* Where the photo/file will land */
.wiki-drop-caret {
  position: absolute; z-index: 25;
  height: 2px; border-radius: 2px;
  background: var(--accent);
  pointer-events: none;
  box-shadow: 0 0 0 1px rgba(255,255,255,.5);
}

/* image resize frame */
.wiki-img-frame {
  position: absolute; z-index: 20;
  border: 2px solid var(--accent);
  border-radius: var(--radius-sm);
  pointer-events: none;
  box-shadow: 0 0 0 1px rgba(255,255,255,.6);
}
.wiki-img-handle {
  position: absolute; right: -7px; bottom: -7px;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--bg);
  cursor: nwse-resize; pointer-events: auto;
}
.wiki-img-size {
  position: absolute; left: 0; top: -24px;
  background: var(--ink); color: var(--bg);
  font-size: 11px; font-weight: 600;
  padding: 2px 7px; border-radius: 6px;
  font-variant-numeric: tabular-nums;
}
/* alignment toolbar on the selected image */
.wiki-img-tools {
  position: absolute; top: -30px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 2px; pointer-events: auto;
  background: var(--ink); border-radius: 8px; padding: 3px;
  box-shadow: var(--shadow-menu);
}
.wiki-img-tools button {
  width: 26px; height: 22px; border: none; border-radius: 5px;
  background: transparent; color: var(--bg); cursor: pointer;
  font-size: 12px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.wiki-img-tools button:hover { background: rgba(255,255,255,0.18); }
.wiki-img-tools button.on { background: var(--accent); color: #fff; }

/* image float alignment (editor + reader) */
.wiki-body img.wiki-img-left,
.wiki-quill .ql-editor img.wiki-img-left {
  float: left; margin: 6px 18px 10px 0; max-width: 55%;
}
.wiki-body img.wiki-img-right,
.wiki-quill .ql-editor img.wiki-img-right {
  float: right; margin: 6px 0 10px 18px; max-width: 55%;
}
.wiki-body img.wiki-img-center,
.wiki-quill .ql-editor img.wiki-img-center {
  display: block; float: none; margin: 14px auto;
}
/* keep floats from bleeding past the article / onto the board */
.wiki-body::after, .wiki-quill .ql-editor::after { content: ""; display: block; clear: both; }
.wiki-body .wiki-board { clear: both; }

/* free (absolute) image positioning over the text */
.wiki-quill .ql-editor, .wiki-body { position: relative; }
.wiki-body img.wiki-img-free,
.wiki-quill .ql-editor img.wiki-img-free {
  position: absolute; float: none; z-index: 3; margin: 0;
}
.wiki-quill .ql-editor img.wiki-img-free { cursor: move; }
.wiki-quill .ql-toolbar {
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius) var(--radius) 0 0;
  border-bottom: 0;
  background: var(--surface-2, var(--surface));
}
.wiki-quill .ql-container {
  border: 1px solid var(--rule-strong);
  border-radius: 0 0 var(--radius) var(--radius);
  font-family: inherit;
  font-size: 15px;
  min-height: 420px;
  background: var(--surface);
  color: var(--ink);
}
/* Left padding leaves room for the block handle to sit outside the text. */
.wiki-quill .ql-editor { min-height: 420px; line-height: 1.6; padding: 16px 18px 16px 34px; }
.wiki-blk-handle {
  position: absolute; z-index: 15;
  width: 18px; height: 20px;
  display: none; align-items: center; justify-content: center;
  border: 0; padding: 0;
  background: transparent;
  color: var(--muted);
  font-size: 12px; line-height: 1; letter-spacing: -1px;
  border-radius: 3px;
  cursor: grab;
  opacity: .5;
  transition: opacity .1s, background .1s, color .1s;
}
.wiki-blk-handle:hover { opacity: 1; background: var(--rule); color: var(--ink); }
.wiki-blk-handle:active { cursor: grabbing; }
.wiki-quill .ql-editor .wiki-blk-dragging { opacity: .4; }
.wiki-quill .ql-editor.ql-blank::before { color: var(--muted); font-style: normal; }
.wiki-quill .ql-stroke { stroke: var(--ink); }
.wiki-quill .ql-fill   { fill: var(--ink); }
.wiki-quill .ql-picker { color: var(--ink); }
.wiki-quill .ql-picker-options { background: var(--surface); color: var(--ink); border: 1px solid var(--rule); }
.wiki-quill .ql-snow.ql-toolbar button:hover .ql-stroke,
.wiki-quill .ql-snow.ql-toolbar button.ql-active .ql-stroke { stroke: var(--accent, #2563EB); }
.wiki-quill .ql-snow.ql-toolbar button:hover .ql-fill,
.wiki-quill .ql-snow.ql-toolbar button.ql-active .ql-fill { fill: var(--accent, #2563EB); }
.wiki-quill .ql-editor blockquote {
  border-left: 3px solid var(--rule-strong);
  padding-left: 12px;
  color: var(--muted);
  margin: 1em 0;
}
.wiki-quill .ql-editor pre,
.wiki-quill .ql-editor .ql-code-block-container {
  background: var(--hover);
  color: var(--ink);
  border-radius: var(--radius);
  padding: 10px 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* Render wiki body the same way both views see headings/lists/etc */
.wiki-body img { max-width: 100%; height: auto; border-radius: var(--radius); }
.wiki-body blockquote {
  border-left: 3px solid var(--rule-strong);
  padding-left: 12px;
  color: var(--muted);
  margin: 1em 0;
}

.wiki-mobile-close { display: none; }

@media (max-width: 720px) {
  /* Master-detail: one pane fills the screen at a time. */
  .wiki-container { grid-template-columns: 1fr; grid-auto-rows: 100%; }
  .wiki-list { max-height: none; border-right: none; }
  .wiki-detail { display: none; padding: 20px 20px 48px; position: relative; }
  .wiki-container.show-detail .wiki-list { display: none; }
  .wiki-container.show-detail .wiki-detail { display: block; }
  .wiki-title { font-size: 28px; }
  .wiki-head { flex-direction: column; align-items: flex-start; }
  .wiki-actions { flex-wrap: wrap; }
  .wiki-detail-inner { position: relative; }
  .wiki-mobile-close {
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 0 14px auto;
    width: 34px;
    height: 34px;
    border: 1px solid var(--rule-strong);
    border-radius: 50%;
    background: var(--surface);
    color: var(--muted);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
  }
  .wiki-mobile-close:active { background: var(--surface-2); }

  /* Editor: pin Отмена / Сохранить to a bottom bar instead of the top */
  .wiki-editor { padding-bottom: 76px; }
  .wiki-editor .wiki-actions {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 80;
    display: flex; flex-wrap: nowrap; gap: 10px;
    padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
    background: var(--bg); border-top: 1px solid var(--rule);
    box-shadow: 0 -4px 16px rgba(0,0,0,0.06);
  }
  .wiki-editor .wiki-actions .wiki-btn {
    flex: 1; display: flex; align-items: center; justify-content: center;
    height: 42px; font-size: 15px;
  }
  /* Chat launcher would sit over the Сохранить button while editing */
  body.wiki-editing .chat-fab { display: none; }
}

/* ══ MODELS DB — applications button + queue modal ══ */
.mdb-list-head { display: none; }
/* Компактные, в один рост с переключателем вида: три крупные кнопки с эмодзи
   весили в тулбаре больше самого списка. */
.mdb-apps-btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--radius-sm, 4px);
  padding: 3px 10px; font: inherit; font-size: 12px; font-weight: 500;
  cursor: pointer; transition: background 120ms, border-color 120ms;
  height: 26px; line-height: 1;
}
.mdb-apps-btn:hover { background: var(--hover); border-color: var(--ink); }
#mdb-apps-slot { display: inline-flex; align-items: center; gap: 8px; }
/* primary "add model" button */
.mdb-add-btn { background: var(--ink); color: var(--bg); border-color: var(--ink); }
.mdb-add-btn:hover { background: var(--ink-soft, #5A584F); border-color: var(--ink-soft, #5A584F); }

/* «…» тулбара: заявки, импорт и ссылка на анкету (issue #300). Квадратная и в
   один рост с соседкой — подписи у неё нет, и без явной ширины кнопка была бы
   уже точек, которые в ней нарисованы. */
.mdb-more-wrap { position: relative; display: flex; }
.mdb-more-btn { padding: 0 8px; gap: 4px; color: var(--muted, #6B6862); }
.mdb-more-btn svg { width: 16px; height: 16px; fill: currentColor; }
.mdb-more-btn:hover { color: var(--ink, #37352F); }
/* Правым краем под кнопку: «…» стоит предпоследней у самого края окна, и меню,
   растущее вправо, уезжало бы за него вместе с половиной подписей. */
.mdb-more-menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 30;
  min-width: 190px;
  padding: 6px;
  background: var(--surface, #fff);
  border: 1px solid var(--rule, rgba(55,53,47,.12));
  border-radius: 8px;
  box-shadow: 0 8px 28px rgba(0,0,0,.13);
}
.mdb-more-menu.hidden { display: none; }
.mdb-more-item {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%;
  padding: 7px 8px; border: 0; border-radius: 5px;
  background: transparent; color: var(--ink, #37352F);
  font: inherit; font-size: 13px; text-align: left;
  cursor: pointer; white-space: nowrap;
}
.mdb-more-item:hover { background: var(--hover, #F7F6F3); }
.mdb-more-count {
  background: var(--ink); color: var(--surface);
  border-radius: 999px; padding: 1px 7px;
  font-size: 11px; font-weight: 700;
}
/* Пустой счётчик не занимает места: пункт «Заявки» без ожидающих анкет обязан
   выглядеть как два соседних, а не как пункт с оторванным хвостом. */
.mdb-more-count:empty { display: none; }
.mdb-apps-ico { font-size: 14px; }
.mdb-apps-badge {
  display: none; margin-left: auto;
  background: var(--ink); color: var(--surface);
  border-radius: 999px; padding: 2px 8px;
  font-size: 11px; font-weight: 700; min-width: 18px;
  align-items: center; justify-content: center;
}

/* Apps modal */
.apps-overlay {
  position: fixed; inset: 0;
  background: rgba(20, 18, 14, 0.45);
  display: none; align-items: flex-start; justify-content: center;
  padding: 60px 20px 40px; overflow-y: auto;
  z-index: 1500;
}
.apps-overlay.open { display: flex; }
.apps-modal {
  background: var(--surface); border-radius: 12px;
  max-width: 820px; width: 100%;
  box-shadow: 0 20px 60px rgba(0,0,0,0.25);
  border: 1px solid var(--rule);
  padding: 24px 26px 28px;
}
.apps-h {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 14px;
}
.apps-h h2 {
  font-family: 'DM Serif Display', serif; font-weight: 400; font-size: 26px;
  margin: 0; letter-spacing: -0.01em;
}
.apps-close {
  background: transparent; border: 0; font-size: 20px; line-height: 1;
  cursor: pointer; color: var(--muted); padding: 4px 8px; border-radius: var(--radius);
}
.apps-close:hover { background: var(--hover); color: var(--ink); }

.apps-section { margin-top: 22px; }
.apps-section-h { display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px; }
.apps-section-h h3 {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--muted); font-weight: 600; margin: 0;
}
.apps-count {
  background: var(--rule); color: var(--ink);
  font-size: 11px; font-weight: 700;
  border-radius: 999px; padding: 1px 8px;
}

/* `--paper` — токен из apply.html, публичной анкеты со своим набором цветов;
   здесь его не объявляет никто, поэтому `background` не применялся вовсе, а не
   брал что-то похожее. */
.apps-invite-form {
  display: flex; gap: 10px;
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--radius); padding: 10px;
}
.apps-input {
  font: inherit; font-size: 14px;
  border: 1px solid var(--rule); background: var(--surface); color: var(--ink);
  border-radius: var(--radius); padding: 8px 10px; width: 100%;
  transition: border-color 120ms;
}
.apps-input:focus { outline: none; border-color: var(--ink); }
.apps-url-input { font-family: 'JetBrains Mono', monospace; font-size: 12px; margin-top: 8px; }

.apps-btn {
  font: inherit; font-size: 13px; font-weight: 500;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 7px 12px; cursor: pointer; white-space: nowrap;
  transition: background 120ms, border-color 120ms;
}
.apps-btn:hover { background: var(--hover); border-color: var(--ink); }
.apps-btn-primary { background: var(--ink); color: var(--surface); border-color: var(--ink); }
.apps-btn-primary:hover { background: #000; }
.apps-btn-danger { color: var(--danger, #B05050); }
.apps-btn-danger:hover { background: rgba(176,80,80,0.08); border-color: var(--danger, #B05050); }

.apps-card {
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--radius); padding: 12px 14px;
  margin-bottom: 10px;
}
.apps-card-pending { border-color: var(--ink); }
.apps-card-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; flex-wrap: wrap;
}
.apps-card-l { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.apps-card-meta { font-size: 12px; color: var(--muted); }
.apps-card-actions { display: flex; gap: 8px; }
.apps-card-foot { margin-top: 10px; font-size: 12px; }
.apps-card-foot a { color: var(--muted); text-decoration: none; }
.apps-card-foot a:hover { color: var(--ink); text-decoration: underline; }

.apps-status {
  display: inline-flex; align-items: center;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
  padding: 3px 8px; border-radius: 999px;
  background: var(--rule); color: var(--ink);
}
.apps-st-invited   { background: #E8E5DE; color: #6B6862; }
.apps-st-submitted { background: #FFEDC9; color: #8A5A00; }
.apps-st-accepted  { background: #DCEFD6; color: #2D6A2D; }
.apps-st-rejected  { background: #F4D9D5; color: #8A2A2A; }

.apps-dl {
  margin-top: 12px;
  display: grid; grid-template-columns: 160px 1fr; gap: 4px 14px;
  font-size: 13px;
}
.apps-dl-k { color: var(--muted); }
.apps-dl-v { color: var(--ink); word-break: break-word; }

.apps-empty {
  font-size: 13px; color: var(--muted-soft);
  padding: 14px 0;
}

.apps-toast {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%) translateY(20px);
  background: var(--ink); color: var(--surface);
  font-size: 13px; padding: 10px 16px; border-radius: var(--radius);
  opacity: 0; pointer-events: none; z-index: 2000;
  transition: opacity 200ms, transform 200ms;
}
.apps-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

@media (max-width: 640px) {
  .apps-invite-form { flex-direction: column; }
  .apps-dl { grid-template-columns: 1fr; }
  .apps-dl-k { font-weight: 600; }
}

/* Apps: admin language toggle for new invite */
.apps-lang-toggle {
  display: inline-flex; background: var(--surface); border: 1px solid var(--rule);
  border-radius: 999px; padding: 2px; align-items: center; flex-shrink: 0;
}
.apps-lang-toggle button {
  border: 0; background: transparent; font: inherit; font-size: 12px; font-weight: 600;
  padding: 5px 12px; border-radius: 999px; color: var(--muted); cursor: pointer;
  letter-spacing: 0.04em; transition: background 120ms, color 120ms;
}
.apps-lang-toggle button.active { background: var(--ink); color: var(--surface); }

/* ── Model media library ── */
.mdb-media { margin-top: 22px; }
.mdb-media.mdb-drop { outline: 2px dashed #C8A882; outline-offset: 4px; border-radius: 8px; }
/* Ход загрузки пачки фотографий — в шапке медиатеки, между «МЕДИА (10)» и
   кнопкой «Загрузить» (issue #294). Полоска занимает всё, что осталось между
   ними, потому что мерить ей нечего: ширина здесь — это просто расстояние от
   заголовка до кнопки.

   Она бледнее и тоньше подписи рядом: `--muted-soft` против `--muted` у
   `.mdb-books-h`, три пикселя против строки текста. Проценты по ней никто не
   считает — читают «3 из 10», а полоска только говорит, что счёт идёт. Тот же
   порядок, что и у `--sb-count` в сайдбаре: число тише имени, а рисунок тише
   числа. */
.mdb-media-up { display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0; }
.mdb-media-up-track {
  flex: 1 1 auto; min-width: 0; height: 3px; border-radius: 2px; overflow: hidden;
  background: var(--rule-strong, rgba(55,53,47,0.16));
}
.mdb-media-up-fill {
  display: block; height: 100%; width: 0;
  background: var(--muted-soft, #BFBEB7); transition: width 160ms ease-out;
}
.mdb-media-up-num {
  flex: 0 0 auto; font-size: 11px; color: var(--muted, #9B9A93);
  font-variant-numeric: tabular-nums;
}
.mdb-media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: 10px; margin-top: 10px; }
.mdb-media-cell { position: relative; aspect-ratio: 3/4; border-radius: 6px; overflow: hidden; background: color-mix(in oklch, var(--ink) 6%, var(--bg)); cursor: zoom-in; }
.mdb-media-cell img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mdb-media-acts { position: absolute; top: 4px; right: 4px; display: none; gap: 4px; }
.mdb-media-cell:hover .mdb-media-acts { display: flex; }
.mdb-media-acts .mdb-icon-btn { background: color-mix(in oklch, var(--bg) 92%, transparent); }

/* «Показать все N фото» — полоса во всю ширину под превью (issue #298), а не
   ссылка сбоку: она стоит там, где обрывается сетка, и обрыв объясняет. Число
   в подписи — половина её смысла: девять кадров на карточке выглядят одинаково
   и у модели с десятком снимков, и у модели с полусотней. */
.mdb-media-all-btn {
  display: block; width: 100%; margin-top: 10px; padding: 9px 12px;
  border: 1px dashed var(--rule-strong, #E9E8E5); border-radius: var(--radius, 8px);
  background: transparent; color: var(--ink, #37352F); font: inherit; font-size: 13px;
  cursor: pointer; transition: background 120ms, border-color 120ms;
}
.mdb-media-all-btn:hover { background: var(--hover, #EFEEEC); border-color: var(--accent, #C8A882); }
/* Сетка в окне держит тот же размер ячейки, что и в карточке, — шире окно, не
   снимки: увеличенные заодно с окном, они дали бы третий размер миниатюры на
   одном экране. Прокручивается сетка, а не окно: шапка с числом кадров
   остаётся на месте, как в пикере фотографий для бука. */
.mdb-media-all-grid { margin-top: 0; padding: 14px 16px; overflow-y: auto; grid-auto-rows: min-content; }

/* Видео модели. Плеер родной, браузерный: свои кнопки означали бы свой
   полноэкранный режим, свою перемотку и свой айфон — а нативные controls уже
   умеют и картинку-в-картинке, и скорость, и жесты. Размер задаётся сеткой,
   пропорции — самим файлом (проходка вертикальная, интро горизонтальное). */
.mdb-vid-head { margin-top: 24px; }
.mdb-vids { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; margin-top: 10px; }
.mdb-vid { border: 1px solid var(--rule); border-radius: var(--radius); overflow: hidden; background: var(--surface-2); }
.mdb-vid-el { width: 100%; max-height: 420px; display: block; background: #000; }
.mdb-vid-fail { padding: 14px 16px; font-size: 13px; color: var(--muted); }
.mdb-vid-bar { display: flex; align-items: center; gap: 6px; padding: 8px 10px; border-top: 1px solid var(--rule); }
.mdb-vid-name { flex: 1; min-width: 0; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mdb-vid-hint { margin-top: 8px; font-size: 12px; color: var(--muted); line-height: 1.5; }
.mdb-vpick-list { padding: 10px 12px; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; }
.mdb-vpick-row { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border: 1px solid var(--rule); border-radius: 8px; cursor: pointer; }
.mdb-vpick-row.sel { border-color: #C8A882; background: color-mix(in oklch, #C8A882 12%, var(--surface)); }
.mdb-vpick-row.used { opacity: .5; cursor: default; }
.mdb-vpick-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mdb-vpick-size { font-size: 12px; color: var(--muted); }

/* Upload progress toast */
.mdb-toast { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: var(--ink); color: var(--bg); padding: 8px 16px; border-radius: 8px; font-size: 13px; z-index: 4000; box-shadow: 0 6px 20px rgba(0,0,0,.2); }

/* Lightbox */
.mdb-lb { position: fixed; inset: 0; background: rgba(0,0,0,.88); display: none; align-items: center; justify-content: center; z-index: 4100; }
.mdb-lb.open { display: flex; }
.mdb-lb-img { max-width: 92vw; max-height: 90vh; object-fit: contain; border-radius: 6px; }
.mdb-lb-x, .mdb-lb-prev, .mdb-lb-next { position: absolute; background: none; border: none; color: #fff; font-size: 28px; cursor: pointer; padding: 10px 16px; opacity: .8; z-index: 1; }
.mdb-lb-x:hover, .mdb-lb-prev:hover, .mdb-lb-next:hover { opacity: 1; }
.mdb-lb-x { top: 8px; right: 12px; }
.mdb-lb-prev { left: 4px; top: 50%; transform: translateY(-50%); font-size: 44px; }
.mdb-lb-next { right: 4px; top: 50%; transform: translateY(-50%); font-size: 44px; }
.mdb-lb-count { position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%); color: #fff; font-size: 13px; opacity: .8; }

/* Photo picker overlay */
.mdb-ovl { position: fixed; inset: 0; background: rgba(15,15,15,.30); display: flex; align-items: center; justify-content: center; z-index: 4050; padding: 36px; }
.mdb-ovl-card { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius); width: min(720px, 100%); max-height: 86vh; display: flex; flex-direction: column; box-shadow: var(--shadow-modal); animation: rise .18s ease; }
.mdb-ovl-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 16px 20px; font-size: 18px; font-weight: 700; border-bottom: 1px solid var(--rule); }
.mdb-ovl-foot { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 20px; border-top: 1px solid var(--rule); background: var(--surface-2); }
/* Высота ряда задана явно, а не оставлена на `auto` (issue #312). Ячейка здесь
   скролл-контейнер — `overflow: hidden` ради скруглённых углов, — а такой бокс
   не отдаёт в расчёт ряда высоту, которую ему даёт `aspect-ratio`. Ряды выходили
   нулевыми и разбирали поровну ту высоту, до которой флексбокс ужал сетку под
   `max-height` карточки: 62 пикселя вместо 144. Сами ячейки при этом оставались
   4/3 от своей ширины и рисовались поверх нижних — фото ложились одно на другое,
   и видно было по полоске от каждого. `min-content` считает ряд по ячейке,
   сетка перерастает карточку и листается, как ей и положено. */
.mdb-pick-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); grid-auto-rows: min-content; gap: 8px; padding: 14px 16px; overflow-y: auto; }
.mdb-pick-cell { position: relative; aspect-ratio: 3/4; border-radius: 6px; overflow: hidden; cursor: pointer; outline: 2px solid transparent; }
.mdb-pick-cell img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mdb-pick-cell.sel { outline-color: #C8A882; }
.mdb-pick-cell.used { opacity: .45; cursor: default; }
.mdb-pick-mark { position: absolute; top: 4px; left: 4px; background: color-mix(in oklch, var(--bg) 92%, transparent); border-radius: 6px; font-size: 11px; padding: 2px 6px; }
.mdb-book-thumb img { cursor: zoom-in; }
.mdb-book-thumb[draggable="true"] { cursor: grab; }

/* Small confirm/prompt dialog (native dialogs are blocked in embedded browsers) */
.mdb-ovl-sm { width: min(420px, 100%); }
/* Окно медиатеки шире остальных: 720 пикселей это пять колонок снимков — на
   полтора ряда больше, чем в карточке, и ради полутора рядов окно не
   открывают. Ограничение всё равно есть, потому что сетка во весь широкий
   монитор — это одна строка фотографий и километр пустоты под ней. */
.mdb-ovl-wide { width: min(1080px, 100%); }
.mdb-ovl-body { padding: 18px 16px 6px; font-size: 14px; line-height: 1.5; white-space: pre-line; }
.mdb-ovl-input { width: 100%; margin-top: 10px; }

/* Model panel tabs: Инфо | Медиа | Буки */
/* Линия под вкладками — внутренней тенью, а не `border-bottom`, и это не
   вкусовщина. Активная вкладка подчёркивается своей рамкой в два пикселя на том
   же самом месте, и заезжала она на общую линию отрицательным `margin-bottom`.
   Этот минус пиксель торчал за коробкой ряда — а ряд прокручиваемый
   (`overflow-x: auto`: вкладок пять, и в узкую панель они не всегда помещаются),
   и по правилам CSS `overflow-y` у него от этого тоже становится прокруткой.
   Ради одного лишнего пикселя ряд вкладок отращивал собственную вертикальную
   полосу высотой в самого себя — коротенький бегунок сбоку от «Инфо · Медиа ·
   Буки», который и обвели в issue #273.

   Тень рисуется внутри коробки и ничего не переполняет: подчёркивание встаёт
   ровно на линию, а полосе прокрутки нечего показывать. Горизонтальная при этом
   осталась работать, но не рисуется — то же, что у ряда чипов в шапке. */
.mdb-tabs {
  display: flex; gap: 10px; margin: 20px 0 20px;
  box-shadow: inset 0 -1px 0 var(--rule);
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
}
.mdb-tabs::-webkit-scrollbar { display: none; }
.mdb-tab { background: none; border: none; padding: 8px 10px 10px; font-size: 14px; cursor: pointer; color: var(--text); opacity: .55; border-bottom: 2px solid transparent; font-family: inherit; white-space: nowrap; }
.mdb-tab:hover { opacity: .85; }
.mdb-tab.active { opacity: 1; font-weight: 600; border-bottom-color: var(--ink); }

/* ══ Документооборот (Docs / Finance) ══ */
/* Финансы и Документооборот — два отдельных экрана с одинаковой рамкой. */
.docs-container,
/* Гейтер — по той же причине, что у `.mdb-wrap` выше: шапка одна на все экраны. */
.fin-container { flex: 1; min-height: 0; overflow-y: auto; scrollbar-gutter: stable; padding: 8px var(--pad-x, 32px) 64px; }
.docs-head { margin-bottom: 8px; }
.docs-title { font-size: 22px; font-weight: 700; margin: 4px 0 12px; }
.docs-tabs { margin-top: 0; }
.docs-body { margin-top: 8px; }
.docs-loading, .docs-empty { color: var(--muted, #999); padding: 24px 4px; font-size: 14px; }
.docs-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 14px 0; }
.docs-filter { max-width: 200px; }
.docs-warn { background: color-mix(in oklch, #E5A50A 18%, var(--bg)); border: 1px solid color-mix(in oklch, #E5A50A 45%, transparent); color: var(--text); padding: 10px 14px; border-radius: 8px; font-size: 13px; margin-bottom: 12px; }

/* Finance summary cards */
.fin-cards { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 4px; }
.fin-card { background: color-mix(in oklch, var(--ink) 4%, var(--bg)); border: 1px solid color-mix(in oklch, var(--ink) 10%, transparent); border-radius: 10px; padding: 12px 16px; min-width: 190px; }
.fin-card-cur { font-size: 12px; font-weight: 700; letter-spacing: .04em; color: var(--muted, #999); margin-bottom: 6px; }
.fin-row { display: flex; justify-content: space-between; gap: 16px; font-size: 13px; padding: 2px 0; }
.fin-row.fin-total { border-top: 1px solid color-mix(in oklch, var(--ink) 12%, transparent); margin-top: 4px; padding-top: 6px; font-weight: 600; }
.fin-pos { color: #16A34A; }
.fin-neg { color: #DC2626; }

/* Счёт модели: «сколько мы должны человеку» рядом с «сколько человек принёс
   агентству». Рамка, фон и заголовок нужны не для красоты — без них два блока
   карточек с крупным итогом внизу читаются как один, и читают верхний. */
.fin-ledger { margin: 18px 0 4px; padding: 14px 16px; border-radius: 12px;
  border: 1px solid color-mix(in oklch, var(--ink) 14%, transparent);
  background: color-mix(in oklch, var(--ink) 2%, var(--bg)); }
.fin-ledger-head { display: flex; align-items: center; gap: 12px; margin-bottom: 4px; }
.fin-ledger-head .mdb-btn { margin-left: auto; }
.fin-ledger .docs-hint { margin-bottom: 12px; }
.fin-ledger .docs-empty { padding: 8px 4px 12px; }
.fin-card-ledger { background: var(--bg); }

/* Tables (shared by finance + documents) */
.docs-table-wrap { overflow-x: auto; border: 1px solid color-mix(in oklch, var(--ink) 10%, transparent); border-radius: 10px; }
.docs-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.docs-table th {
  text-align: left; font-weight: 600;
  font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted, #999); background: var(--surface-2);
  padding: 9px 12px; border-bottom: 1px solid var(--rule); white-space: nowrap;
}
.docs-table td { padding: 10px 12px; border-bottom: 1px solid color-mix(in oklch, var(--ink) 7%, transparent); vertical-align: top; }
.docs-table tr:last-child td { border-bottom: none; }
.fin-td-date, .fin-td-amt { white-space: nowrap; }
.fin-td-amt { font-weight: 600; font-variant-numeric: tabular-nums; }
.fin-td-note { color: var(--muted, #999); max-width: 240px; }
.fin-kind { display: inline-block; padding: 2px 8px; border-radius: 6px; font-size: 12px; font-weight: 500; }
.fin-kind-income { background: color-mix(in oklch, #16A34A 16%, var(--bg)); color: #16A34A; }
.fin-kind-expense { background: color-mix(in oklch, #DC2626 14%, var(--bg)); color: #DC2626; }
.fin-kind-payout { background: color-mix(in oklch, #E5A50A 18%, var(--bg)); color: #B45309; }
.doc-cat { display: inline-block; padding: 2px 8px; border-radius: 6px; font-size: 12px; background: color-mix(in oklch, var(--ink) 8%, var(--bg)); }
.doc-note { color: var(--muted, #999); font-size: 12px; margin-top: 2px; }
.doc-exp-soon { color: #B45309; font-weight: 600; }
.doc-exp-over { color: #DC2626; font-weight: 600; }

/* Modal form rows */
.docs-form { padding: 14px 16px; display: grid; gap: 10px; max-height: 70vh; overflow-y: auto; }
.docs-f { display: grid; grid-template-columns: 120px 1fr; align-items: center; gap: 10px; font-size: 13px; }
.docs-f > span:first-child { color: var(--muted, #999); }
.docs-f .mdb-input { width: 100%; }
.doc-file-pick { display: flex; align-items: center; gap: 8px; }
.doc-file-name { font-size: 12px; color: var(--muted, #999); }

/* Model-page docs tab reuses .mdb-docs container */
.mdb-docs { margin-top: 16px; }

/* Model-page "Поездки" tab: one card per season, conditions above what it
   actually brought in. The two are separated by the card border on purpose —
   a guarantee is a promise, not money that arrived. */
.mdb-placements { margin-top: 16px; }
.pl-card {
  border: 1px solid color-mix(in oklch, var(--ink) 12%, transparent);
  border-radius: 12px; padding: 14px 16px; margin-bottom: 12px;
}
.pl-head { display: flex; align-items: flex-start; gap: 12px; }
.pl-head > div:first-child { flex: 1; min-width: 0; }
.pl-title { font-size: 15px; font-weight: 600; }
.pl-where { font-weight: 400; color: var(--muted, #999); }
.pl-line { font-size: 13px; margin-top: 6px; }
.pl-line > span { color: var(--muted, #999); margin-right: 6px; }
.pl-nomoney { font-size: 12px; color: var(--muted, #999); margin-top: 10px; }

/* Finance commission extras */
.fin-comm-preview { font-weight: 600; font-variant-numeric: tabular-nums; }
.fin-td-rate { white-space: nowrap; color: var(--muted, #999); }

/* Finance analytics: period control, KPIs, month bars, model table */
.fin-period { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 4px; }
.fin-mode { display: inline-flex; gap: 2px; background: color-mix(in oklch, var(--ink) 6%, var(--bg)); border-radius: 8px; padding: 3px; }
.fin-mode-btn { background: none; border: none; padding: 6px 12px; border-radius: 6px; font-size: 13px; cursor: pointer; color: var(--text); font-family: inherit; }
.fin-mode-btn.active { background: var(--bg); font-weight: 600; box-shadow: 0 1px 3px rgba(0,0,0,.08); }
.fin-nav { display: inline-flex; align-items: center; gap: 8px; }
.fin-nav-btn { background: none; border: 1px solid color-mix(in oklch, var(--ink) 14%, transparent); border-radius: 6px; width: 28px; height: 28px; cursor: pointer; color: var(--text); font-size: 16px; line-height: 1; }
.fin-nav-btn:hover { background: color-mix(in oklch, var(--ink) 6%, var(--bg)); }
.fin-nav-label { font-weight: 600; font-size: 14px; min-width: 90px; text-align: center; }

.fin-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin: 14px 0 6px; }
.fin-kpi { background: color-mix(in oklch, var(--ink) 4%, var(--bg)); border: 1px solid color-mix(in oklch, var(--ink) 10%, transparent); border-radius: 10px; padding: 12px 14px; }
.fin-kpi-strong { border-color: color-mix(in oklch, var(--ink) 22%, transparent); }
.fin-kpi-l { font-size: 12px; color: var(--muted, #999); margin-bottom: 4px; }
.fin-kpi-v { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
.fin-base-note { font-size: 12px; color: var(--muted, #999); margin-bottom: 14px; }

.fin-two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: start; }
@media (max-width: 720px) { .fin-two-col { grid-template-columns: 1fr; } }
.fin-block { margin-top: 16px; }
.fin-block-h { font-size: 13px; font-weight: 600; color: var(--muted, #999); margin-bottom: 8px; }
.fin-bars { display: flex; flex-direction: column; gap: 6px; }
.fin-bar-row { display: grid; grid-template-columns: 54px 1fr auto; align-items: center; gap: 8px; font-size: 12px; }
.fin-bar-lbl { color: var(--muted, #999); white-space: nowrap; }
.fin-bar-track { height: 14px; background: color-mix(in oklch, var(--ink) 7%, var(--bg)); border-radius: 4px; overflow: hidden; }
.fin-bar-fill { display: block; height: 100%; background: #16A34A; border-radius: 4px; min-width: 2px; }
.fin-bar-val { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Operating expenses block header */
.fin-exp-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: 22px 0 8px; }

/* ── Планирование поступлений ───────────────────────────────────────────── */

/* Sub-navigation between the finance sections. */
.fin-subnav { display: flex; gap: 2px; margin-bottom: 14px; border-bottom: 1px solid var(--rule, color-mix(in oklch, var(--ink) 12%, transparent)); }
.fin-subnav-btn {
  background: none; border: none; border-bottom: 2px solid transparent;
  padding: 8px 14px; margin-bottom: -1px; font-size: 13px; cursor: pointer;
  color: var(--muted, #999); font-family: inherit;
}
.fin-subnav-btn:hover { color: var(--text); }
.fin-subnav-btn.active { color: var(--text); font-weight: 600; border-bottom-color: var(--text); }

.fin-kpis-cash { margin-top: 4px; }
.fin-link { color: var(--text); text-decoration: underline; cursor: pointer; margin-left: 6px; }

/* Forecast bars: confirmed money sits solid at the left, expected money is
   hatched behind it, so the certain part of a quarter reads at a glance. */
.fin-fc { display: flex; flex-direction: column; gap: 6px; }
.fin-fc-row { display: grid; grid-template-columns: 92px 1fr auto 24px; align-items: center; gap: 10px; font-size: 12px; }
.fin-fc-lbl { color: var(--muted, #999); white-space: nowrap; }
.fin-fc-track { display: flex; height: 16px; background: color-mix(in oklch, var(--ink) 7%, var(--bg)); border-radius: 4px; overflow: hidden; }
.fin-fc-conf { display: block; height: 100%; background: #16A34A; }
.fin-fc-exp { display: block; height: 100%; background: repeating-linear-gradient(135deg, color-mix(in oklch, #16A34A 45%, var(--bg)) 0 5px, color-mix(in oklch, #16A34A 22%, var(--bg)) 5px 10px); }
.fin-fc-val { font-variant-numeric: tabular-nums; white-space: nowrap; font-weight: 600; }
.fin-fc-cnt { color: var(--muted, #999); text-align: right; font-variant-numeric: tabular-nums; }
.fin-fc-over .fin-fc-lbl { color: #DC2626; font-weight: 600; }
.fin-fc-over .fin-fc-conf { background: #DC2626; }
.fin-fc-over .fin-fc-exp { background: repeating-linear-gradient(135deg, color-mix(in oklch, #DC2626 45%, var(--bg)) 0 5px, color-mix(in oklch, #DC2626 22%, var(--bg)) 5px 10px); }

.fin-legend { display: flex; gap: 16px; margin-top: 8px; font-size: 11px; color: var(--muted, #999); }
.fin-legend i { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; }
.fin-dot-conf { background: #16A34A; }
.fin-dot-exp { background: repeating-linear-gradient(135deg, color-mix(in oklch, #16A34A 45%, var(--bg)) 0 4px, color-mix(in oklch, #16A34A 22%, var(--bg)) 4px 8px); }

.fin-rc-sum { font-size: 13px; color: var(--muted, #999); margin-bottom: 8px; }
.fin-rc-sum b { color: var(--text); font-variant-numeric: tabular-nums; }

/* Payment / receivable status chips. */
.fin-chip { display: inline-block; padding: 2px 8px; border-radius: 6px; font-size: 11px; font-weight: 500; white-space: nowrap; }
.fin-st-exp  { background: color-mix(in oklch, var(--ink) 8%, var(--bg)); color: var(--muted, #999); }
.fin-st-conf { background: color-mix(in oklch, #3B82F6 16%, var(--bg)); color: #2563EB; }
.fin-st-part { background: color-mix(in oklch, #E5A50A 20%, var(--bg)); color: #B45309; }
.fin-st-paid { background: color-mix(in oklch, #16A34A 16%, var(--bg)); color: #16A34A; }
.fin-st-off  { background: color-mix(in oklch, #DC2626 12%, var(--bg)); color: #DC2626; }

.fin-tr-over td { background: color-mix(in oklch, #DC2626 5%, transparent); }
.fin-tr-arch { opacity: .55; }
.fin-overdue { font-size: 11px; color: #DC2626; margin-top: 2px; }
.fin-alloc { font-size: 12px; color: var(--muted, #999); }
.fin-td-act { white-space: nowrap; }

/* Marks the overview as fact-only, so the missing work-log money reads as a
   deliberate exclusion rather than a gap. */
.fin-fact-note {
  font-size: 12px; color: var(--muted, #999); line-height: 1.5; margin-bottom: 14px;
  padding-left: 10px; border-left: 2px solid color-mix(in oklch, var(--ink) 16%, transparent);
}
/* Something needs the user's attention but isn't an error. */
.fin-warn-note {
  font-size: 12px; line-height: 1.5; margin: 10px 0 14px; padding: 9px 12px; border-radius: 8px;
  background: color-mix(in oklch, #E5A50A 12%, var(--bg)); color: #92400E;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.fin-warn-note .fin-link { color: inherit; }

/* Forecast figure inside a fact card: visibly set apart, never part of the sum. */
.fin-row-plan {
  margin-top: 6px; padding-top: 6px; opacity: .7;
  border-top: 1px dashed color-mix(in oklch, var(--ink) 18%, transparent);
}

.fin-models { max-width: 220px; }
.fin-model-chip {
  display: inline-block; padding: 1px 7px; margin: 1px 3px 1px 0; border-radius: 5px;
  font-size: 11px; background: color-mix(in oklch, var(--ink) 8%, var(--bg)); white-space: nowrap;
}
.fin-tr-click { cursor: pointer; }
.fin-tr-click:hover td { background: color-mix(in oklch, var(--ink) 4%, transparent); }

.fin-st-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 14px 0 10px; }
.fin-st-title { font-size: 17px; font-weight: 600; display: flex; align-items: center; gap: 9px; }

.fin-recon { font-size: 12px; padding: 8px 12px; border-radius: 8px; margin-bottom: 6px; }
.fin-recon-ok  { background: color-mix(in oklch, #16A34A 10%, var(--bg)); color: #16A34A; }
.fin-recon-bad { background: color-mix(in oklch, #E5A50A 14%, var(--bg)); color: #B45309; }

.docs-hint { font-size: 12px; color: var(--muted, #999); line-height: 1.45; }
.docs-check { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--text); }
.docs-check input { margin: 0; }
.mdb-btn-sm { padding: 4px 10px; font-size: 12px; }

/* Оплата стейтмента в один клик — зелёная, чтобы не путать с ＄ (форма платежа) */
.fin-icon-ok:hover { border-color: var(--ok, #2E7D32); color: var(--ok, #2E7D32); }
.mdb-btn-ok {
  border-color: var(--ok, #2E7D32);
  color: var(--ok, #2E7D32);
}
.mdb-btn-ok:hover { background: var(--ok, #2E7D32); color: #fff; }

/* ── SETTINGS ── */
/* Настройки по макету — 860×640: семь вкладок и строки «лейбл — контрол»
   в колонке шириной 560 переносились в две строки каждая. */
.set-card { width: min(860px, 100%); height: min(640px, 100%); }
.set-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 18px 12px;
  border-bottom: 1px solid var(--rule);
}
.set-head h3 { font-size: 18px; font-weight: 700; margin: 0; }

/* Вкладки стоят между шапкой и телом, а не внутри тела: карточка — колонка, и
   прокручивается в ней только .set-body. Внутри неё панель вкладок уезжала бы
   вверх ровно тогда, когда до неё есть дело, — на длинной «Системе».
   Горизонтальная прокрутка вместо переноса: семь вкладок в две строки на
   телефоне съедают треть карточки, а сама лента листается пальцем. */
.set-tabs {
  display: flex; gap: 2px; padding: 0 12px; flex-shrink: 0;
  overflow-x: auto; scrollbar-width: none;
  border-bottom: 1px solid var(--rule);
}
.set-tabs::-webkit-scrollbar { display: none; }
.set-tab {
  background: none; border: none; border-bottom: 2px solid transparent;
  padding: 8px 12px 10px; margin-bottom: -1px; white-space: nowrap;
  font-size: 13.5px; font-family: inherit; cursor: pointer; color: var(--muted, #999);
}
.set-tab:hover { color: var(--text); }
.set-tab.active { color: var(--text); font-weight: 600; border-bottom-color: var(--text); }

.set-body { overflow-y: auto; padding: 4px 18px 18px; }

.set-group { padding-top: 18px; }
.set-group + .set-group { border-top: 1px solid var(--rule); margin-top: 18px; }
.set-group-title {
  /* Wrapping rather than squeezing: the note is a whole clause, and a title
     broken across three lines to make room for it is how «Мой почтовый ящик»
     read on a phone. */
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 10px;
}
.set-group-note { font-size: 11px; font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted-soft); }

.set-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 9px 0;
}
.set-label { font-size: 13px; font-weight: 500; min-width: 0; }
.set-hint {
  display: block; font-size: 11px; font-weight: 400; color: var(--muted);
  margin-top: 2px; line-height: 1.35;
}
.set-input { width: 280px; max-width: 100%; flex-shrink: 0; }

.set-seg {
  display: flex; flex-shrink: 0;
  border: 1px solid var(--rule); border-radius: var(--radius-md, 6px);
  overflow: hidden; background: var(--surface-2);
}
.set-seg-btn {
  border: none; background: none; cursor: pointer;
  padding: 5px 11px; font: inherit; font-size: 12px;
  color: var(--muted); white-space: nowrap;
  transition: background .1s, color .1s;
}
.set-seg-btn + .set-seg-btn { border-left: 1px solid var(--rule); }
.set-seg-btn:hover { color: var(--ink); }
.set-seg-btn.active { background: var(--bg); color: var(--ink); font-weight: 500; }

.set-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 12px;
  padding-top: 14px; margin-top: 4px;
}

/* ── Мой профиль и доступ ──
   One group with two halves — who you are, and how you get in — separated by a
   heading rather than split into two groups: both are «про меня» and a rule
   between them would read as the same boundary that divides mine from the
   agency's, which is the one distinction this screen is built on. */
.set-sub-title {
  font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); margin: 16px 0 2px; padding-top: 12px;
  border-top: 1px dashed var(--rule);
}
.set-input-tiny { min-width: 0; width: 72px; text-align: center; }

/* Swatches wrap: sixteen of them never fit beside a label on a phone, and a
   colour picker that scrolls sideways is one nobody scrolls. */
.set-swatches { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; max-width: 60%; }
.set-swatch {
  width: 22px; height: 22px; border-radius: 50%; cursor: pointer;
  border: 2px solid transparent; padding: 0;
  transition: transform .1s, border-color .1s;
}
.set-swatch:hover { transform: scale(1.12); }
.set-swatch.active { border-color: var(--ink); }

/* Ссылки на экраны, которые настраиваются в другом месте. Кнопки в столбик на
   узком экране: три подписи в строку — это горизонтальная прокрутка карточки,
   а её здесь быть не должно. */
.set-row-links { align-items: flex-start; }
.set-links { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
.set-status { font-size: 12px; color: var(--muted); }
.set-status-err { color: var(--danger, #C0392B); }
/* A stale or failed backup has to be legible at a glance, not read for. */
.set-hint-bad { color: var(--danger, #C0392B); font-weight: 500; }

/* Ответ на строку слева, когда менять его нечем: тариф, дата оплаты, «занято 4
   из 5». Стоит там же, где в соседних строках стоит поле или переключатель, —
   иначе читаемое и настраиваемое выглядят как разные экраны. Красным — то, во
   что уже упёрлись: строка про исчерпанный лимит и есть объяснение отказа,
   который человек увидит на кнопке. */
.set-value {
  font-size: 13px; font-weight: 500; text-align: right; flex-shrink: 0;
  max-width: 55%; color: var(--ink);
}
.set-value-bad { color: var(--danger, #C0392B); }

/* ── Мой почтовый ящик ──
   A form rather than a row of toggles, because connecting a mailbox is one
   deliberate action with four fields in it. Stacked full width: on a phone
   these are the hardest inputs in the app to type into, and an application
   password is pasted rather than typed only when it fits on the screen. */
.set-mbx-body { display: flex; flex-direction: column; gap: 10px; }
.set-mbx-field { display: flex; flex-direction: column; gap: 4px; }
.set-mbx-field > span { font-size: 11px; color: var(--muted); }
.set-mbx-field .mdb-input { width: 100%; }
.set-mbx-grid { display: grid; grid-template-columns: 1fr 90px; gap: 10px; }

/* Three states with three different next moves — working, worth a look, and
   broken — so the block is coloured rather than uniformly grey. One variable
   per tone, set on the block and read by everything inside it, because the
   same three colours also mark whether a password is stored. */
.set-tone-ok   { --tone: #2E7D32; }
.set-tone-warn { --tone: #B45309; }
.set-tone-bad  { --tone: var(--danger, #C0392B); }
body.dark .set-tone-ok   { --tone: #6FBF73; }
body.dark .set-tone-warn { --tone: #E0A106; }

/* The standing answer to "does my mail work". It sits at the top of the panel
   while «Проверить» sits at the bottom of it, so it has to be findable after a
   scroll rather than blend into the hints around it. */
.set-mbx-state {
  display: flex; flex-direction: column; gap: 3px;
  font-size: 12px; line-height: 1.45; color: var(--muted);
  padding: 10px 12px;
  border: 1px solid var(--rule); border-left: 3px solid var(--tone, var(--rule-strong));
  border-radius: var(--radius-md, 6px);
  background: var(--surface-2);
}
.set-mbx-state-title { font-size: 13px; font-weight: 600; color: var(--tone, var(--ink)); }
.set-mbx-state-check { margin-top: 5px; padding-top: 5px; border-top: 1px dashed var(--rule-strong); }

/* Whether a password is stored — a statement, not a hint, because the field
   above it is empty in both cases. Settled reads grey rather than green: the
   block above is already green when the mail works, and this is the smaller
   of the two facts. */
.set-mbx-secret {
  font-size: 12px; line-height: 1.4; font-weight: 500;
  margin: -4px 0 2px; color: var(--tone, var(--muted));
}
.set-mbx-secret.set-tone-ok { font-weight: 400; color: var(--muted); }

/* Open by default whenever there is no working password, folded away once
   there is: the two Yandex 360 mistakes it heads off are made once each. */
.set-mbx-help {
  font-size: 11px; line-height: 1.5; color: var(--muted);
  border: 1px solid var(--rule); border-radius: var(--radius-md, 6px);
  padding: 9px 12px;
}
.set-mbx-help > summary { cursor: pointer; font-size: 12px; font-weight: 500; color: var(--ink); }
.set-mbx-help p { margin: 8px 0 0; }
.set-mbx-help ol { margin: 8px 0 0; padding-left: 17px; }
.set-mbx-help li { margin-top: 6px; }
.set-mbx-help b { color: var(--ink); font-weight: 600; }
.set-mbx-warn {
  font-size: 12px; line-height: 1.4; padding: 9px 11px;
  border: 1px solid var(--danger, #C0392B); border-radius: var(--radius-md, 6px);
  color: var(--danger, #C0392B);
}
.set-mbx-warn code { font-family: ui-monospace, monospace; font-size: 11px; }
.set-mbx-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  flex-wrap: wrap; padding-top: 4px;
}
.set-mbx-foot .set-status { margin-right: auto; }

/* ── Вопросы ──
   Пустой результат поиска и подпись под справочником — единственное, что от
   вкладки поддержки здесь осталось: сама переписка уехала в чат. */
.set-sup-empty { display: block; padding: 10px 0; }

/* Ответы свёрнуты: справочник читают не подряд, а по одному вопросу. При
   поиске раскрываются все найденные — искали-то как раз ответ. */
.set-faq-item {
  border-bottom: 1px solid var(--rule); padding: 9px 0;
}
.set-faq-item > summary {
  cursor: pointer; font-size: 13px; line-height: 1.45; color: var(--ink);
  list-style-position: outside;
}
.set-faq-a { font-size: 12px; line-height: 1.6; color: var(--muted); padding: 2px 0 4px 2px; }
.set-faq-a p { margin: 7px 0 0; }
/* Подпись под справочником и дверь в чат стоят одной строкой: «не нашли
   ответ» без кнопки — это указание искать её самому, а искать её будет как раз
   тот, кто уже ничего не нашёл. */
.set-faq-foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 16px;
}
.set-faq-foot .mdb-btn-ghost { font-size: 12px; padding: 4px 10px; }

.side-foot-gear {
  flex-shrink: 0; width: 15px; height: 15px; color: var(--muted-soft);
  opacity: 0; transition: opacity .12s;
}
.side-foot-gear svg { width: 100%; height: 100%; display: block; }
.side-foot-user:hover .side-foot-gear { opacity: 1; }

/* Calendar density — the month grid's minimum row height. */
body[data-density="compact"] { --row-h: 88px; }
body[data-density="roomy"]   { --row-h: 152px; }

@media (max-width: 640px) {
  .set-row { flex-direction: column; align-items: stretch; gap: 8px; }
  .set-input { width: 100%; }
  .set-seg-btn { flex: 1; }
}

/* ── MONTH STREAM ── */
/* The month grid scrolls continuously, so no cell is "another month" any more.
   The 1st carries its month name instead, and gets a heavier left rule — that
   pair is what marks a boundary as it goes past. The rule is vertical: a top
   rule ran across the row and read as a broken grid line, not a boundary. */
.day-month {
  margin-left: 5px; font-size: 11px; font-weight: 600;
  color: var(--ink-soft); text-transform: lowercase;
}
/* Границу месяца отмечает первое число: жирнее остальных и с названием месяца
   рядом (`MONTHS_SHORT` в 02-calendar.js). Тень вдоль всей клетки говорила то
   же самое третий раз и читалась как полоса грязи на сетке. */
.day-cell.month-start .day-num { color: var(--ink); font-weight: 600; }

.day-flash { animation: dayFlash .8s ease-out; }
@keyframes dayFlash {
  0%, 55% { background: color-mix(in oklch, var(--accent, #3B82F6) 14%, transparent); }
  100%    { background: transparent; }
}

/* Sidebar counts line up in a column: the name takes the slack and truncates,
   the number keeps a fixed width so single and double digits share an edge.
   Сама цифра описана выше, рядом с остальной колонкой: два правила `.view-count`
   в одном файле спорили за `padding`, и выигрывало дальнее — то есть комментарий
   про правый край стоял над стилем, который этот край не задавал. */
.model-item-name {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Money change log ── */
.fin-audit-chg {
  display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
  font-size: 12px; line-height: 1.7;
}
.fin-audit-field { color: var(--muted); font-family: var(--mono, ui-monospace, monospace); font-size: 11px; }
.fin-audit-from { color: var(--muted); text-decoration: line-through; }
.fin-audit-arrow { color: var(--muted-soft); }
.fin-audit-to { color: var(--ink); font-weight: 500; }

/* ── Почта ──
   Same two-pane shape as the Wiki: the conversations on the left, the open one
   on the right, and one pane at a time once the screen is too narrow for both. */
.mail-container {
  display: grid;
  grid-template-columns: 400px 1fr;
  height: 100%;
  min-height: 0;
  overflow: hidden;
  background: var(--surface);
}
.mail-list {
  display: flex; flex-direction: column;
  min-height: 0;
  border-right: 1px solid var(--rule);
  background: var(--surface-2);
}
.mail-list-head { padding: 12px 12px 10px; border-bottom: 1px solid var(--rule); }
.mail-boxes { display: flex; gap: 4px; flex-wrap: wrap; margin-bottom: 8px; }
.mail-box {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border: 1px solid transparent; border-radius: var(--radius);
  background: none; font: inherit; font-size: 13px; color: var(--ink-soft);
  cursor: pointer;
}
.mail-box:hover { background: var(--hover); }
.mail-box.active { background: var(--surface); border-color: var(--rule-strong); color: var(--ink); font-weight: 600; }
.mail-box-n { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.mail-search {
  width: 100%; padding: 7px 10px;
  border: 1px solid var(--rule-strong); border-radius: var(--radius);
  background: var(--surface); font: inherit; font-size: 13px; color: var(--ink);
}
/* The second axis under the search: whose mail is the row above, what kind of
   mail is this one. Quieter than the boxes on purpose — it narrows a list
   rather than choosing one. */
.mail-views { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 8px; }
.mail-view {
  padding: 4px 9px; border: 1px solid var(--rule); border-radius: 999px;
  background: none; font: inherit; font-size: 12px; color: var(--muted); cursor: pointer;
}
.mail-view:hover { background: var(--hover); color: var(--ink-soft); }
.mail-view.active { background: var(--surface); border-color: var(--rule-strong); color: var(--ink); }
.mail-sync {
  margin-top: 8px; padding: 6px 10px;
  border: 1px solid var(--rule-strong); border-radius: var(--radius);
  background: var(--surface); font: inherit; font-size: 12px; color: var(--ink-soft); cursor: pointer;
}
.mail-sync:hover { background: var(--hover); }
.mail-note { margin-top: 8px; font-size: 12px; line-height: 1.5; color: var(--muted); }
/* Shown in place of a wiki article when DOMPurify did not load — see 08-wiki.js */
.wiki-note { margin-bottom: 10px; font-size: 12px; line-height: 1.5; color: #B45309; }
.wiki-raw { white-space: pre-wrap; word-break: break-word; font-size: 13px; line-height: 1.6; }
.mail-note-warn { color: #B45309; }

.mail-threads { flex: 1; min-height: 0; overflow-y: auto; padding: 6px; }
.mail-row {
  display: block; width: 100%; text-align: left;
  padding: 9px 10px; margin-bottom: 2px;
  border: 1px solid transparent; border-radius: var(--radius);
  background: none; font: inherit; color: var(--ink); cursor: pointer;
}
.mail-row:hover { background: var(--hover); }
.mail-row.active { background: var(--surface); border-color: var(--rule-strong); }
.mail-row-top { display: flex; align-items: baseline; gap: 8px; }
.mail-row-who { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 600; }
.mail-row-date { flex-shrink: 0; font-size: 11px; color: var(--muted); }
.mail-row-subj {
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mail-count {
  flex-shrink: 0; font-size: 11px; color: var(--muted);
  border: 1px solid var(--rule-strong); border-radius: 8px; padding: 0 5px;
}
.mail-clip { flex-shrink: 0; font-size: 11px; }
.mail-row-prev {
  font-size: 12px; color: var(--muted); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mail-row-tags { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 6px; }
.mail-tag {
  font-size: 11px; padding: 1px 6px; border-radius: 6px;
  background: color-mix(in oklch, var(--ink) 7%, var(--bg)); color: var(--ink-soft);
  max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mail-tag-staff { background: color-mix(in oklch, var(--accent) 16%, var(--bg)); color: var(--ink); }
.mail-tag-model { background: color-mix(in oklch, #8BA89B 30%, var(--bg)); }
/* A letter nobody owns is the one that gets forgotten — it reads as a warning. */
.mail-tag-none { background: color-mix(in oklch, #E5A50A 24%, var(--bg)); color: #7A4E00; }
.mail-tag-manual { background: none; color: var(--muted); }

.mail-detail { min-height: 0; overflow-y: auto; padding: 24px 32px 64px; }
.mail-detail-inner { max-width: 780px; }
.mail-empty { color: var(--muted); padding: 48px 8px; font-size: 14px; }
.mail-subject { font-size: 24px; font-weight: 700; margin: 0 0 4px; line-height: 1.25; }
.mail-thread-meta { font-size: 12px; color: var(--muted); margin-bottom: 16px; }
.mail-letter {
  border: 1px solid var(--rule); border-radius: var(--radius-md);
  background: var(--surface); margin-bottom: 10px; overflow: hidden;
}
.mail-letter.open { border-color: var(--rule-strong); }
.mail-letter-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  padding: 12px 14px; cursor: pointer;
}
.mail-letter-head:hover { background: var(--hover); }
.mail-letter-who { font-size: 13px; font-weight: 600; }
.mail-letter-to { font-size: 12px; color: var(--muted); margin-top: 2px; }
.mail-letter-date { flex-shrink: 0; font-size: 12px; color: var(--muted); white-space: nowrap; }
.mail-attr {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 8px 14px; border-top: 1px solid var(--rule); background: var(--surface-2);
}
.mail-attr-l { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.mail-attr-edit {
  margin-left: auto; background: none; border: none; padding: 2px 4px;
  font: inherit; font-size: 12px; color: var(--accent); cursor: pointer;
}
.mail-body { padding: 14px; font-size: 14px; line-height: 1.6; overflow-x: auto; }
.mail-body img, .mail-body table { max-width: 100%; }
.mail-body a { color: var(--accent); }
.mail-text { font: inherit; white-space: pre-wrap; word-break: break-word; margin: 0; }
.mail-empty-body { color: var(--muted); font-size: 13px; }
.mail-att { display: flex; flex-wrap: wrap; gap: 8px; padding: 0 14px 12px; }
.mail-att-item {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border: 1px solid var(--rule-strong); border-radius: var(--radius);
  font-size: 12px; color: var(--ink); text-decoration: none;
}
.mail-att-item:hover { background: var(--hover); }
.mail-att-size { color: var(--muted); }
.mail-att-gone { border-style: dashed; color: var(--muted); }
/* The name is the control in all three states — a link once the bytes are
   here, a button while they are still in the mailbox — so it has to look the
   same either way or the same file appears to be two different things. */
.mail-att-name {
  padding: 0; border: 0; background: none; font: inherit; font-size: 12px;
  color: var(--ink); text-decoration: none; cursor: pointer;
}
.mail-att-name:disabled { color: var(--muted); cursor: default; }
/* Dotted, because the file is described and not present: the row is a promise
   the mail server has to keep, not a document sitting on this disk. */
.mail-att-off { border-style: dotted; }
.mail-att-keep {
  padding: 0 2px; border: 0; background: none;
  font-size: 13px; line-height: 1; color: var(--muted); cursor: pointer;
}
.mail-att-keep:hover { color: var(--ink); }
.mail-att-keep.on { color: #B45309; }
.mail-letter-acts { display: flex; gap: 8px; padding: 0 14px 14px; }
.mail-letter-acts .mdb-btn { text-decoration: none; }
.mail-mobile-close { display: none; }

@media (max-width: 720px) {
  .mail-container { grid-template-columns: 1fr; grid-auto-rows: 100%; }
  .mail-detail { display: none; padding: 16px 16px 48px; }
  .mail-container.show-detail .mail-list { display: none; }
  .mail-container.show-detail .mail-detail { display: block; }
  .mail-mobile-close {
    display: inline-flex; align-items: center; justify-content: center;
    width: 32px; height: 32px; margin-bottom: 8px;
    border: 1px solid var(--rule-strong); border-radius: var(--radius);
    background: var(--surface); color: var(--ink); font-size: 15px; cursor: pointer;
  }
  .mail-subject { font-size: 20px; }
}

/* ── Сверка прогноза с фактом ─────────────────────────────────────────────
   Отчёт, а не проводка: цифры двух реестров стоят рядом и нигде не
   складываются, поэтому подписи при числах мелкие и приглушённые — они
   поясняют, что именно сравнивается, а не спорят за внимание с самим числом. */
.fin-rec-cnt { font-size: 11px; color: var(--muted, #999); font-weight: 400; }
.fin-rec-ok  { font-size: 11px; color: #16A34A; margin-top: 2px; }

/* ── Поиск и фильтры на экране моделей ────────────────────────────────────
   Панель стоит между тулбаром и списком и рассчитана на то, что контролов в
   ней много: шесть диапазонов, три списка и поиск не встают в строку ни на
   одном экране, поэтому строка переносится, а не сжимает поля до нечитаемых.
   Числовые поля узкие намеренно — в них по три цифры, и широкое поле рядом с
   «от/до» читается как место для фразы. */
.mdb-filters { padding: 0 var(--pad-x, 32px) 8px; flex-shrink: 0; }
.mdb-filters-row { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; }
/* The search + funnel live on the toolbar row itself (#mdb-search-slot),
   next to the view toggle: a row of their own pushed the list down for two
   small controls.

   Слот не растягивается: поле свёрнуто до лупы (`ModelsDB.toggleSearch`), и
   `flex-grow` тянул бы за собой пустое место посреди строки — раскрытое поле
   добирает ширину само, своим `flex` ниже. */
#mdb-search-slot { flex: 0 1 auto; min-width: 0; flex-wrap: nowrap; }
/* Свёрнутый поиск — то же движение, что у лупы в шапке календаря
   (`.view-row.search-open`): в покое видна одна кнопка, поле разворачивается
   по щелчку. Поле в 260 пикселей стояло раскрытым всегда, хотя имя набирают
   изредка, и занимало середину строки (issue #272). */
.mdb-filters-row .mdb-f-search { display: none; }
.mdb-filters-row.mdb-search-open .mdb-f-search { display: block; }
.mdb-filters-row.mdb-search-open .mdb-f-search-btn { display: none; }
.mdb-f-search, .mdb-f-num, .mdb-f-sel {
  border: 1px solid var(--rule, rgba(55,53,47,.12));
  border-radius: var(--radius, 6px);
  background: var(--surface, #fff);
  color: var(--ink, #37352F);
  font: inherit; font-size: 12.5px;
  padding: 5px 8px;
}
.mdb-f-search { flex: 1 1 180px; min-width: 140px; max-width: 260px; }
.mdb-f-num { width: 62px; }
.mdb-f-sel { max-width: 160px; }
.mdb-f-search:focus, .mdb-f-num:focus, .mdb-f-sel:focus {
  outline: none; border-color: var(--accent, #C8A882);
}
.mdb-f-range { display: inline-flex; align-items: center; gap: 4px; }
.mdb-f-lbl { font-size: 11.5px; color: var(--muted, #9B9A93); white-space: nowrap; }
.mdb-filters-foot { display: flex; align-items: center; gap: 10px; padding-top: 6px; }
.mdb-f-found { font-size: 11.5px; color: var(--muted, #9B9A93); }
.mdb-f-reset {
  border: 0; background: none; padding: 0;
  font: inherit; font-size: 11.5px; color: var(--accent, #2383E2);
  cursor: pointer; text-decoration: underline;
}
.mdb-f-reset.hidden { display: none; }
/* Воронка, за которой сложены диапазоны, статус и сортировка. Точка в углу —
   «за сложенной панелью что-то выбрано». */
.mdb-f-toggle {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex-shrink: 0;
  border: 1px solid var(--rule, rgba(55,53,47,.12));
  border-radius: var(--radius, 6px);
  background: var(--surface, #fff); color: var(--ink-soft, #5A584F);
  cursor: pointer; transition: background 120ms, border-color 120ms;
}
.mdb-f-toggle:hover { background: var(--hover, #EFEEEC); color: var(--ink, #37352F); }
.mdb-f-toggle[aria-expanded="true"] { background: var(--hover, #EFEEEC); color: var(--ink, #37352F); }
.mdb-f-toggle.on::after {
  content: ''; position: absolute; top: 4px; right: 4px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent, #2383E2);
}
.mdb-filters-adv { padding-top: 6px; }
.mdb-filters-adv.hidden { display: none; }
/* Меню смены статуса у чипа на плитке (сам чип — рядом с .mdb-chip выше) */
.mdb-status-menu {
  position: fixed; z-index: 1600;
  display: flex; flex-direction: column; min-width: 140px;
  background: var(--bg, #fff); border: 1px solid var(--rule-strong, rgba(55,53,47,.2));
  border-radius: var(--radius, 6px); box-shadow: 0 8px 24px rgba(0,0,0,.12);
  padding: 4px;
}
.mdb-status-opt {
  border: 0; background: transparent; text-align: left;
  padding: 6px 8px; border-radius: 4px; font: inherit; font-size: 13px;
  color: var(--ink, #37352F); cursor: pointer;
}
.mdb-status-opt:hover { background: var(--hover, #EFEEEC); }
.mdb-status-opt.cur { font-weight: 600; }
@media (max-width: 720px) {
  .mdb-filters { padding: 0 var(--pad-x, 16px) 6px; }
  .mdb-f-search { max-width: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   МОБИЛЬНАЯ ОТДЕЛКА

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

/* ── Касание вместо наведения ──
   На тач-устройстве `:hover` не наступает никогда — он либо не срабатывает
   вовсе, либо залипает после тапа. В файле 200 правил с `:hover` и три с
   `:active`, и это, а не вёрстка, — главная причина, по которой продукт на
   телефоне ощущается неживым: нажатие ничем не отвечает. Отклик даём здесь
   один раз на общие классы, а не на каждый экран отдельно. */
@media (hover: none) {
  .tb-btn:active, .icon-btn:active, .mdb-btn:active, .mdb-icon-btn:active,
  .fin-nav-btn:active, .vt-btn:active, .nav-btn:active, .today-btn:active,
  .set-row:active, .mdb-row:active, .list-item:active, .mail-row:active,
  .model-item:active, .day-cell:active, .fin-subnav-btn:active,
  .mdb-tab:active, .set-tab:active, .wiki-row:active {
    background: var(--hover);
  }
  .btn-primary:active, .btn-new:active, .mdb-btn-primary:active { filter: brightness(1.35); }
  .ev-card:active, .cal-bar:active, .kb-card:active, .pkg-card:active,
  .year-month:active, .mdb-card:active { filter: brightness(0.94); }

  /* Кнопки, которые видно только под курсором, на телефоне не существуют:
     удалить фотографию модели, ответить на сообщение, добавить подстраницу.
     Каждая из них — единственный способ сделать своё действие, и на тач-
     устройстве этого способа не было вовсе. Образец рядом: `.mdb-row-copy` и
     `.list-item-del` открыты так же и давно. */
  .mdb-media-acts { display: flex; }
  .chat-msg-actions { opacity: 1; }
  .mdb-book-thumb-del, .wiki-row-plus, .wiki-row-grip, .wiki-cat-plus {
    opacity: 1;
  }
  /* Карточка доски — единственная плашка съёмки, которую на телефоне рисуют
     (`.ev-card` там `display:none`), поэтому её «…» открывается и без
     наведения: за ними лежат «Редактировать» и «Удалить», и другой двери к ним
     с доски нет. */
  .kb-card > .ev-more-btn { opacity: 1; pointer-events: auto; }

  /* Выбранная модель в боковом списке красилась в `--bg` — то есть в белое на
     почти белом `--surface-2`, и выбор переставал быть виден. Правило писалось
     до нынешней палитры, когда фон списка был другим. */
  .model-item.active { background: var(--hover); }
}

/* ══ ТЕЛЕФОН (≤ 768px) ══ */
@media (max-width: 768px) {

  /* Вырез и домашняя полоса. `env(safe-area-inset-*)` до сих пор ничего не
     значил: ни одна страница не объявляла `viewport-fit=cover`, без которого
     iOS сам отступает от выреза и все шесть обращений к `env()` дают ноль.
     Теперь объявляет — и отступы надо расставить по-настоящему. */
  .topbar { padding-left: max(10px, env(safe-area-inset-left));
            padding-right: max(10px, env(safe-area-inset-right)); }
  .sidebar { padding-left: max(8px, env(safe-area-inset-left)); }
  /* Домашняя полоса отодвигает содержимое панели, а не саму панель: отступ,
     оставленный на `.sidebar`, укоротил бы дорожку бегунка снизу. */
  #sidebar-scroll { padding-bottom: max(12px, env(safe-area-inset-bottom)); }
  .chat-fab { bottom: calc(18px + env(safe-area-inset-bottom, 0px)); }

  /* Скролл внутренней панели, докрутившись до края, продолжался прокруткой
     всей оболочки: список писем дотягивал до конца — и уезжал топбар. */
  .mail-list, .mail-detail, .wiki-detail, .wiki-list, .fin-container,
  .mdb-container, .chat-messages, .set-body, .list-container, .docs-container,
  .pkg-container, .side-panel-inner, .mdb-detail {
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }

  /* ── Топбар ──
     Шесть кнопок, и «Пользователи» словами — это 95 пикселей из 370: вместе с
     остальными строка не помещалась, «Выйти» обрезалось, а имени экрана не
     оставалось вовсе. Боковое меню на телефоне спрятано, и человек переставал
     видеть, где находится.

     Значок рисуем маской, а не разметкой: кнопка в index.html — это текст без
     `<svg>`, а прятать её целиком нельзя (управление коллегами больше ниоткуда
     не открывается). Маска даёт ту же обводку, что у соседних значков, и
     красится `currentColor`, поэтому тему держит сама. */
  #users-btn {
    font-size: 0; width: var(--tap); padding: 0; justify-content: center;
  }
  #users-btn::before {
    content: ''; width: 17px; height: 17px; background: currentColor;
    -webkit-mask: var(--ico-users); mask: var(--ico-users);
  }
  /* «+ Событие» подписью — ещё 150 пикселей, из-за которых обрезалось «Выйти».
     Значок в кнопке свой и размечен инлайновым размером, поэтому `font-size: 0`
     гасит подпись, не трогая его: остаётся круглая кнопка с плюсом — то, чем
     главное действие и выглядит на телефоне. */
  .btn-new { font-size: 0; width: var(--tap); padding: 0; gap: 0; border-radius: 50%; }
  /* Подсказка про ⌘K на экране, к которому не подключена клавиатура, — совет,
     которому нечем последовать. Прячется она теперь в меню, куда и уехала
     вместе с формой; цифры видов в соседнем меню остаются — они и на телефоне
     говорят, каким по счёту стоит вид. */
  .cal-pop-end .menu-row .kbd { display: none; }
  .tb-btn { height: var(--tap); }
  .tb-btn.icon { width: var(--tap); height: var(--tap); }
  .topbar { min-height: 52px; }

  /* ── Пальцем, а не курсором ──
     Двадцать шесть пикселей — это стрелка «предыдущий месяц», кнопка в каждой
     ячейке финансовой таблицы и крестик на теге. Мимо такого промахиваются.
     Растим область нажатия, а не саму кнопку: рисунок остаётся прежним. */
  .icon-btn, .mdb-icon-btn, .fin-nav-btn, .mdb-view-btn, .sb-fold-btn, .sb-tool-btn,
  .stm-del, .wiki-caret, .set-swatch, .chat-msg-act, .mail-att-keep, .list-group-pin {
    position: relative;
  }
  .icon-btn::after, .mdb-icon-btn::after, .fin-nav-btn::after,
  .mdb-view-btn::after, .sb-fold-btn::after, .sb-tool-btn::after, .stm-del::after,
  .wiki-caret::after, .set-swatch::after, .chat-msg-act::after,
  .mail-att-keep::after, .list-group-pin::after {
    content: ''; position: absolute; top: 50%; left: 50%;
    width: var(--tap); height: var(--tap); transform: translate(-50%, -50%);
  }
  /* Правило выше писалось под одиночную кнопку: площадка в 44px шире значка и
     свободно вылезает за него. Лупа и воронка стоят вплотную, и площадка
     воронки накрывала правые 40% лупы — палец попадал в лупу, а открывались
     фильтры. Поэтому здесь кнопки растут по-настоящему, а площадка по ширине
     не выходит за свою кнопку. «+» с шестерёнкой «Статуса» — такая же пара в
     таком же ряду, и правится она этим же правилом, а не своей копией. */
  .sb-head-tools { gap: 6px; --sb-tool-size: 32px; }
  .sb-tool-btn::after { width: 100%; }
  .mdb-btn, .mdb-btn-sm, .btn-new, .fin-subnav-btn, .vt-btn { min-height: 40px; }

  /* ── Общий диалог формы ──
     За ним 49 форм: все финансы, карточка модели, пакеты, поездки, документы.
     Мобильного вида у него не было ни одного, и на телефоне он оставался
     диалогом по центру с полями в 156 пикселей — при том что соседний
     `.modal` календаря давно открывается снизу во всю ширину. Приводим к нему:
     две системы диалогов терпимы, две разные — на телефоне нет. */
  .mdb-ovl { padding: 0; align-items: flex-end; }
  .mdb-ovl-card {
    width: 100%; max-width: 100%;
    border-radius: 18px 18px 0 0; border-bottom: 0;
    max-height: 92dvh;
  }
  .mdb-ovl-head { padding: 14px 16px; font-size: 17px; }
  .mdb-ovl-foot {
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
    gap: 10px;
  }
  .mdb-ovl-foot .mdb-btn { flex: 1; justify-content: center; min-height: 48px; }
  .docs-form { padding: 14px 16px calc(14px + env(safe-area-inset-bottom, 0px)); max-height: none; }
  /* Подпись над полем, а не слева от него: в колонке 120px помещается «Контр…»,
     а полю остаётся меньше половины экрана. */
  .docs-f { grid-template-columns: 1fr; align-items: stretch; gap: 5px; }
  .docs-f > span:first-child { font-size: 12px; }
  .docs-f .mdb-input, .docs-f select, .docs-f input { min-height: 46px; }

  /* ── Карточка модели ──
     34px по каждому краю — это 68 пикселей из 390, отданные полю. Экран,
     который букер держит открытым весь день. */
  .mdb-detail-inner { padding: 0 var(--pad-x) 40px; }

  /* Плавающая кнопка чата стоит поверх последней строки списка. Там, где
     запас уже был (`.list-container`, `.pkg-container`), это видно; здесь его
     забыли. */
  .mdb-wrap, .mail-detail, .wiki-detail, .fin-container, .docs-container {
    padding-bottom: calc(78px + env(safe-area-inset-bottom, 0px));
  }

  /* Многодневная полоса события вылезала за последнюю колонку сетки: ширина
     считается по числу дней, а на телефоне колонка вчетверо у́же. */
  .cal-bars { overflow: hidden; }
  .cal-bar { font-size: 11px; }

  /* Доска канбана листается по колонке, а не останавливается посередине. */
  .kanban-board { scroll-snap-type: x mandatory; }
  .kb-col { scroll-snap-align: start; }

  /* Затемнение под боковым меню появлялось мгновенно, пока само меню
     выезжало четверть секунды. */
  .sidebar-backdrop {
    display: block; opacity: 0; pointer-events: none;
    transition: opacity .25s ease;
  }
  .sidebar-backdrop.visible { opacity: 1; pointer-events: auto; }

  /* Настройки: строка распадается на две, но значение осталось прижатым
     вправо — подпись у левого края, значение у правого, связи между ними не
     видно. */
  .set-value, .set-links, .set-swatches { text-align: left; justify-content: flex-start; }
  .set-swatches { max-width: none; }

  /* ── Строка сотрудника ──
     Аватар, имя, чип роли и три кнопки словами — это 328 пикселей из 346, и
     имени оставалось восемнадцать: оно обрезалось в «Антон», а логин — в «@…»,
     то есть строка переставала отвечать на оба вопроса, ради которых её
     читают. Кнопки уходят на свою строку и делят её поровну: у них одинаковый
     вес, и панелью они читаются лучше, чем хвостом за обрезанным именем. */
  .user-row {
    flex-wrap: wrap; padding: 10px 6px;
    border-bottom: 1px solid var(--rule);
  }
  .user-row:last-of-type { border-bottom: 0; }
  /* Отступ под аватар — кнопки встают под именем, а не под картинкой: 30
     пикселей аватара плюс зазор строки. Он вычитается из базиса, иначе строка
     кнопок — это 100% плюс отступ, то есть ровно на отступ шире своей строки. */
  .user-row-actions { flex: 1 0 calc(100% - 41px); gap: 8px; margin-left: 41px; }
  .user-row-actions .icon-btn {
    flex: 1; justify-content: center; min-height: 40px; font-size: 12px;
  }

  /* Кнопка встаёт под адресом во всю ширину: рядом с полем ей остаётся сотня
     пикселей, а это единственное действие, ради которого экран открывали, —
     ссылка показывается один раз, и не скопировав её, сотрудника заводят
     заново. */
  .invite-copy { flex-wrap: wrap; }
  .invite-copy .btn-primary { flex: 1 0 100%; }
}

/* ══ УЗКИЙ ЭКРАН (≤ 720px) — таблицы, почта, финансы ══ */
@media (max-width: 720px) {

  /* ── Ряды вкладок ──
     Семь вкладок финансов — это около 650 пикселей в колонке шириной 354.
     Флекс-элементы не сжимаются у́же своего содержимого, поэтому ряд не просто
     обрезался: он растягивал `.fin-container`, у которого `overflow-y: auto`,
     а значит вычисленный `overflow-x: auto` — и вбок ехал весь экран финансов
     целиком. Прокрутка ряда лечит и обрезку, и это. */
  .fin-subnav {
    overflow-x: auto; flex-wrap: nowrap;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .fin-subnav::-webkit-scrollbar { display: none; }
  .fin-subnav-btn { flex-shrink: 0; white-space: nowrap; }

  /* ── Почта ──
     Мобильный вид у почты был, но не работал: колонка `1fr` — это
     `minmax(auto, 1fr)`, а `auto` не даёт треку стать у́же самого длинного
     слова внутри. Список писем держал 627 пикселей в экране на 390, и адрес
     с темой обрезались справа. Лечится не вёрсткой, а `minmax(0, 1fr)`. */
  .mail-container { grid-template-columns: minmax(0, 1fr); }
  .mail-list, .mail-detail { min-width: 0; }
  .mail-row-subj, .mail-row-prev { min-width: 0; }
  .mail-mobile-close { width: var(--tap); height: var(--tap); }
  /* Письмо приходит с чужой вёрсткой — таблицей на 700 пикселей в том числе.
     У картинок ограничение уже стоит, у таблиц его забыли. */
  .mail-body table, .wiki-body table { max-width: 100%; display: block; overflow-x: auto; }

  /* ── Таблица → карточки ──
     Двенадцать колонок в 354 пикселя показывают две с половиной, и строка
     читается боковой прокруткой по одной. Поэтому на телефоне строка перестаёт
     быть строкой: каждая ячейка получает свою подпись из шапки (её проставляет
     `labelTableCells` в 04-ui.js) и встаёт в столбик, а строка становится
     карточкой. Прокрутки не остаётся вовсе — читать нечего вбок. */
  .docs-table-wrap { overflow: visible; border: 0; border-radius: 0; }
  .docs-table, .docs-table tbody, .docs-table tr, .docs-table td { display: block; }
  .docs-table thead { display: none; }
  .docs-table { font-size: 14px; }
  .docs-table tr {
    margin-bottom: 10px; padding: 4px 2px 2px;
    border: 1px solid var(--rule-strong); border-radius: var(--radius-lg);
    background: var(--surface);
  }
  .docs-table td {
    display: grid; grid-template-columns: 11ch minmax(0, 1fr);
    gap: 10px; align-items: baseline;
    padding: 7px 12px; border-bottom: 1px solid var(--rule);
  }
  .docs-table tr td:last-child { border-bottom: 0; }
  .docs-table td::before {
    content: attr(data-label);
    font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
    color: var(--muted); overflow-wrap: anywhere;
  }
  /* Ячейка без подписи — это колонка кнопок: у неё пустой `<th>`. Подписать её
     нечем, а строкой на кнопку она растянула бы карточку вчетверо — кнопок в
     строке бывает три. `inline-flex` сводит их в одну панель внизу карточки:
     соседние ячейки встают в строку, потому что сама строка здесь `block`. */
  .docs-table td[data-label=""] {
    display: inline-flex; align-items: center;
    padding: 8px 6px; border-bottom: 0;
  }
  .docs-table td[data-label=""]::before { display: none; }
  .fin-td-note { max-width: none; }
  /* Подытог и «ничего не найдено» идут через colspan, подписей у них нет —
     они остаются обычной строкой во всю ширину. */
  .docs-table td[colspan] { grid-template-columns: 1fr; }
  .docs-table td[colspan]::before { display: none; }

  /* Сводка обзора вставала в две колонки по 172 пикселя, а «677 608,70 RUB»
     в них не помещается — сумма переносилась на вторую строку, отрываясь от
     своей валюты, и третья карточка оставляла дыру рядом с собой. В столбик
     число помещается целиком, и читать сводку сверху вниз всё равно
     привычнее, чем зигзагом. */
  .fin-kpis { grid-template-columns: 1fr; }
  .fin-kpi { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
  .fin-kpi-l { margin-bottom: 0; }
  .fin-kpi-v { font-size: 19px; white-space: nowrap; }
  .fin-card { min-width: 0; flex: 1 1 100%; }
}

/* Таймлайн на телефоне остаётся нечитаемым, и починить его отсюда нечем:
   `TL_DAY_W` и `TL_NAME_W` — числа в 02-calendar.js, до которых media query
   не достаёт, поэтому из тридцати одного дня видно пять. Клипается он честно
   (`.tl-head-outer { overflow: hidden }`), вбок экран не едет — то есть это
   не поломка вёрстки, а вкладка, которую надо переписать на токены ширины.
   Прятать её отсюда не стали: какие экраны показывать на телефоне — вопрос
   не оформления. */


/* ══ ИМПОРТ СОБЫТИЯ ══
   «Создать из текста» / «Создать из файла» (19-event-import.js) и шапка
   неподтверждённого события в боковой панели (03-events.js). */

/* Окно шире обычного: сюда вставляют письмо, а письмо в поле шириной с форму
   «Новое событие» превращается в столбик из двух слов. */
.modal.modal-import { max-width: 560px; width: 100%; }

.ev-import-ta {
  width: 100%; box-sizing: border-box;
  min-height: 150px; resize: vertical;
  font: inherit; font-size: 13px; line-height: 1.5;
  padding: 10px 12px;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--surface); color: var(--ink);
}
.ev-import-ta:focus { outline: none; border-color: var(--rule-strong); }
/* Пример письма бледнее обычной подписи (`--muted-soft`, а не `--muted`): в
   поле на девять строк он занимает две и цветом по умолчанию читается уже
   вставленным текстом, а не подсказкой (issue #226). Это та строка, которую
   не читают, а стирают собой, — то же место, где `--muted-soft` держит числа
   чужого месяца. */
.ev-import-ta::placeholder { color: var(--muted-soft); }

/* Зона перетаскивания и кнопка выбора — один прямоугольник: человек, которому
   написали «перетащите», всё равно щёлкает по нему. */
.ev-import-drop {
  border: 1px dashed var(--rule-strong); border-radius: var(--radius);
  padding: 26px 16px; text-align: center; cursor: pointer;
  background: var(--surface-2);
  transition: background .14s, border-color .14s;
}
.ev-import-drop:hover, .ev-import-drop.over {
  background: var(--hover); border-color: var(--ink);
}
.ev-import-drop-main { font-size: 13px; font-weight: 500; color: var(--ink); }
.ev-import-drop-sub  { font-size: 11px; color: var(--muted); margin-top: 5px; }

.ev-import-note { font-size: 12px; color: var(--muted); line-height: 1.5; }

/* Адрес для писем: поле только для чтения, но именно поле, а не строка текста.
   Его выделяют и копируют руками не реже, чем кнопкой, — на телефоне, где
   доступа к буферу у страницы может не быть вовсе. */
.ev-import-addr {
  width: 100%; box-sizing: border-box;
  font: inherit; font-size: 13px; padding: 8px 10px;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--surface-2); color: var(--ink);
}
/* «Адрес выпущен» и «Адрес скопирован» — в подвале, слева от «Закрыть», а не
   под объясняющей подписью: там их не отличить от неё, а сказаны они про
   только что нажатое. */
.ev-import-said { margin-right: auto; font-size: 12px; color: var(--muted); }

/* Кнопка под полем, а не в подвале окна: она относится к адресу над ней, а в
   подвале уже стоит «Закрыть», и две кнопки в одном углу читались бы парой
   «сохранить/отменить», которой здесь нет. */
.modal-actions.modal-actions-left { justify-content: flex-start; margin-top: 10px; }
.ev-import-err {
  margin-top: 10px; font-size: 12px; line-height: 1.45;
  color: var(--danger, #B4342B);
}

/* Исходник показывается моноширинным и как есть, с переносами: это письмо, а
   не наш текст, и «красиво» здесь означало бы «не то, что прочитала машина». */
.ev-import-source {
  max-height: 340px; overflow: auto;
  margin: 0; padding: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11.5px; line-height: 1.55;
  white-space: pre-wrap; word-break: break-word;
  background: var(--surface-2); color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--radius);
}

/* ── Шапка неподтверждённого события в боковой панели ── */
.spn-draft {
  margin: 0 0 12px;
  padding: 10px 12px;
  border: 1px dashed var(--unconf-line); border-radius: var(--radius);
  background: var(--unconf-fill);
}
.spn-draft-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.spn-draft-tag {
  font-size: 10.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--unconf-ink);
}
.spn-draft-src { font-size: 11.5px; color: var(--muted); }
/* Предупреждения — строки, а не значки: «год не назван» и «время из другой
   зоны» не различаются картинкой, а различать их надо. */
.spn-draft-warn {
  display: flex; flex-direction: column; gap: 3px;
  margin-top: 7px; font-size: 11.5px; color: var(--unconf-ink);
}
.spn-draft-warn span::before { content: '· '; }
.spn-draft-acts { display: flex; gap: 6px; margin-top: 10px; }
.spn-draft-ok, .spn-draft-btn {
  font: inherit; font-size: 12px; padding: 5px 11px;
  border-radius: var(--radius-sm); cursor: pointer;
  border: 1px solid var(--rule-strong); background: var(--surface); color: var(--ink);
}
.spn-draft-ok { background: var(--ink); color: var(--surface); border-color: var(--ink); }
.spn-draft-ok:hover  { filter: brightness(1.25); }
.spn-draft-btn:hover { background: var(--hover); }
