/* ==========================================================================
   app.css — компонентный слой прототипа E02-F04.
   Правило: ни одного сырого hex/px вне шкалы. Только var(--*) из tokens.css.
   Блоки разделены комментариями по будущим EJS-партиалам.
   ========================================================================== */

/* Зазор сетки слотов зависит от плотности, но бренд-токеном не является:
   design.md описывает бренд, а не раскладку конкретного компонента. */
:root { --slot-gap: var(--space-x2); }
.density-compact { --slot-gap: var(--space-x1); }

/* --------------------------------------------------------------------------
   0. База
   -------------------------------------------------------------------------- */
*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--color-surface-page);
  color: var(--color-text-primary);
  font: var(--type-body-base);
  -webkit-font-smoothing: antialiased;
  /* Неразрывная строка (тестовое имя `Клиент1792…`, почта, адрес API) рвётся, только когда
     сама не помещается в свою колонку. На ширину колонок `break-word` не влияет — таблицы и
     короткие подписи считаются как раньше (E14-F53-T05, E14-F40-T06, E16-F03-T02). Рвётся она
     только там, где колонке позволено сжиматься: у детей флекса и сетки ниже `min-width: 0`. */
  overflow-wrap: break-word;
}

img { max-width: 100%; display: block; }

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

h1, h2, h3, h4, p, ul, ol, dl, dd, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }

button { font-family: inherit; }

:focus-visible {
  outline: 1px solid var(--color-border-focus);
  outline-offset: 0;
  box-shadow: var(--elevation-focus-ring);
  border-radius: var(--radius-sm);
}

.num { font-variant-numeric: tabular-nums; }

/* [hidden] должен побеждать display:flex/grid у компонентов */
[hidden] { display: none !important; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   1. Оболочка страницы  →  partials/header.ejs, partials/footer.ejs
   -------------------------------------------------------------------------- */
/* ШАПКА ВСЕГДА ТЁМНАЯ. Это не стилистический выбор, а следствие ассета: у логотипа
   есть только белая версия под тёмную плашку (Logo_2.png, белый на прозрачном).
   На светлой шапке он невидим. Пока брендбук не отдаст тёмную версию — шапка
   светлой быть не может. Правило и его причина продублированы в design.md.
   Фон — сплошной #2f3642, а не gradients.brand-dark: во всю ширину градиент
   вырождается в неровную заливку, и контраст пунктов пришлось бы считать к
   диапазону цветов, а не к одному. Плюс brand-dark занят primary-кнопкой. */
/* Содержимое шапки прижато к той же колонке, что и контент страницы: иначе на
   слитом тёмном блоке логотип повисает в углу, а заголовок героя начинается на
   280px правее. В compact контейнер 1440px, формула сама вырождается в обычные
   поля страницы — плотная командная шапка остаётся во всю ширину. */
.site-header {
  display: flex;
  align-items: center;
  gap: var(--space-x6);
  padding: var(--header-padding-y) var(--page-padding);
  background: var(--color-surface-inverse);
  color: var(--color-text-inverse);
  /* Липкая: меню роли — основной способ передвижения по системе, и на длинной странице
     оно должно оставаться под рукой. z-index ниже служебной панели ревью (100). */
  position: sticky;
  top: 0;
  z-index: 50;
}

/* Индикатор загрузки htmx. Свой, а не впрыснутый библиотекой: её inline-<style> запрещён
   нашим CSP (layout.ejs → meta htmx-config). */
.htmx-indicator { opacity: 0; transition: opacity var(--motion-fast); }
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator { opacity: 1; }

/* Публичный экран записи: шапка растворяется в тёмном герое — см. .hero-block.
   Липкость здесь снимается: прозрачная полоса поверх контента читалась бы как артефакт,
   а меню на публичном экране и нет — держать нечего. */
.site-header--on-hero {
  background: transparent;
  position: static;
  /* Только здесь содержимое шапки прижимается к колонке контента: на слитом тёмном блоке
     логотип иначе повисает в углу, а заголовок героя начинается на 280px правее. На рабочих
     местах ролей эта же формула зажимала меню в колонку ~895px, и восемь разделов менеджера
     распирали страницу вбок — там шапка идёт во всю ширину. */
  padding-left: max(var(--page-padding), calc((100% - var(--container-max)) / 2));
  padding-right: max(var(--page-padding), calc((100% - var(--container-max)) / 2));
}

/* flex: none — иначе на узком экране логотип сжимается вместе с флекс-строкой и словесный
   знак превращается в пятно раньше, чем сработает медиазапрос. */
.site-header__brand { display: inline-flex; align-items: center; flex: none; }
/* 48px — минимум, на котором в трёхстрочном словесном знаке читаются буквы
   (высота строки ассета ≈ 8% от его высоты). Ниже логотип превращается в пятно. */
.site-header__logo { height: var(--header-logo-height); width: auto; flex: none; }

/* Кнопка меню. На широком экране её нет: пункты помещаются строкой. */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: var(--touch-target);
  height: var(--touch-target);
  border: 1px solid var(--color-inverse-a20);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-text-inverse);
  cursor: pointer;
}
.nav-toggle:hover { background: var(--color-inverse-a12); }
.nav-toggle:focus-visible {
  outline: 1px solid var(--color-border-focus);
  box-shadow: var(--elevation-focus-ring);
}
/* Три полосы одним элементом: средняя — фон, крайние — псевдоэлементы. */
.nav-toggle__bars,
.nav-toggle__bars::before,
.nav-toggle__bars::after {
  display: block;
  width: 18px;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
}
.nav-toggle__bars { position: relative; }
.nav-toggle__bars::before,
.nav-toggle__bars::after { content: ""; position: absolute; left: 0; }
.nav-toggle__bars::before { top: -6px; }
.nav-toggle__bars::after { top: 6px; }

.site-nav {
  display: flex;
  gap: var(--space-x2);
  margin-left: auto;
}

/* Навигация размечена списком (ul/li), а не голыми ссылками: так скринридер
   объявляет число разделов. Раскладку несёт список, чтобы .site-nav сохранил
   `margin-left: auto` и прижимал навигацию вправо. */
.site-nav__list {
  display: flex;
  align-items: center;
  gap: var(--space-x2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-nav__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-x2);
  /* Пункт не переносится внутри себя: «Записи встреч» в две строки распирает шапку по
     высоте и ломает её ритм. Не помещается строка целиком — уходим в бургер (медиазапрос). */
  white-space: nowrap;
  min-height: var(--touch-target);
  padding: 0 var(--space-x4);
  border-radius: var(--radius-pill);
  color: var(--color-text-on-inverse-muted);
  font: var(--type-label-md);
  letter-spacing: var(--tracking-label-md);
  transition: background var(--motion-fast), color var(--motion-fast);
}
.site-nav__link:hover {
  background: var(--color-inverse-a12);
  color: var(--color-text-inverse);
  text-decoration: none;
}
/* Текущий раздел несут ДВА признака, а не один: заливка + циановая точка.
   Заливка сама по себе даёт к шапке лишь 1.87:1 и по WCAG 1.4.11 состояние
   идентифицировать не может; точка #00ceff даёт 6.53:1 и закрывает порог. */
.site-nav__link.is-current {
  background: var(--color-inverse-a20);
  color: var(--color-text-inverse);
}
.site-nav__link.is-current::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: var(--radius-pill);
  background: var(--color-accent-cyan-bright);
  flex: none;
}

/* Вложенные уровни меню (контур команды). У человека с полными правами разделов двадцать
   один — строкой они не помещаются ни при какой типографике. Пункт верхнего уровня это
   ГРУППА, раскрытие — нативный <details>: без JS меню остаётся рабочим, с клавиатуры
   открывается само, состояние объявляет сам. Разметка одна на оба экрана, различие несёт
   этот файл: на широком — выпадающая панель, на узком (медиазапрос ниже) — аккордеон
   внутри той же тёмной шторки. */
.site-nav__item { display: flex; }
.nav-group { position: relative; }
/* Треугольник браузера убираем двумя способами сразу: Firefox слушает list-style,
   вебкиты — свой псевдоэлемент. */
.nav-group__summary { list-style: none; cursor: pointer; }
.nav-group__summary::-webkit-details-marker { display: none; }
.nav-group__summary:focus-visible {
  outline: 1px solid var(--color-border-focus);
  box-shadow: var(--elevation-focus-ring);
}
.nav-group__label { white-space: nowrap; }
/* Галочка из двух граней квадрата: своя иконка не нужна, а поворот показывает состояние. */
.nav-group__caret {
  flex: none;
  width: 7px; height: 7px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform var(--motion-fast);
}
.nav-group[open] > .nav-group__summary .nav-group__caret { transform: translateY(2px) rotate(-135deg); }

/* Панель группы — тот же приём, что у меню под аватаркой: светлая плашка над контентом.
   Два меню в одной шапке обязаны выглядеть одинаково, поэтому токены взяты у .user-menu__list
   слово в слово. */
.nav-group__list {
  position: absolute;
  top: calc(100% + var(--space-x2));
  left: 0;
  z-index: 40;
  min-width: 220px;
  margin: 0;
  padding: var(--space-x2);
  list-style: none;
  background: var(--color-surface);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-modal);
}
/* Последние группы раскрываются ВЛЕВО: панель шириной 220px у правого края шапки иначе
   вылезает за экран и тянет горизонтальный скролл — ровно то, от чего уходим. */
.site-nav__item:nth-last-child(-n + 3) .nav-group__list { left: auto; right: 0; }
.nav-group__link {
  display: block;
  padding: var(--space-x3);
  border-radius: var(--radius-md);
  color: var(--color-text-primary);
  font: var(--type-body-md);
  text-decoration: none;
  white-space: nowrap;
}
.nav-group__link:hover { background: var(--color-surface-sunken); text-decoration: none; }
.nav-group__link:focus-visible {
  outline: 1px solid var(--color-border-focus);
  box-shadow: var(--elevation-focus-ring);
}
/* Текущий раздел внутри группы несут ДВА признака, как и в шапке: заливка и полоса слева
   (правило карточки из design.md — выбранная получает полосу 3px и фон accent-soft). */
.nav-group__link.is-current {
  background: var(--color-surface-accent-soft);
  box-shadow: inset 3px 0 0 var(--color-accent-brand);
}

.site-nav + .user-menu { margin-left: var(--space-x4); }
.site-header__brand + .user-menu { margin-left: auto; }
/* Чип СЖИМАЕТСЯ, а не распирает шапку (E14-F53-T05): при `flex: none` длинное имя клиента
   («КьюэйПовтор Клиент1792…») рядом с восемью пунктами меню давало строке шапки 1406–1490 px
   при окне 1366 — тёмная полоса обрывалась, имя резалось краем, вся страница ехала вбок.
   Сжимается только имя (многоточие), аватар и меню остаются целыми; полное имя — в title. */
.user-menu { position: relative; flex: 0 1 auto; min-width: 0; }

/* Чип стал кнопкой: сбрасываем всё, что браузер даёт кнопке по умолчанию, иначе
   на тёмной шапке появляется системная рамка и серая заливка. */
.user-chip--button {
  appearance: none;
  background: none;
  border: 0;
  padding: var(--space-x1) var(--space-x2);
  border-radius: var(--radius-md);
  cursor: pointer;
  text-align: left;
  transition: background var(--motion-fast);
}
.user-chip--button:hover { background: var(--color-inverse-a12); }
.user-chip--button:focus-visible { outline: 2px solid var(--color-accent-cyan-bright); outline-offset: 2px; }

/* Профиль — раздел без пункта в меню, поэтому текущим становится сам чип. Признаков два, как
   и у пунктов меню: заливка (к шапке всего 1.87:1, одной мало) плюс циановая кромка аватарки. */
.user-chip--button.is-current { background: var(--color-inverse-a20); }
.user-chip--button.is-current .user-chip__avatar { border-color: var(--color-accent-cyan-bright); }

.user-menu__list {
  position: absolute;
  top: calc(100% + var(--space-x2));
  right: 0;
  z-index: 40;
  min-width: 200px;
  margin: 0;
  padding: var(--space-x2);
  list-style: none;
  background: var(--color-surface);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-modal);
}
.user-menu__list[hidden] { display: none; }
.user-menu__form { margin: 0; }
.user-menu__link {
  display: block;
  width: 100%;
  padding: var(--space-x3) var(--space-x3);
  border: 0;
  border-radius: var(--radius-md);
  background: none;
  color: var(--color-text-primary);
  font: var(--type-body-md);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.user-menu__link:hover { background: var(--color-surface-sunken); }
.user-menu__link--danger { color: var(--color-danger); }

.user-chip { display: flex; align-items: center; gap: var(--space-x3); }

/* На тёмной шапке аватар — контурный, а не залитый: светло-голубой диск
   #d2e3ea дал бы к шапке 9.22:1 и перетянул бы внимание с логотипа. */
.user-chip__avatar {
  display: grid;
  place-items: center;
  width: var(--header-avatar-size); height: var(--header-avatar-size);
  border-radius: var(--radius-pill);
  background: transparent;
  border: 1px solid var(--color-inverse-a40);
  color: var(--color-text-inverse);
  font: var(--type-label-md);
  letter-spacing: var(--tracking-label-md);
}

.user-chip__body { display: flex; flex-direction: column; min-width: 0; }
.user-chip { min-width: 0; max-width: 100%; }
.user-chip__avatar { flex: none; }
.user-chip__name,
.user-chip__meta { max-width: 24ch; overflow: hidden; text-overflow: ellipsis; }
.user-chip__name { font: var(--type-label-md); letter-spacing: var(--tracking-label-md); color: var(--color-text-inverse); white-space: nowrap; }
.user-chip__meta { font: var(--type-label-sm); letter-spacing: var(--tracking-label-sm); color: var(--color-text-on-inverse-muted); white-space: nowrap; }

.site-footer {
  margin-top: auto;
  padding: var(--space-x8) var(--page-padding) var(--space-x10);
  border-top: 1px solid var(--color-border-subtle);
  color: var(--color-text-secondary);
  font: var(--type-body-xs);
}
.site-footer__inner {
  max-width: var(--container-max);
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-x2) var(--space-x6);
  align-items: center;
}
/* Золото системно живёт только в «моменте действия» (кружок на publiс-CTA,
   кольцо на отметке успеха, кольцо аватара закреплённого коуча) — один акцент
   на экран. В подвале его нет намеренно. */

.page {
  flex: 1;
  width: 100%;
  min-width: 0;
  padding: var(--space-x10) var(--page-padding) var(--space-x16);
}
.page--tight { padding-top: var(--space-x8); }

.container {
  width: 100%;
  min-width: 0;
  max-width: var(--container-max);
  margin: 0 auto;
}

.stack { display: flex; flex-direction: column; gap: var(--section-gap); }
.stack--tight { gap: var(--card-gap); }
.stack--x5 { gap: var(--space-x5); }

/* --------------------------------------------------------------------------
   2. Типографика страницы
   -------------------------------------------------------------------------- */
.page-title {
  font: var(--type-headline-lg);
  color: var(--color-text-primary);
}
.page-lead {
  margin-top: var(--space-x3);
  font: var(--type-body-lg);
  color: var(--color-text-secondary);
  max-width: 62ch;
}
.section-title { font: var(--type-title-lg); }
.section-label {
  font: var(--type-label-caps);
  letter-spacing: var(--tracking-label-caps);
  text-transform: uppercase;
  color: var(--color-text-secondary);
}
.meta { font: var(--type-body-sm); color: var(--color-text-secondary); }
.meta-xs { font: var(--type-body-xs); color: var(--color-text-secondary); }

/* --------------------------------------------------------------------------
   3. Публичный герой  →  partials/public-hero.ejs
   -------------------------------------------------------------------------- */
/* Стык «тёмная шапка + тёмный герой» решён слиянием, а не разделителем: градиент
   красит ОДИН блок, внутри которого лежат и шапка, и герой. Шва нет физически.
   Альтернативы отвергнуты: разделитель делает шов заметным вместо того, чтобы его
   убрать, и достаётся в наследство всем экранам; светлый герой инвертирует
   иерархию — хром оказывается тяжелее контента. Встык два тёмных давали бы
   #2f3642 / #326386 = 1.89:1: не граница, а грязное пятно. */
.hero-block {
  background: var(--gradient-brand-dark);
  color: var(--color-text-inverse);
  border-bottom-left-radius: var(--radius-xl);
  border-bottom-right-radius: var(--radius-xl);
}

.hero {
  padding: var(--space-x10) var(--page-padding) var(--space-x12);
}
.hero__inner { max-width: var(--container-max); margin: 0 auto; }
/* Не золотом: #decca2 на светлом стопе градиента #326386 даёт 4.06:1 — ниже AA. */
.hero__eyebrow {
  font: var(--type-label-caps);
  letter-spacing: var(--tracking-label-caps);
  text-transform: uppercase;
  color: var(--color-text-inverse);
}
.hero__title {
  margin-top: var(--space-x4);
  font: var(--type-display);
  letter-spacing: var(--tracking-display);
}
.hero__text {
  margin-top: var(--space-x4);
  font: var(--type-body-lg);
  max-width: 58ch;
  color: var(--color-text-inverse);
  opacity: 0.88;
}

.meta-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-x2);
  margin-top: var(--space-x6);
}
.meta-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-x2);
  padding: var(--space-x2) var(--space-x4);
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-inverse-a28);
  font: var(--type-label-md);
  letter-spacing: var(--tracking-label-md);
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   4. Карточка  →  partials/card.ejs
   -------------------------------------------------------------------------- */
.card {
  background: var(--color-surface-card);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--elevation-raised);
  padding: var(--card-padding);
}
.card--lg { border-radius: var(--radius-lg); padding: var(--space-x8); }
.card--sunken { background: var(--color-surface-sunken); box-shadow: var(--elevation-flat); }
.card--accent { background: var(--color-info-surface); border-color: var(--color-info-border); }

.card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-x4);
  margin-bottom: var(--space-x4);
}
.card__title { font: var(--type-title-lg); }
.card__title--sm { font: var(--type-title-md); }

/* --------------------------------------------------------------------------
   5. Кнопки  →  partials/button.ejs
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-x2);
  min-height: var(--control-height);
  padding: 0 var(--space-x6);
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  font: var(--type-label-md);
  letter-spacing: var(--tracking-label-md);
  cursor: pointer;
  text-decoration: none;
  transition: background var(--motion-fast), border-color var(--motion-fast), color var(--motion-fast);
}
.btn:hover { text-decoration: none; }

.btn--primary {
  background: var(--gradient-brand-dark);
  color: var(--color-text-on-accent);
}
.btn--primary:hover { background: var(--color-accent-brand-hover); color: var(--color-text-on-accent); }
.btn--primary:active { background: var(--color-accent-brand-active); }

.btn--secondary {
  background: var(--color-surface-card);
  color: var(--color-accent-brand);
  border-color: var(--color-border-control);
}
.btn--secondary:hover {
  background: var(--color-surface-accent-softer);
  border-color: var(--color-accent-brand);
  color: var(--color-accent-brand);
}

/* Ghost — самая тихая из кнопок, но всё-таки кнопка: рамка и та же pill-форма, что у
   остальных. Прозрачный прямоугольник без границы читался как текст, и заказчик узнавал
   в нём кнопку только по наведению мыши (замечание 30.07). */
.btn--ghost {
  background: var(--color-surface-card);
  color: var(--color-accent-brand);
  border-color: var(--color-border-subtle);
}
.btn--ghost:hover {
  background: var(--color-surface-sunken);
  color: var(--color-text-link-hover);
  border-color: var(--color-border-control);
}

/* Кнопка-знак: круглая, шириной в свою высоту. Только там, где подписи не помещаются —
   в строке таблицы. Название кнопки живёт в aria-label и title (см. components/action.ejs). */
.btn--icon {
  min-height: 40px;
  width: 40px;
  padding: 0;
  font: var(--type-title-sm);
}
.density-compact .btn--icon { min-height: 32px; width: 32px; }

.btn--danger {
  background: var(--color-surface-card);
  color: var(--color-danger);
  border-color: var(--color-danger);
}
.btn--danger:hover { background: var(--color-danger-surface); color: var(--color-danger); }

.btn--lg { min-height: 56px; padding: 0 var(--space-x8); font: var(--type-title-md); }
.btn--block { width: 100%; }

.btn[disabled],
.btn[aria-disabled="true"] {
  background: var(--color-surface-muted);
  color: var(--color-text-disabled);
  border-color: transparent;
  cursor: not-allowed;
  box-shadow: none;
  pointer-events: none;
}
/* Выключенная ссылка, которая всё же остаётся ссылкой на якорь («Подключиться» вне окна встречи,
   E04-F07-T02): вид выключенной кнопки, нажатие безвредно (якорь той же страницы) и не «проваливается»
   в карточку под ней, а курсор говорит «пока нельзя». */
.btn--inactive[aria-disabled="true"],
.btn--inactive[aria-disabled="true"]:hover {
  background: var(--color-surface-muted);
  color: var(--color-text-disabled);
  border-color: transparent;
  box-shadow: none;
  cursor: not-allowed;
  pointer-events: auto;
}

/* Золотой «кружок» — брендовая деталь, только на крупной primary публичного экрана. */
.btn__dot {
  width: 10px; height: 10px;
  border-radius: var(--radius-pill);
  background: var(--color-accent-gold);
  flex: none;
}

.btn-row { display: flex; flex-wrap: wrap; gap: var(--space-x3); align-items: center; min-width: 0; }
/* Кнопка не шире своей строки: длинная подпись («Открыть набор «QA Поток · …»») переносится
   внутри pill, а не уводит её за край карточки (E14-F53-T06). */
.btn { max-width: 100%; text-align: center; }
.inline-row { display: flex; flex-wrap: wrap; gap: var(--space-x3); align-items: center; }

/* --------------------------------------------------------------------------
   6. Бейдж статуса  →  partials/badge.ejs
   -------------------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-x2);
  padding: 2px 10px;
  border-radius: var(--radius-pill);
  background: var(--badge-bg);
  color: var(--badge-fg);
  font: var(--type-label-sm);
  letter-spacing: var(--tracking-label-sm);
  white-space: nowrap;
}
.badge::before,
.badge__pill::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: var(--radius-pill);
  background: currentColor;
  flex: none;
}
/* Тон — парой переменных, а не прямой заливкой: ту же пару берёт пилюля бейджа с пояснением. */
.badge--info { --badge-bg: var(--color-info-surface); --badge-fg: var(--color-info); }
.badge--success { --badge-bg: var(--color-success-surface); --badge-fg: var(--color-success); }
.badge--warning { --badge-bg: var(--color-warning-surface); --badge-fg: var(--color-warning); }
.badge--danger { --badge-bg: var(--color-danger-surface); --badge-fg: var(--color-danger); }
.badge--neutral { --badge-bg: var(--color-neutral-badge-surface); --badge-fg: var(--color-neutral-badge); }

/* Бейдж с пояснением (components/badge.ejs, E14-F53-T08): статус — в пилюле одной строкой,
   пояснение после « — » — обычной подписью под ней. Фраза «Отменена, сессия сохранена —
   подберём новое время» целиком в пилюле сжималась колонкой в овал на 4–5 строк. Текст
   элемента остаётся полной фразой — тире живёт в скрытом span. */
.badge--with-note {
  position: relative;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 0;
  border-radius: 0;
  background: none;
  color: var(--color-text-secondary);
  font: var(--type-body-xs);
  letter-spacing: normal;
  white-space: normal;
}
.badge--with-note::before { content: none; }
.badge__pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-x2);
  padding: 2px 10px;
  border-radius: var(--radius-pill);
  background: var(--badge-bg);
  color: var(--badge-fg);
  font: var(--type-label-sm);
  letter-spacing: var(--tracking-label-sm);
  white-space: nowrap;
}
.badge__note { max-width: 32ch; }

/* --------------------------------------------------------------------------
   7. Часовой пояс  →  partials/timezone-bar.ejs
   -------------------------------------------------------------------------- */
.tz-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-x3);
  padding: var(--space-x3) var(--space-x4);
  background: var(--color-info-surface);
  border: 1px solid var(--color-info-border);
  border-radius: var(--radius-md);
  color: var(--color-info);
  font: var(--type-body-sm);
}
.tz-bar__icon { font: var(--type-title-md); line-height: 1; }
.tz-bar__text { color: var(--color-info); }
.tz-bar__note { color: var(--color-text-secondary); }
/* Подпись поля города: прижата к самому полю, поэтому отступ «до упора» задаёт она. */
.tz-bar__label { margin-left: auto; color: var(--color-text-secondary); }
.tz-bar__label--block { display: block; margin: var(--space-x4) 0 var(--space-x2); }
.tz-bar__select {
  min-height: var(--control-height);
  padding: 0 var(--space-x3);
  border: 1px solid var(--color-border-control);
  border-radius: var(--radius-sm);
  background: var(--color-surface-card);
  color: var(--color-text-primary);
  font: var(--type-body-sm);
}

.tz-bar__select--block {
  display: block;
  width: 100%;
  margin: 0;
}

/* --------------------------------------------------------------------------
   8. Выбор коуча  →  partials/coach-option.ejs
   -------------------------------------------------------------------------- */
.coach-list { display: grid; gap: var(--space-x3); }

/* Коуч — ссылка на свой адрес, а не радиокнопка: у каждого своё расписание и свой URL,
   который можно переслать. Подчёркивание снимаем — карточка сама себе индикатор. */
.coach-option {
  position: relative;
  display: block;
  color: inherit;
  text-decoration: none;
}
.coach-option:hover { text-decoration: none; }
.coach-option__body {
  display: flex;
  align-items: center;
  gap: var(--space-x4);
  padding: var(--space-x4);
  min-height: var(--row-height);
  background: var(--color-surface-card);
  border: 1px solid var(--color-border-subtle);
  border-left: 3px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--motion-fast), background var(--motion-fast), box-shadow var(--motion-fast);
}
.coach-option__body:hover {
  border-color: var(--color-border-default);
  border-left-color: var(--color-border-default);
  box-shadow: var(--elevation-overlay);
}
.coach-option.is-selected .coach-option__body {
  background: var(--color-surface-accent-soft);
  border-color: var(--color-accent-brand);
  border-left-color: var(--color-accent-brand);
}
.coach-option:focus-visible .coach-option__body {
  outline: 1px solid var(--color-border-focus);
  box-shadow: var(--elevation-focus-ring);
}

.coach-avatar {
  display: grid;
  place-items: center;
  width: 48px; height: 48px;
  flex: none;
  border-radius: var(--radius-pill);
  background: var(--color-surface-accent-softer);
  color: var(--color-info);
  font: var(--type-title-md);
}
.coach-avatar--lg { width: 64px; height: 64px; font: var(--type-title-lg); }
.coach-avatar--gold { box-shadow: 0 0 0 2px var(--color-accent-gold); }

.coach-option__text { display: flex; flex-direction: column; gap: 2px; }
.coach-option__name { font: var(--type-title-md); }
.coach-option__meta { font: var(--type-body-sm); color: var(--color-text-secondary); }
.coach-option__side { margin-left: auto; text-align: right; }

/* --------------------------------------------------------------------------
   9. Календарь месяца  →  partials/month-calendar.ejs
   -------------------------------------------------------------------------- */
.booking-layout {
  display: grid;
  grid-template-columns: minmax(280px, 320px) 1fr;
  gap: var(--space-x8);
  align-items: start;
}

.calendar { }
.calendar__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-x2);
  margin-bottom: var(--space-x4);
}
.calendar__month { font: var(--type-title-md); }
.calendar__nav {
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border: 1px solid var(--color-border-control);
  border-radius: var(--radius-pill);
  background: var(--color-surface-card);
  color: var(--color-accent-brand);
  font: var(--type-title-md);
  cursor: pointer;
  transition: background var(--motion-fast), border-color var(--motion-fast);
}
.calendar__nav:hover { background: var(--color-surface-accent-softer); border-color: var(--color-accent-brand); }
.calendar__nav[disabled] { color: var(--color-text-disabled); border-color: var(--color-border-subtle); cursor: not-allowed; }

.calendar__weekdays,
.calendar__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--space-x1);
}
.calendar__weekdays {
  margin-bottom: var(--space-x2);
  font: var(--type-label-caps);
  letter-spacing: var(--tracking-label-caps);
  text-transform: uppercase;
  color: var(--color-text-secondary);
  text-align: center;
}

.day {
  position: relative;
  aspect-ratio: 1;
  min-height: 40px;
  display: grid;
  place-items: center;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-text-secondary);
  font: var(--type-numeric-slot);
  font-variant-numeric: tabular-nums;
  cursor: default;
}
.day--free {
  background: var(--color-surface-card);
  border-color: var(--color-border-control);
  color: var(--color-text-primary);
  cursor: pointer;
  transition: background var(--motion-fast), border-color var(--motion-fast);
}
.day--free:hover { background: var(--color-surface-accent-soft); border-color: var(--color-accent-brand); }
.day--free::after {
  content: "";
  position: absolute;
  bottom: 6px;
  width: 4px; height: 4px;
  border-radius: var(--radius-pill);
  background: var(--color-accent-cyan);
}
.day.is-selected {
  background: var(--color-accent-brand);
  border-color: var(--color-accent-brand);
  color: var(--color-text-on-accent);
}
.day.is-selected::after { display: none; }
.day--muted { color: var(--color-text-disabled); }
.day[disabled] { color: var(--color-text-disabled); cursor: not-allowed; }
/* Золото в календаре не используем: оно зарезервировано под момент действия. */
.day--today { box-shadow: inset 0 0 0 1px var(--color-border-default); }

.calendar__legend {
  margin-top: var(--space-x4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-x4);
  font: var(--type-body-xs);
  color: var(--color-text-secondary);
}
.calendar__legend span { display: inline-flex; align-items: center; gap: var(--space-x2); }
.calendar__legend i {
  width: 8px; height: 8px; border-radius: var(--radius-pill);
  background: var(--color-accent-cyan);
  display: inline-block;
}
.calendar__legend i.is-today { background: var(--color-border-default); }

/* --------------------------------------------------------------------------
   10. Слот времени — ключевой элемент  →  partials/time-slot.ejs
   -------------------------------------------------------------------------- */
.slots__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-x4);
  margin-bottom: var(--space-x5);
}
.slots__date { font: var(--type-title-lg); }
.slots__count { font: var(--type-body-sm); color: var(--color-text-secondary); }

.slots__foot { margin-top: var(--space-x5); }
.slots-group + .slots-group { margin-top: var(--space-x6); }
.slots-group__label {
  display: block;
  margin-bottom: var(--space-x3);
  font: var(--type-label-caps);
  letter-spacing: var(--tracking-label-caps);
  text-transform: uppercase;
  color: var(--color-text-secondary);
}

.slots-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: var(--slot-gap);
}

.slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 48px;
  padding: var(--space-x2);
  background: var(--color-surface-card);
  border: 1px solid var(--color-border-control);
  border-radius: var(--radius-md);
  color: var(--color-text-primary);
  cursor: pointer;
  transition: background var(--motion-fast), border-color var(--motion-fast), color var(--motion-fast),
    opacity var(--motion-base), transform var(--motion-base);
}
.slot__time {
  font: var(--type-numeric-slot);
  font-variant-numeric: tabular-nums;
}
.slot__note {
  font: var(--type-label-sm);
  letter-spacing: var(--tracking-label-sm);
  color: var(--color-text-secondary);
  text-align: center;
}
.slot:hover { background: var(--color-surface-accent-soft); border-color: var(--color-accent-brand); }
.slot.is-selected {
  background: var(--color-accent-brand);
  border-color: var(--color-accent-brand);
  color: var(--color-text-on-accent);
}
.slot.is-selected .slot__note { color: var(--color-text-on-accent); }

.slot--last { border-color: var(--color-warning); }
.slot--last .slot__note { color: var(--color-warning); }

/* Деградация: календарь коуча недоступен, занятость не проверена. */
.slot--unverified { border-style: dashed; border-color: var(--color-warning-border); }
.slot--unverified .slot__note { color: var(--color-warning); }

.slot--unavailable,
.slot[aria-disabled="true"] {
  background: var(--color-surface-sunken);
  border: 1px dashed var(--color-border-subtle);
  color: var(--color-text-disabled);
  cursor: not-allowed;
}
.slot--unavailable .slot__note { color: var(--color-text-disabled); }
.slot--unavailable:hover { background: var(--color-surface-sunken); border-color: var(--color-border-subtle); }

/* Слот уходит из выдачи — чужая бронь. */
.slot.is-vanishing {
  opacity: 0;
  transform: scale(0.94);
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   11. Липкая панель подтверждения  →  partials/sticky-confirm.ejs
   -------------------------------------------------------------------------- */
.sticky-bar {
  position: sticky;
  bottom: var(--space-x4);
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-x4);
  margin-top: var(--space-x6);
  padding: var(--space-x4) var(--space-x6);
  background: var(--color-surface-inverse);
  color: var(--color-text-inverse);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-modal);
}
.sticky-bar[hidden] { display: none; }
/* Тот же #2f3642, что и шапка — значит тот же непрозрачный токен, не opacity. */
.sticky-bar__label { font: var(--type-label-sm); letter-spacing: var(--tracking-label-sm); color: var(--color-text-on-inverse-muted); }
.sticky-bar__value {
  display: block;
  font: var(--type-title-lg);
  font-variant-numeric: tabular-nums;
}
.sticky-bar__actions { margin-left: auto; display: flex; gap: var(--space-x3); align-items: center; }
/* На тёмной плите ghost остаётся прозрачным: белая кнопка рядом с primary спорила бы с ней
   за главное действие, а рамка по светлому слою уже говорит, что это кнопка. */
.sticky-bar .btn--ghost {
  background: transparent;
  border-color: var(--color-inverse-a20);
  color: var(--color-accent-cyan-bright);
}
.sticky-bar .btn--ghost:hover {
  background: var(--color-inverse-a12);
  border-color: var(--color-inverse-a40);
  color: var(--color-accent-cyan-bright);
}

/* --------------------------------------------------------------------------
   12. Поля формы  →  partials/field.ejs
   -------------------------------------------------------------------------- */
.form { display: flex; flex-direction: column; gap: var(--field-gap); }

/* Поиск над таблицей: поле тянется, кнопка прижата к нему, подсказка уходит под строку. */
/* Токены — `--space-x*`. Прежние `--space-3` / `--space-4` в tokens.css не существуют: отступ
   карточки и зазор ряда тихо обнулялись, подпись «Найти клиента» и кнопка «Найти» упирались
   в рамку, а на 360 px кнопка уходила за край (E14-F09-T02-S01/S02). */
.card--inline { padding: var(--space-x4) var(--space-x5); }
.form--inline { flex-direction: row; align-items: flex-end; gap: var(--space-x3); flex-wrap: wrap; }
.form--inline .field { flex: 1 1 18rem; }
.form--inline .btn-row { margin: 0; }
/* В ряду поле и кнопка — ровно `--control-height` (border-box): вертикальный padding поля
   (12+12) плюс строка body-md (24,8) и рамка давали 50,8px против 48px у кнопки, и при
   `align-items: flex-end` верх поля вставал на 2,8px выше кнопки (E02-F04-T08). Текст
   в input/select по вертикали центрирует сам браузер. Textarea не трогаем — ей нужна высота. */
.form--inline input.field__control,
.form--inline select.field__control { height: var(--control-height); min-height: 0; padding-block: 0; }
/* Подсказка строчной формы — своим рядом под полем (form-block.ejs): внутри поля она удлиняла
   его, и кнопка, прижатая к низу ряда, съезжала ниже поля (E02-F04-T08). Отступ сверху — как
   у подсказки внутри поля, а не общий зазор ряда. */
.form--inline .form__hint { flex-basis: 100%; margin-top: calc(var(--space-x2) - var(--space-x3)); }
@media (max-width: 40rem) {
  /* В столбик `flex: 1` растянул бы поле по ВЫСОТЕ — между подсказкой и кнопкой
     появлялся пустой провал в пол-экрана. По вертикали поле занимает своё. */
  .form--inline { flex-direction: column; align-items: stretch; }
  .form--inline .field { flex: 0 0 auto; }
  /* В столбик подсказка возвращается под поле, кнопка — после неё. */
  .form--inline .btn-row { order: 1; }
}
/* Поиск — строчная форма из одного поля (E14-F09-T02): кнопка «Найти» стоит в одну строку с
   полем на любой ширине. Поле сжимается до 10rem; тесно и для «Сбросить» — кнопки целиком
   уходят следующей строкой, подсказка остаётся последней. */
.form--search .field { flex: 1 1 10rem; min-width: 0; }
.form--search .btn-row { flex: none; flex-wrap: nowrap; }
@media (max-width: 40rem) {
  .form--search { flex-direction: row; align-items: flex-end; }
  .form--search .field { flex: 1 1 10rem; }
  .form--search .btn-row { order: 0; }
  .form--search .btn-row .btn { padding: 0 var(--space-x4); }
}

.field { display: flex; flex-direction: column; gap: var(--space-x2); }
.field__label { font: var(--type-label-md); letter-spacing: var(--tracking-label-md); color: var(--color-text-secondary); }
.field__control {
  min-height: var(--control-height);
  padding: var(--space-x3);
  background: var(--color-surface-card);
  border: 1px solid var(--color-border-control);
  border-radius: var(--radius-sm);
  color: var(--color-text-primary);
  font: var(--type-body-md);
  width: 100%;
}
.field__control::placeholder { color: var(--color-text-disabled); }
.field__control:hover { border-color: var(--color-border-strong); }
.field__control:focus {
  outline: none;
  border-color: var(--color-border-focus);
  box-shadow: var(--elevation-focus-ring);
}
textarea.field__control { min-height: 112px; resize: vertical; font: var(--type-body-md); }
.field__hint { font: var(--type-body-xs); color: var(--color-text-secondary); }
/* Подсказка у галки — своей строкой под подписью, а не продолжением её текста. */
.field__hint--block { display: block; margin-top: 2px; }
.field__error { font: var(--type-body-xs); color: var(--color-danger); }
/* Обязательное поле (`Field.required`): звёздочка у подписи тем же `danger`, каким сервер
   назовёт незаполненное поле, — пометка заранее обещает ровно ту ошибку. Цвет — не
   единственный носитель: сам знак «*» и строка-расшифровка над полями. */
.field__required { margin-left: var(--space-x1); color: var(--color-danger); }
.form--inline .form__required-note { flex-basis: 100%; }

.checkbox { display: flex; gap: var(--space-x3); align-items: flex-start; font: var(--type-body-sm); color: var(--color-text-secondary); }
.checkbox input { margin-top: 3px; width: 18px; height: 18px; accent-color: var(--color-accent-brand); }

/* --------------------------------------------------------------------------
   13. Сводка / список пар «ключ — значение»  →  partials/summary-list.ejs
   -------------------------------------------------------------------------- */
.summary { display: grid; gap: var(--space-x3); min-width: 0; }
.summary__row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-x4);
  min-width: 0;
  padding-bottom: var(--space-x3);
  border-bottom: 1px solid var(--color-border-subtle);
}
.summary__row:last-child { border-bottom: 0; padding-bottom: 0; }
/* Ключ держит разумную ширину, значение сжимается и рвёт неразрывную строку (E14-F53-T05,
   E16-F03-T02): имя коуча `Коуч1792…caspent` или адрес `https://www.googleapis.com/…`
   выпирали за правый край карточки и вместе с ней — за край окна, а ключ «Коуч» сжимался
   в столбик по слову на шесть строк. */
.summary__key { flex: 0 1 auto; min-width: min(9rem, 40%); font: var(--type-body-sm); color: var(--color-text-secondary); }
.summary__value { flex: 0 1 auto; min-width: 0; overflow-wrap: anywhere; font: var(--type-title-md); text-align: right; font-variant-numeric: tabular-nums; }
.summary__value--plain { font: var(--type-body-md); }

/* --------------------------------------------------------------------------
   14. Статистика пакета  →  partials/stat.ejs
   -------------------------------------------------------------------------- */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--card-gap);
}
/* Дети сеток и флекс-рядов экрана сжимаются ниже своей неразрывной строки — иначе одно длинное
   слово в карточке задаёт ширину всей колонке, а с ней и странице (E14-F53-T05). */
.stat-grid > *,
.panel-grid > * { min-width: 0; }
.stat-grid--2 { grid-template-columns: repeat(2, 1fr); }
.stat { display: flex; flex-direction: column; gap: var(--space-x2); }
.stat__label {
  font: var(--type-label-caps);
  letter-spacing: var(--tracking-label-caps);
  text-transform: uppercase;
  color: var(--color-text-secondary);
}
.stat__value {
  font: var(--type-headline-md);
  font-variant-numeric: tabular-nums;
}
.stat__note { font: var(--type-body-sm); color: var(--color-text-secondary); }

/* Ширина заливки — единственное значение, приходящее из данных: передаётся
   кастомным свойством --progress (в EJS: style="--progress: <%= pct %>%"). */
.progress {
  display: block;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-muted);
  overflow: hidden;
}
.progress__fill {
  display: block;
  height: 100%;
  width: var(--progress, 0%);
  background: var(--gradient-brand-dark);
}

/* Строка фактов «остаток / оплачено до» над сеткой слотов */
.fact-row { display: flex; flex-wrap: wrap; gap: var(--space-x8); align-items: center; }
.fact { display: flex; flex-direction: column; gap: var(--space-x1); }
.fact__value { font: var(--type-title-md); font-variant-numeric: tabular-nums; }
.fact__side { margin-left: auto; }

/* --------------------------------------------------------------------------
   15. Ближайшая сессия  →  partials/next-session.ejs
   -------------------------------------------------------------------------- */
.next-session {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-x8);
  align-items: center;
}
.date-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 120px;
  padding: var(--space-x4);
  border-radius: var(--radius-lg);
  background: var(--gradient-brand-dark);
  color: var(--color-text-inverse);
}
.date-block__day { font: var(--type-display); letter-spacing: var(--tracking-display); font-variant-numeric: tabular-nums; }
.date-block__month { font: var(--type-label-md); letter-spacing: var(--tracking-label-md); }
.date-block__weekday { font: var(--type-label-sm); letter-spacing: var(--tracking-label-sm); opacity: 0.8; }

.date-block--sm { min-width: 72px; padding: var(--space-x3); border-radius: var(--radius-md); background: var(--color-surface-sunken); color: var(--color-text-primary); }
.date-block--sm .date-block__day { font: var(--type-title-lg); font-variant-numeric: tabular-nums; }
.date-block--sm .date-block__month { font: var(--type-label-sm); letter-spacing: var(--tracking-label-sm); color: var(--color-text-secondary); }

/* --------------------------------------------------------------------------
   16. Список сессий  →  partials/session-row.ejs
   -------------------------------------------------------------------------- */
.session-list { display: flex; flex-direction: column; gap: var(--card-gap); }

.session-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--space-x5);
  align-items: center;
  padding: var(--card-padding);
  background: var(--color-surface-card);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--elevation-raised);
  transition: border-color var(--motion-fast), box-shadow var(--motion-fast);
}
.session-row:hover { border-color: var(--color-border-default); box-shadow: var(--elevation-overlay); }
.session-row--past { box-shadow: var(--elevation-flat); background: var(--color-surface-page); }

.session-row__body { display: flex; flex-direction: column; gap: var(--space-x1); min-width: 0; }
.session-row__title { font: var(--type-title-md); }
.session-row__program {
  font: var(--type-label-sm);
  letter-spacing: var(--tracking-label-sm);
  color: var(--color-info);
}
.session-row__meta { font: var(--type-body-sm); color: var(--color-text-secondary); font-variant-numeric: tabular-nums; }
.session-row__side { display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-x2); }

.segmented {
  display: inline-flex;
  gap: var(--space-x1);
  padding: var(--space-x1);
  background: var(--color-surface-muted);
  border-radius: var(--radius-pill);
}
.segmented__item {
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-x5);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-secondary);
  font: var(--type-label-md);
  letter-spacing: var(--tracking-label-md);
  cursor: pointer;
  transition: background var(--motion-fast), color var(--motion-fast);
}
.segmented__item:hover { color: var(--color-text-primary); }
.segmented__item.is-active { background: var(--color-surface-card); color: var(--color-text-primary); box-shadow: var(--elevation-raised); }

/* --------------------------------------------------------------------------
   17. Баннеры  →  partials/banner.ejs
   -------------------------------------------------------------------------- */
.banner {
  display: flex;
  gap: var(--space-x4);
  padding: var(--space-x4) var(--space-x5);
  border-radius: var(--radius-sm);
  border-left: 3px solid var(--color-info);
  background: var(--color-info-surface);
}
.banner--warning { border-left-color: var(--color-warning); background: var(--color-warning-surface); }
.banner--danger { border-left-color: var(--color-danger); background: var(--color-danger-surface); }
.banner--success { border-left-color: var(--color-success); background: var(--color-success-surface); }
.banner__icon { font: var(--type-title-lg); line-height: 1.1; flex: none; }
.banner--warning .banner__icon { color: var(--color-warning); }
.banner--danger .banner__icon { color: var(--color-danger); }
.banner--success .banner__icon { color: var(--color-success); }
.banner--info .banner__icon,
.banner .banner__icon { color: var(--color-info); }
.banner--warning .banner__icon { color: var(--color-warning); }
.banner__body { display: flex; flex-direction: column; gap: var(--space-x2); }
.banner__actions { margin-left: auto; align-self: center; flex: none; }
.banner__title { font: var(--type-title-sm); color: var(--color-text-primary); }
.banner__text { font: var(--type-body-sm); color: var(--color-text-secondary); }
.banner[hidden] { display: none; }

/* Появление — только сдвигом, без прозрачности (E14-F09-T05): с `opacity: 0` на старте плашка
   при подмене экрана и при остановленной анимации (фоновая вкладка, снимок) оставалась почти
   невидимой. Текст читается в любой момент анимации и в покое. */
.banner.is-appearing { animation: banner-in var(--motion-slow) both; }
@keyframes banner-in {
  from { transform: translateY(-6px); }
  to { transform: none; }
}
/* Итог действия читается целиком: текст успеха — основным цветом, а не вторичным (AA на
   светло-зелёной подложке с запасом), рамка слева — заметнее. */
.banner--success { border-left-width: 4px; }
.banner--success .banner__text { color: var(--color-text-primary); }

/* --------------------------------------------------------------------------
   18. Пустое состояние  →  partials/empty-state.ejs
   -------------------------------------------------------------------------- */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-x4);
  padding: var(--space-x16) var(--space-x6);
}
/* Иконка — тонкая обводка 48 px (design.md → «Пустое состояние»), рисуется SVG. Прежний знак
   «—» в шрифте заголовка читался тонкой горизонтальной чертой, а не иконкой (E14-F53-T03). */
.empty__icon { display: inline-flex; line-height: 0; color: var(--color-border-default); }
.empty__icon svg { width: 48px; height: 48px; }
.empty__title { font: var(--type-title-md); color: var(--color-text-primary); }
.empty__text { font: var(--type-body-md); color: var(--color-text-secondary); max-width: 44ch; }

/* --------------------------------------------------------------------------
   19. Загрузка (скелетоны)  →  partials/slots-skeleton.ejs
   -------------------------------------------------------------------------- */
.skeleton {
  background: var(--color-surface-muted);
  border-radius: var(--radius-md);
  animation: skeleton-pulse 1.2s ease-in-out infinite;
}
.skeleton--slot { min-height: 48px; }
.skeleton--day { aspect-ratio: 1; min-height: 40px; }
.skeleton--line { height: 14px; border-radius: var(--radius-sm); }
.skeleton--line-lg { height: 22px; border-radius: var(--radius-sm); }
.skeleton--w40 { width: 40%; }
.skeleton--w60 { width: 60%; }

@keyframes skeleton-pulse {
  0%, 100% { opacity: 0.6; }
  50% { opacity: 1; }
}

.loading-note {
  display: flex;
  align-items: center;
  gap: var(--space-x2);
  font: var(--type-body-sm);
  color: var(--color-text-secondary);
}
.spinner {
  width: 16px; height: 16px;
  border: 2px solid var(--color-surface-muted);
  border-top-color: var(--color-accent-brand);
  border-radius: var(--radius-pill);
  animation: spin 0.8s linear infinite;
  flex: none;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* --------------------------------------------------------------------------
   20. Экран успеха
   -------------------------------------------------------------------------- */
.success-mark {
  width: 96px; height: 96px;
  margin: 0 auto;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  background: var(--color-success-surface);
  border: 2px solid var(--color-accent-gold);
  color: var(--color-success);
}
.success-title {
  margin-top: var(--space-x6);
  font: var(--type-display);
  letter-spacing: var(--tracking-display);
  text-align: center;
}
.success-lead {
  margin-top: var(--space-x4);
  font: var(--type-body-lg);
  color: var(--color-text-secondary);
  text-align: center;
}

/* Карточка назначенной встречи. Тот же приём, что у «ближайшей встречи» в кабинете
   (.next-session + .date-block): слева тёмный блок «когда», справа — с кем и на каких
   условиях. Своя разметка, а не .date-block, по одной причине: публичный экран отдаёт день
   готовой строкой («Вторник, 12 августа»), а .date-block собирается из отдельных
   день/месяц/дня недели, которых в PublicBookingView нет. */
.booked { display: grid; grid-template-columns: auto 1fr; gap: var(--space-x8); align-items: center; }
.booked__when {
  display: flex;
  flex-direction: column;
  gap: var(--space-x1);
  min-width: 200px;
  padding: var(--space-x5);
  border-radius: var(--radius-lg);
  background: var(--gradient-brand-dark);
  color: var(--color-text-inverse);
  text-align: center;
}
.booked__time { font: var(--type-display); letter-spacing: var(--tracking-display); font-variant-numeric: tabular-nums; }
.booked__date { font: var(--type-label-md); letter-spacing: var(--tracking-label-md); }

.steps { counter-reset: step; display: flex; flex-direction: column; gap: var(--space-x5); }
.steps__item { display: flex; gap: var(--space-x4); }
.steps__item::before {
  counter-increment: step;
  content: counter(step);
  flex: none;
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border-radius: var(--radius-pill);
  background: var(--color-surface-accent-softer);
  color: var(--color-info);
  font: var(--type-label-md);
  font-variant-numeric: tabular-nums;
}
.steps__title { font: var(--type-title-sm); }
.steps__text { font: var(--type-body-sm); color: var(--color-text-secondary); }

.link-box {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-x3);
  padding: var(--space-x4);
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  font: var(--type-body-sm);
  word-break: break-all;
}

/* --------------------------------------------------------------------------
   21. Карта прототипа (index)
   -------------------------------------------------------------------------- */
.map-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--card-gap);
}
.map-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-x2);
  padding: var(--card-padding);
  background: var(--color-surface-card);
  border: 1px solid var(--color-border-subtle);
  border-left: 3px solid var(--color-accent-brand);
  border-radius: var(--radius-md);
  box-shadow: var(--elevation-raised);
  transition: box-shadow var(--motion-fast), border-color var(--motion-fast);
}
.map-card:hover { box-shadow: var(--elevation-overlay); text-decoration: none; border-color: var(--color-border-default); border-left-color: var(--color-accent-brand); }
.map-card--state { border-left-color: var(--color-warning); }
.map-card__slug {
  font: var(--type-label-caps);
  letter-spacing: var(--tracking-label-caps);
  text-transform: uppercase;
  color: var(--color-text-secondary);
}
.map-card__title { font: var(--type-title-md); color: var(--color-text-primary); }
.map-card__text { font: var(--type-body-sm); color: var(--color-text-secondary); }

/* --------------------------------------------------------------------------
   22. Служебная панель прототипа — В ПРОДУКТ НЕ ПЕРЕНОСИТСЯ
   -------------------------------------------------------------------------- */
.proto-toggle {
  position: fixed;
  left: var(--space-x4);
  bottom: var(--space-x4);
  z-index: 100;
  min-height: 40px;
  padding: 0 var(--space-x4);
  border: 1px solid var(--color-accent-gold);
  border-radius: var(--radius-pill);
  background: var(--color-surface-inverse);
  color: var(--color-accent-gold);
  font: var(--type-label-sm);
  letter-spacing: var(--tracking-label-sm);
  cursor: pointer;
  box-shadow: var(--elevation-modal);
}
.proto-panel {
  position: fixed;
  left: var(--space-x4);
  bottom: 64px;
  z-index: 100;
  width: 280px;
  max-height: 70vh;
  overflow-y: auto;
  padding: var(--space-x4);
  background: var(--color-surface-inverse);
  color: var(--color-text-inverse);
  border-radius: var(--radius-md);
  box-shadow: var(--elevation-modal);
}
.proto-panel[hidden] { display: none; }
.proto-panel__title {
  font: var(--type-label-caps);
  letter-spacing: var(--tracking-label-caps);
  text-transform: uppercase;
  color: var(--color-accent-gold);
  margin-bottom: var(--space-x3);
}
.proto-panel__note { font: var(--type-body-xs); opacity: 0.7; margin-bottom: var(--space-x4); }
.proto-panel a {
  display: block;
  padding: var(--space-x2) var(--space-x3);
  border-radius: var(--radius-sm);
  color: var(--color-text-inverse);
  font: var(--type-body-sm);
}
.proto-panel a:hover { background: var(--color-inverse-a12); text-decoration: none; }
.proto-panel a.is-current { background: var(--color-inverse-a12); color: var(--color-accent-cyan-bright); }
.proto-panel__group { margin-bottom: var(--space-x4); }
/* Подпись у пункта панели: чем этот человек отличается — «пакет исчерпан», «нет окон». */
.proto-panel__hint { opacity: 0.6; }

/* --------------------------------------------------------------------------
   23. Адаптив
   -------------------------------------------------------------------------- */
@media (max-width: 1024px) {
  .booking-layout { grid-template-columns: 1fr; gap: var(--space-x6); }
  .stat-grid { grid-template-columns: 1fr 1fr; }
}

/* Порог схлопывания меню считается по самой широкой строке. Раньше это была роль менеджера
   с восемью разделами: её строка (логотип + меню + чип) занимала 1286px, поэтому 1280px давал
   ровно 1px перелива и горизонтальный скролл на всю страницу. С группировкой (E14-F33-T02)
   верхний уровень не растёт от числа выданных разделов вовсе: их семь у любых прав — шесть
   групп и «Помощь». Порог оставлен прежним, с запасом: добавленная группа не должна
   воспроизвести ту же ошибку на пиксель. */
@media (max-width: 1360px) {
  /* Меню роли уезжает под кнопку. Семь-восемь пунктов в строку не помещаются ни при какой
     типографике, а перенос на две-три строки ломает высоту шапки и сдвигает контент.
     Разметка та же — переключается только раскладка. */
  .site-header { gap: var(--space-x4); }
  .site-header__brand { margin-right: auto; }
  .nav-toggle { display: inline-flex; order: 3; }
  .user-menu { order: 2; }
  .site-nav + .user-menu,
  .site-header__brand + .user-menu { margin-left: 0; }

  .site-nav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin-left: 0;
    padding: var(--space-x3) var(--page-padding) var(--space-x4);
    background: var(--color-surface-inverse);
    border-bottom-left-radius: var(--radius-lg);
    border-bottom-right-radius: var(--radius-lg);
    box-shadow: var(--elevation-modal);
  }
  .site-nav.is-open { display: block; }
  /* Шторка не длиннее экрана: у полных прав это семь групп, и раскрытые они не помещаются
     в 100vh — прокрутка внутри шторки, а не вместе со страницей под ней. */
  .site-nav.is-open { max-height: calc(100vh - 100%); overflow-y: auto; }
  .site-nav__list { flex-direction: column; align-items: stretch; gap: var(--space-x1); }
  .site-nav__link { width: 100%; }

  /* Группы на узком экране — АККОРДЕОН внутри шторки, а не вторая всплывающая панель:
     светлая плашка поверх тёмной шторки читалась бы как чужой слой, а места под неё нет. */
  .site-nav__item { display: block; }
  .nav-group { position: static; }
  .nav-group__summary { justify-content: space-between; }
  .nav-group__list {
    position: static;
    min-width: 0;
    margin: var(--space-x1) 0 var(--space-x2) var(--space-x4);
    padding: 0 0 0 var(--space-x3);
    background: transparent;
    border: 0;
    border-left: 1px solid var(--color-inverse-a20);
    border-radius: 0;
    box-shadow: none;
  }
  .nav-group__link {
    display: flex;
    align-items: center;
    min-height: var(--touch-target);
    padding: 0 var(--space-x4);
    border-radius: var(--radius-pill);
    color: var(--color-text-on-inverse-muted);
    font: var(--type-label-md);
    letter-spacing: var(--tracking-label-md);
  }
  .nav-group__link:hover { background: var(--color-inverse-a12); color: var(--color-text-inverse); }
  /* На тёмном признаки текущего те же, что у пунктов шапки: заливка a20 даёт 1.87:1 и одна
     состояние не передаёт (WCAG 1.4.11) — второй признак несёт циановая точка. */
  .nav-group__link.is-current {
    background: var(--color-inverse-a20);
    color: var(--color-text-inverse);
    box-shadow: none;
    gap: var(--space-x2);
  }
  .nav-group__link.is-current::before {
    content: "";
    width: 6px; height: 6px;
    border-radius: var(--radius-pill);
    background: var(--color-accent-cyan-bright);
    flex: none;
  }
}

@media (max-width: 768px) {
  .user-chip__body { display: none; }
  /* Пара «ключ — значение» в строку требует места под обе колонки. На 390px длинный ключ
     («Кто здесь работает») сжимается в столбик по слову, а значение уезжает за карточку.
     Кладём их друг под друга. */
  .summary__row { flex-direction: column; align-items: flex-start; gap: var(--space-x1); }
  .summary__value { text-align: left; }
  .hero-block { border-bottom-left-radius: var(--radius-lg); border-bottom-right-radius: var(--radius-lg); }
  .hero { padding: var(--space-x6) var(--page-padding) var(--space-x8); }
  .hero__title { font: 400 36px/1.15 var(--font-display); }
  .success-title { font: 400 36px/1.15 var(--font-display); }
  .page-title { font: var(--type-headline-md); }
  .next-session { grid-template-columns: 1fr; gap: var(--space-x5); }
  /* Блок «когда» на 390px занимает всю ширину: рядом с ним второй колонке места нет,
     а 48px времени в узкой колонке переносятся по цифрам. */
  .booked { grid-template-columns: 1fr; gap: var(--space-x5); }
  .booked__when { min-width: 0; }
  .date-block { flex-direction: row; gap: var(--space-x3); align-items: baseline; justify-content: flex-start; }
  .session-row { grid-template-columns: auto 1fr; }
  .session-row__side { grid-column: 1 / -1; flex-direction: row; align-items: center; justify-content: space-between; }
  .stat-grid { grid-template-columns: 1fr; }
  .card--lg { padding: var(--space-x5); }
  .tz-bar__label { margin-left: 0; }
  .tz-bar__select { width: 100%; }
  /* Бейдж «Ближайшее — 4 августа» не переносится (white-space: nowrap) и на 390px
     распирал строку коуча — страница уезжала вбок. Роняем бейдж на свою строку. */
  .coach-option__body { flex-wrap: wrap; }
  .coach-option__text { min-width: 0; }
  .coach-option__side { margin-left: 0; width: 100%; text-align: left; }
  .proto-panel { width: calc(100vw - var(--space-x8)); }
}

@media (max-width: 480px) {
  .slots-grid { grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .skeleton { animation: none; opacity: 0.8; }
}

/* --------------------------------------------------------------------------
   Дополнения шелла (перенесены из базового app.css каркаса)
   -------------------------------------------------------------------------- */

/* Ссылка «к содержимому» — первый таб-стоп на каждой странице. */
.skip-link { position: absolute; left: -100vw; }
.skip-link:focus {
  position: static;
  display: inline-block;
  padding: var(--space-x2) var(--space-x4);
  background: var(--color-surface-card);
}

/* Пустое состояние шелла: посадочная страница и экран ошибки. */
.empty-state { text-align: center; padding: var(--section-gap) 0; }
.empty-state__title { margin: 0 0 var(--space-x4); font: var(--type-headline-lg); }
.empty-state__text {
  margin: 0 auto var(--space-x6);
  max-width: 62ch;
  color: var(--color-text-secondary);
  font: var(--type-body-md);
}

/* --------------------------------------------------------------------------
   24. Рабочее место роли  →  views/screen.ejs
   Один слой на все тридцать разделов: шапка экрана, вкладки, фильтры, таблица,
   боковые панели. Отдельная вёрстка на раздел — это как раз то, из-за чего
   первый раунд прототипа развалился на галерею несогласованных экранов.
   -------------------------------------------------------------------------- */

.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-x6);
  flex-wrap: wrap;
}
/* Ряд действий сжимается (E06-F04-T06-S01, записка #128): при `flex: none` его ширина равна
   ширине содержимого, и предложение у кнопки в одну строку выталкивало соседние кнопки за
   край окна 1024–1440 px. Сжимаемый ряд переносит кнопки внутри себя. */
.page-head__actions { flex: 0 1 auto; min-width: 0; padding-top: var(--space-x2); }
/* Заголовок сжимается и переносит неразрывное имя («ДлиннаяФамилияБезПробелов1792…»), а не
   растягивает экран шире телефона (E14-F40-T06, 534 px при окне 390). */
.page-head > :first-child { min-width: 0; max-width: 100%; }
.page-title-row > .page-title { min-width: 0; }
.page-title, .page-lead { overflow-wrap: anywhere; }
/* Кнопка-знак у заголовка (`Screen.titleAction`, карандаш переименования). Ряд не переносится:
   длинное название ломается внутри себя, а знак остаётся при его первой строке, а не уезжает
   под заголовок отдельной строкой. */
.page-title-row { display: flex; align-items: flex-start; gap: var(--space-x3); }
.page-title-row .btn--icon { flex: none; }

/* Вкладки подразделов (профиль). Не сегмент-контрол: пунктов бывает четыре и они
   длинные, а сегменты рассчитаны на два-три коротких. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-x1);
  border-bottom: 1px solid var(--color-border-subtle);
}
.tabs__item {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: 0 var(--space-x4);
  border-bottom: 2px solid transparent;
  color: var(--color-text-secondary);
  font: var(--type-label-md);
  letter-spacing: var(--tracking-label-md);
}
.tabs__item:hover { color: var(--color-text-primary); text-decoration: none; }
.tabs__item.is-active {
  color: var(--color-text-primary);
  border-bottom-color: var(--color-accent-brand);
}

/* Фильтр списка. Активный несут два признака — заливка и рамка: одной заливкой
   состояние не идентифицируется (WCAG 1.4.11), как и в пунктах меню. */
.chips { display: flex; flex-wrap: wrap; gap: var(--space-x2); }
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-x2);
  min-height: 36px;
  padding: 0 var(--space-x4);
  border: 1px solid var(--color-border-control);
  border-radius: var(--radius-pill);
  background: var(--color-surface-card);
  color: var(--color-text-secondary);
  font: var(--type-label-sm);
  letter-spacing: var(--tracking-label-sm);
}
.chip:hover { border-color: var(--color-border-strong); color: var(--color-text-primary); text-decoration: none; }
.chip.is-active {
  background: var(--color-surface-accent-soft);
  border-color: var(--color-accent-brand);
  color: var(--color-accent-brand);
}
.chip__count {
  padding: 0 var(--space-x2);
  border-radius: var(--radius-pill);
  background: var(--color-surface-muted);
  color: var(--color-text-secondary);
}
.chip.is-active .chip__count { background: var(--color-surface-card); color: var(--color-accent-brand); }

/* Карточка-таблица: рамку и тень несёт карточка, поля обнуляются — иначе у таблицы
   появляется второй внутренний отступ и строки перестают доходить до края. */
.card--table { position: relative; padding: 0; overflow-x: auto; overflow-y: hidden; max-width: 100%; }
/* position: relative — чтобы карточка была контейнером для absolute-потомков: без него
   `.visually-hidden` в заголовке «Действия» привязан к окну, обходит прокрутку карточки и растягивает
   всю страницу (1422 px при окне 1024–1366, E06-F09-T12). */
/* Аварийная подпись в таблице (строка-сирота «нет учётной записи — задайте почту») переносится
   внутри своей ячейки: одной строкой она ~300 px, растягивала таблицу шире карточки, и хвост
   «задайте почту» — то, что подсказывает действие, — обрезался на 1024–1439 px и на телефоне
   (E06-F09-T12, E06-F04-T06-S01). Короткие бейджи других состояний остаются в одну строку. */
.table .badge--danger { white-space: normal; max-width: 100%; align-items: flex-start; text-align: left; }
.table .badge--danger::before { margin-top: 0.4em; }
/* Длинный бейдж любого тона («Отменена, сессия сохранена — подберём новое время») тоже
   переносится в своей ячейке, а не уезжает за край карточки (E14-F09-T05, 1280 и 390 px).
   Короткий бейдж переносить нечего — он остаётся одной строкой.
   Перенос — ПО СЛОВАМ (`break-word`), не `anywhere`: `anywhere` разрешает разрыв в любом месте
   и при расчёте минимальной ширины колонки, поэтому авто-таблица сжимала колонку статуса до
   одной буквы и «Показан» / «Скрыт» вставали в столбик по букве (E14-F09-T12, 1280 px).
   `break-word` рвёт слово только когда оно само не помещается, а ширину колонки считает по
   самому длинному слову. */
.table .badge { white-space: normal; max-width: 100%; align-items: flex-start; text-align: left; overflow-wrap: break-word; }
/* Неразрывная строка — длинная почта, имя одним словом — распирала таблицу клиентов шире
   карточки (E14-F40-T06). Ей разрыв разрешён где угодно, но ТОЛЬКО по флагу `Cell.wrap`:
   `anywhere` на всех ячейках сжал бы короткие колонки до одной буквы (E14-F09-T12, см. выше).
   Почта при этом ломается перед `@` — шаблон ставит там `<wbr>`. */
.table .table__cell--wrap { overflow-wrap: anywhere; min-width: 0; }
/* Первая колонка — главный текст строки (название встречи, имя): у неё самая длинная строка и
   самая большая доля ширины, поэтому разрыв неразрывного токена («Программа 1792…caspent»)
   здесь безопасен и не даёт ему вытолкнуть колонку действий за край карточки (E14-F53-T05). */
.table td:first-child .table__value { overflow-wrap: anywhere; }
.table .badge::before { margin-top: 0.4em; }
/* На компьютере статус в таблице — одной строкой (E14-F53-T02/T08): пояснение длинного бейджа
   уже вынесено из пилюли (components/badge.ejs), а короткий бейдж в две строки («отмена
   запрошена · / встреча отменена») читается как сбой. Колонка растёт по бейджу; не хватает
   места — таблица прокручивается внутри своей карточки, страница не едет. */
@media (min-width: 1025px) {
  .table .badge:not(.badge--with-note) { white-space: nowrap; align-items: center; }
  .table .badge:not(.badge--with-note)::before { margin-top: 0; }
}
.table .badge--with-note::before { content: none; }
/* Ниже 1025 px бейдж может перенестись: тогда он скруглённый прямоугольник, а не раздутый овал. */
@media (max-width: 1024px) {
  .table .badge:not(.badge--with-note) { border-radius: var(--radius-md); }
}
.card--danger { border-color: var(--color-danger); }

/* Полоса листания под карточкой списка (E14-F09-T01). Навигация, а не данные: без рамки
   карточки, по правому краю, кнопки той же pill-формы, что и остальные. */
.pager {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: var(--space-x2);
  margin-top: calc(-1 * var(--space-x2));
}
.pager__item {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  min-height: 40px;
  padding: 0 var(--space-x3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border-subtle);
  background: var(--color-surface-card);
  color: var(--color-accent-brand);
  font: var(--type-label-md);
  letter-spacing: var(--tracking-label-md);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  transition: background var(--motion-fast), border-color var(--motion-fast), color var(--motion-fast);
}
a.pager__item:hover { background: var(--color-surface-sunken); border-color: var(--color-border-control); color: var(--color-text-link-hover); text-decoration: none; }
.pager__step { padding: 0 var(--space-x4); }
.pager__item.is-current { background: var(--gradient-brand-dark); border-color: transparent; color: var(--color-text-on-accent); }
.pager__item.is-disabled { background: transparent; border-color: transparent; color: var(--color-text-disabled); cursor: default; }
.pager__gap { min-width: 24px; text-align: center; color: var(--color-text-secondary); }
@media (max-width: 480px) {
  /* Номера — одной строкой по центру, шаги «назад/вперёд» — второй строкой пополам:
     девять клеток в 358 px не помещаются, а перенос посреди номеров читался бы как сбой. */
  .pager { justify-content: center; }
  .pager__step { order: 1; flex: 1 1 40%; }
}

/* Превью картинки в панели (E14-F09-T07): обложка продукта, баннер трека. */
.panel__image {
  display: block;
  width: 100%;
  max-height: 240px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  background: var(--color-surface-sunken);
  margin-bottom: var(--space-x3);
}

.table { width: 100%; border-collapse: collapse; }
.table th {
  padding: var(--space-x3) var(--space-x5);
  text-align: left;
  background: var(--color-surface-sunken);
  border-bottom: 1px solid var(--color-border-subtle);
  color: var(--color-text-secondary);
  font: var(--type-label-caps);
  letter-spacing: var(--tracking-label-caps);
  text-transform: uppercase;
  white-space: nowrap;
}
.table td {
  padding: var(--space-x4) var(--space-x5);
  border-bottom: 1px solid var(--color-border-subtle);
  font: var(--type-body-sm);
  vertical-align: top;
}
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:hover { background: var(--color-surface-sunken); }
.table__text { display: inline; }
.table__sub { display: block; margin-top: 2px; font: var(--type-body-xs); color: var(--color-text-secondary); }
.table__cell--num { text-align: right; font-variant-numeric: tabular-nums; }
/* Центр колонки — на подписи и на значениях сразу: `--num` прижимает значение вправо, а `.table th`
   держит подпись слева, и узкая колонка распадается на две несвязанные полосы (замечание заказчика
   03.09). Вес 0-2-0 обязателен: одного класса (0-1-0) хватает против `--num`, но не против
   `.table th` (0-1-1) — с ним центрировались значения, а подписи оставались слева. */
.table .table__cell--center { text-align: center; }
.table__cell--muted { color: var(--color-text-secondary); }
/* Кнопки строки не переносят подпись, но сами встают во вторую строку, когда колонке тесно:
   с `nowrap` на всей ячейке три действия («Подключиться», два знака) задавали колонке ширину
   ряда целиком, таблица уходила вбок внутри карточки, и действия оказывались за её краем. */
.table__cell--actions { text-align: right; }
.table__cell--actions .btn { white-space: nowrap; }
.table__cell--actions .btn { min-height: 36px; padding: 0 var(--space-x4); font: var(--type-label-sm); letter-spacing: var(--tracking-label-sm); }
.table__note { padding: var(--space-x3) var(--space-x5) var(--space-x4); }
/* Строка, требующая вмешательства: цвет + левая кромка, чтобы признак читался
   и в чёрно-белой печати расписания. Кромка — border, а не inset-тень: тень у ячейки
   таблицы с border-collapse приходится на границу соседней и почти не видна. */
.table__row--danger td:first-child,
.table__row--warning td:first-child,
.table__row--success td:first-child { padding-left: calc(var(--space-x5) - 3px); }
.table__row--danger td:first-child { border-left: 3px solid var(--color-danger); }
.table__row--warning td:first-child { border-left: 3px solid var(--color-warning); }
.table__row--success td:first-child { border-left: 3px solid var(--color-success); }

/* Форма-обёртка вокруг одной кнопки не должна ломать строку кнопок. */
.action-form { display: inline-flex; margin: 0; }
/* Предложение у кнопки — узкая колонка, а не строка во весь ряд: без предела оно задаёт
   ширину всему ряду действий (записка #128). */
.action__note { display: block; max-width: 40ch; }
/* В колонке действий ячейка держит кнопки одной строкой (`nowrap`), и предложение наследовало
   это: 40ch упирались в край, хвост уезжал за карточку (E14-F09-T05). Предложение — абзац. */
.table__cell--actions .action__note { white-space: normal; }

/* Перетаскивание строк (E16-F05-T11, public/js/sortable.js). Ручка видна только у таблицы,
   которую пометил скрипт: без него порядок меняют кнопки «↑» и «↓». `touch-action: none` —
   палец на ручке тянет строку, а не листает страницу. */
.sort-handle { display: none; }
.table.is-sortable .sort-handle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 36px;
  min-height: 36px;
  margin-right: var(--space-x1);
  vertical-align: middle;
  color: var(--color-text-secondary);
  cursor: grab;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  border-radius: var(--radius-sm);
}
.table.is-sortable .sort-handle:hover { background: var(--color-surface-sunken); color: var(--color-text-primary); }
.table.is-dragging-rows { user-select: none; -webkit-user-select: none; cursor: grabbing; }
.table.is-dragging-rows .sort-handle { cursor: grabbing; }
.table tbody tr.is-dragging { background: var(--color-surface-sunken); outline: 2px solid var(--color-border-control); outline-offset: -2px; }
.table.is-saving { opacity: 0.7; }
.sort-status { padding: var(--space-x2) var(--space-x5) 0; color: var(--color-text-secondary); }
.sort-status--error { color: var(--color-danger); }

.panel-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: var(--card-gap);
  align-items: start;
}
.panel__text { margin-bottom: var(--space-x4); }
.panel__actions { margin-top: var(--space-x5); }
.summary__note { display: block; font: var(--type-body-xs); color: var(--color-text-disabled); }

.state-block--error { border-color: var(--color-warning-border); background: var(--color-warning-surface); }
.state-block--denied { background: var(--color-surface-sunken); }
.state-block .empty { padding: var(--space-x10) var(--space-x6); }
.state-block--error .empty__icon { color: var(--color-warning); }

.stat-grid--4 { grid-template-columns: repeat(4, 1fr); }

/* Расписание коуча: недельная сетка. День — колонка, окна — карточки внутри. */
.week-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--space-x2);
}
.week-day {
  display: flex;
  flex-direction: column;
  gap: var(--space-x2);
  min-height: 160px;
  padding: var(--space-x3);
  background: var(--color-surface-card);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
}
.week-day--off { background: var(--color-surface-sunken); }
.week-day__label {
  font: var(--type-label-sm);
  letter-spacing: var(--tracking-label-sm);
  color: var(--color-text-secondary);
  text-transform: uppercase;
}
.week-day__date { font: var(--type-title-md); font-variant-numeric: tabular-nums; }
.week-slot {
  display: block;
  padding: var(--space-x2);
  border-radius: var(--radius-sm);
  background: var(--color-surface-accent-softer);
  border-left: 3px solid var(--color-accent-brand);
  color: var(--color-text-primary);
  font: var(--type-body-xs);
  font-variant-numeric: tabular-nums;
}
.week-slot:hover { text-decoration: none; background: var(--color-surface-accent-soft); }
.week-slot--booked { background: var(--color-surface-muted); border-left-color: var(--color-text-secondary); }
.week-slot--group { border-left-color: var(--color-info); background: var(--color-info-surface); }
/* Чужая занятость — заштрихованная полоса: это не окно системы, а факт из календаря коуча. */
.week-slot--busy {
  background: repeating-linear-gradient(
    135deg,
    var(--color-surface-sunken),
    var(--color-surface-sunken) 6px,
    var(--color-surface-muted) 6px,
    var(--color-surface-muted) 12px
  );
  border-left-color: var(--color-border-control);
  color: var(--color-text-secondary);
}
.week-slot__note { display: block; color: var(--color-text-secondary); }
.week-day__add {
  margin-top: auto;
  padding: var(--space-x2);
  border: 1px dashed var(--color-border-control);
  border-radius: var(--radius-sm);
  color: var(--color-text-secondary);
  font: var(--type-body-xs);
  text-align: center;
}
.week-day__add:hover { border-color: var(--color-accent-brand); color: var(--color-accent-brand); text-decoration: none; }

.week-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-x2) var(--space-x6);
  font: var(--type-body-xs);
  color: var(--color-text-secondary);
}
.week-legend__item { display: inline-flex; align-items: center; gap: var(--space-x2); }
.week-legend__mark {
  width: 14px;
  height: 14px;
  border-radius: 3px;
  border-left: 3px solid var(--color-accent-brand);
  background: var(--color-surface-accent-softer);
  flex: none;
}
.week-legend__mark--booked { background: var(--color-surface-muted); border-left-color: var(--color-text-secondary); }
.week-legend__mark--group { background: var(--color-info-surface); border-left-color: var(--color-info); }

@media (max-width: 1024px) {
  .week-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stat-grid--4 { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 768px) {
  .page-head { flex-direction: column; gap: var(--space-x4); }
  .page-head__actions { padding-top: 0; }
  .stat-grid--4 { grid-template-columns: 1fr; }
  .week-grid { grid-template-columns: 1fr; }
  .week-day { min-height: 0; }

  /* Таблица на телефоне становится списком карточек: горизонтальный скролл в
     таблице означает, что половина колонок не видна, а колонок здесь до шести.
     Подпись колонки приезжает в строку из data-label. */
  .table thead { display: none; }
  .table, .table tbody, .table tr, .table td { display: block; width: 100%; }
  .table tr {
    padding: var(--space-x4) var(--space-x5);
    border-bottom: 1px solid var(--color-border-subtle);
  }
  .table tbody tr:last-child { border-bottom: 0; }
  .table td {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-x4);
    padding: var(--space-x1) 0;
    border-bottom: 0;
  }
  .table td::before {
    content: attr(data-label);
    flex: none;
    max-width: 40%;
    font: var(--type-label-sm);
    letter-spacing: var(--tracking-label-sm);
    color: var(--color-text-secondary);
    text-transform: uppercase;
  }
  .table td:empty { display: none; }
  /* Значение занимает вторую колонку целиком и прижимается вправо: подпись слева,
     значение справа — так строка читается как пара, а не как три столбца. */
  .table__value { flex: 1; min-width: 0; text-align: right; }
  /* Центрирование сюда не переезжает: на телефоне колонок нет, есть пары «подпись — значение»,
     и центрировать в паре нечего. Вес держим тот же 0-2-0, что у десктопного правила: медиазапрос
     сам веса не добавляет, и при 0-1-0 центр перебил бы этот блок на телефоне. Спор равных весов
     решает порядок — этот блок ниже по файлу. */
  .table .table__cell--num, .table .table__cell--center, .table .table__cell--actions { text-align: right; }
  .table__sub { text-align: right; }
  .table__row--danger td:first-child,
  .table__row--warning td:first-child,
  .table__row--success td:first-child { border-left: 0; padding-left: 0; }
  .table__row--danger { border-left: 3px solid var(--color-danger); }
  .table__row--warning { border-left: 3px solid var(--color-warning); }
  .table__row--success { border-left: 3px solid var(--color-success); }
  /* Действия строки на телефоне (E14-F53-T06): подпись «Действия» — своей строкой, кнопки под
     ней во всю ширину карточки и переносят подпись внутри pill. Раньше ячейка наследовала
     `nowrap` десктопа, а кнопкам оставались 60 % строки: «Попросить отмену», «Отменить
     встречу» резались правым краем. */
  .table td.table__cell--actions {
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: center;
    gap: var(--space-x2);
    white-space: normal;
    padding-top: var(--space-x2);
  }
  .table td.table__cell--actions::before { flex: 1 0 100%; max-width: none; }
  .table__cell--actions .action-form { display: flex; max-width: 100%; }
  .table__cell--actions .btn { height: auto; padding-block: var(--space-x2); white-space: normal; }
  .table__cell--actions .btn--icon { margin-left: 0; }
  .table__cell--actions .action__note { flex: 1 0 100%; max-width: none; text-align: right; }
  .table__note { padding: var(--space-x3) var(--space-x5) var(--space-x4); }
  /* Бейдж с пояснением в паре «подпись — значение» прижат вправо вместе со значением. */
  .table .badge--with-note { align-items: flex-end; text-align: right; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}

/* --------------------------------------------------------------------------
   25. Чат, диалоги, почты — раунд правок кабинета 29.07
   -------------------------------------------------------------------------- */

/* Кнопка чата видна на любом экране и не уезжает при прокрутке: смысл её в том,
   что помощь под рукой в тот момент, когда человек застрял, а не когда домотал вниз. */
.chat { position: fixed; right: var(--space-x6); bottom: var(--space-x6); z-index: 60; }

.chat__button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-x2);
  min-height: var(--touch-target);
  padding: 0 var(--space-x5);
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--color-surface-inverse);
  color: var(--color-text-inverse);
  font: var(--type-label-md);
  letter-spacing: var(--tracking-label-md);
  box-shadow: var(--elevation-modal);
  cursor: pointer;
}
.chat__button:hover { background: var(--color-brand-dark, var(--color-surface-inverse)); }
.chat__button-icon { font-size: 18px; line-height: 1; }

.chat__panel {
  position: absolute;
  right: 0;
  bottom: calc(var(--touch-target) + var(--space-x3));
  width: min(360px, calc(100vw - var(--space-x8)));
  display: flex;
  flex-direction: column;
  gap: var(--space-x3);
  padding: var(--space-x5);
  background: var(--color-surface);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-modal);
}
.chat__head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-x3); }
.chat__title { font: var(--type-label-lg); letter-spacing: var(--tracking-label-md); }
.chat__lead { font: var(--type-label-sm); color: var(--color-text-secondary); }
.chat__close {
  border: 0; background: none; cursor: pointer;
  font-size: 22px; line-height: 1; color: var(--color-text-secondary);
}
.chat__log { display: flex; flex-direction: column; gap: var(--space-x2); }
.chat__message {
  padding: var(--space-x3) var(--space-x4);
  border-radius: var(--radius-md);
  font: var(--type-body-sm);
}
.chat__message--bot { background: var(--color-surface-sunken); }
.chat__message--human {
  background: var(--color-surface);
  border: 1px dashed var(--color-border-subtle);
  color: var(--color-text-secondary);
}
.chat__form { display: flex; flex-direction: column; gap: var(--space-x2); margin: 0; }
.chat__input { resize: vertical; }
.chat__hint { font: var(--type-label-sm); color: var(--color-text-secondary); }

@media (max-width: 640px) {
  .chat { right: var(--space-x4); bottom: var(--space-x4); }
  .chat__button-label { display: none; }
  .chat__button { padding: 0; width: var(--touch-target); justify-content: center; }
}

/* Диалог. Нативный <dialog>: фокус, Escape и подложка достаются от браузера, а тот же
   адрес открывается обычной страницей, если скрипт не сработал. */
.dialog {
  width: min(520px, calc(100vw - var(--space-x8)));
  padding: 0;
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  color: var(--color-text-primary);
  box-shadow: var(--elevation-modal);
}
.dialog::backdrop { background: rgba(47, 54, 66, 0.45); }
/* Попап, у формы которого нет своей страницы (переименование группы): без скрипта кнопка ведёт
   на якорь попапа, и он раскрывается обычным блоком в конце экрана. Открытый скриптом попап
   (`[open]`) правило не трогает. `dialog--open` — сервер вернул экран после отказа: форма с
   введённым значением раскрыта и без якоря в адресе. */
.dialog:target:not([open]),
.dialog--open:not([open]) { display: block; position: static; margin: var(--space-x6) auto 0; }
.dialog__body { padding: var(--space-x6); display: flex; flex-direction: column; gap: var(--space-x4); }
.dialog__title { font: var(--type-title-lg); }
.dialog__text { font: var(--type-body-sm); color: var(--color-text-secondary); }
/* Внутри попапа форма — уже не карточка: рамка в рамке читается как вложенный экран. */
.dialog .card { border: 0; box-shadow: none; padding: 0; background: transparent; }

/* --------------------------------------------------------------------------
   26. Раунд правок кабинета 30.07: плотные таблицы, матрица уведомлений, чат
   -------------------------------------------------------------------------- */

/* Несколько состояний ячейки — стопкой. Бейджи одинакового вида, один под другим:
   так «проведена» и «запись готова» видны как два факта, а не как текст и метка. */
.table__badges { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-x1); margin-top: var(--space-x1); }

/* Таблица, в которой главное — текст первой колонки (записи встреч). Остальные колонки
   сжимаются по содержимому, а не делят ширину поровну: срок хранения не должен занимать
   столько же места, сколько название встречи (замечание заказчика 30.07). */
.table--wide-first { table-layout: auto; }
.table--wide-first th:first-child,
.table--wide-first td:first-child { width: 45%; }
.table--wide-first th:not(:first-child),
.table--wide-first td:not(:first-child) { white-space: nowrap; }
/* Таблица с колонкой «№» впереди (материалы, продукты, треки): главная колонка — ВТОРАЯ.
   Раньше 45 % ширины доставались номеру, а название и бейдж «Показ» сжимались — бейдж вставал
   в столбик по букве, строки разной высоты (E14-F09-T12). Номер — по содержимому. Только на
   компьютере: до 1024 px ширины колонок сбрасываются правилами ниже (D04 и телефонная стопка). */
@media (min-width: 1025px) {
  .table--wide-first:has(tbody td.table__cell--num:first-child) th:first-child,
  .table--wide-first:has(tbody td.table__cell--num:first-child) td:first-child { width: 1%; }
  .table--wide-first:has(tbody td.table__cell--num:first-child) th:nth-child(2),
  .table--wide-first:has(tbody td.table__cell--num:first-child) td:nth-child(2) { width: 45%; white-space: normal; }
}

/* Колонка действий: знаки в ряд. Форма-обёртка вокруг POST-кнопки не должна занимать строку. */
.table__cell--actions .action-form { display: inline-flex; }
.table__cell--actions .btn--icon { margin-left: var(--space-x1); }

/* Матрица настроек: галка по центру своей колонки, кнопка сохранения под таблицей. */
.table__cell--check { text-align: center; }
.table__cell--check .table__value { display: inline-flex; justify-content: center; }
.table__check { width: 20px; height: 20px; accent-color: var(--color-accent-brand); cursor: pointer; }
.table__submit { padding: var(--space-x4) var(--space-x5); border-top: 1px solid var(--color-border-subtle); }
/* Заголовок внутри карточки-таблицы: у самой карточки паддинги обнулены, поэтому свои. */
.table__head { padding: var(--space-x5) var(--space-x5) var(--space-x3); align-items: center; }

/* Состояние читается как одно утверждение: подпись и метка рядом, а не по краям карточки. */
.summary__row--inline { justify-content: flex-start; gap: var(--space-x3); align-items: baseline; }
.summary__row--inline .summary__value { text-align: left; }

/* Сноска экрана со ссылкой: текст и кнопка в одной строке. */
.screen__note { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-x3); }

/* Чат. Поле ввода было в две строки и без класса-стиля вообще — писать в него неудобно
   (замечание заказчика 30.07). Панель шире, поле на восемь строк, панель прокручивается,
   если экран низкий. */
.chat__panel {
  width: min(420px, calc(100vw - var(--space-x8)));
  max-height: calc(100vh - var(--space-x16));
  overflow-y: auto;
}
textarea.chat__input { min-height: 168px; }
.chat__examples { display: flex; flex-direction: column; gap: var(--space-x2); }
.chat__examples-label {
  font: var(--type-label-sm);
  letter-spacing: var(--tracking-label-sm);
  color: var(--color-text-secondary);
}
.chat__examples-row { display: flex; flex-wrap: wrap; gap: var(--space-x2); }
/* Готовый вопрос — кнопка-чип: нажатие подставляет текст в поле ввода. */
.chat__example {
  padding: var(--space-x2) var(--space-x3);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-pill);
  background: var(--color-surface-card);
  color: var(--color-accent-brand);
  font: var(--type-label-sm);
  letter-spacing: var(--tracking-label-sm);
  text-align: left;
  cursor: pointer;
}
.chat__example:hover { background: var(--color-surface-sunken); border-color: var(--color-border-control); }

/* Полоса пояса в кабинете: вместо списка поясов — дорога в профиль, где указан город. */
.tz-bar__link { margin-left: auto; }

@media (max-width: 768px) {
  /* Строка таблицы на телефоне — пара «подпись — значение», поэтому стопка бейджей
     прижимается вправо вместе со значением. */
  .table__badges { align-items: flex-end; }
  .table--wide-first th:first-child,
  .table--wide-first td:first-child { width: auto; }
  .table--wide-first th:not(:first-child),
  .table--wide-first td:not(:first-child) { white-space: normal; }
  .table__cell--check { text-align: right; }
  .table__cell--check .table__value { justify-content: flex-end; }

  /* Второстепенные колонки на телефоне не показываются: восемь пар «подпись — значение»
     в одной карточке — это два экрана прокрутки на одну строку таблицы. Значение не
     потеряно: строка ведёт в карточку, где есть всё. */
  .table td.table__cell--phone-hidden,
  .table th.table__cell--phone-hidden { display: none; }

  /* Матрица настроек на телефоне: событие — заголовок группы во всю ширину, каналы строками
     под ним. Подпись «Что присылать» рядом с самим событием ничего не добавляет, но забирает
     40% ширины — и «Напоминание за день до встречи» ломалось на две строки у правого края. */
  .table--matrix td:first-child { display: block; padding-bottom: var(--space-x2); }
  .table--matrix td:first-child::before { content: none; }
  .table--matrix td:first-child .table__value,
  .table--matrix td:first-child .table__sub { text-align: left; }
}

/* --------------------------------------------------------------------------
   Приложение Sincera Start в кабинете клиента (E16-F04, E16-F10, E16-F18, E16-F12)
   → views/app-*.ejs, components/app-card.ejs, блоки cabinet-summary.ejs

   Телефон — основной экран: сетка карточек в одну колонку, кнопки во всю ширину,
   вторая колонка появляется, когда помещается. Цвета, отступы и скругления — только токены.
   -------------------------------------------------------------------------- */
.app-back { font: var(--type-body-sm); margin-bottom: var(--space-x3); }
.app-section-title { margin-bottom: var(--space-x4); }
.app-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-x4);
  margin-bottom: var(--space-x4);
}

.app-strip { display: flex; align-items: center; justify-content: space-between; gap: var(--space-x4); flex-wrap: wrap; }
.app-strip__text { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-x2); min-width: 0; flex: 1 1 240px; }

/* Сетка карточек: одна колонка на телефоне, дальше — сколько помещается. */
.app-grid,
.track-banners,
.track-list,
.event-list,
.lesson-list { list-style: none; margin: 0; padding: 0; }
.app-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--space-x5); }
.app-grid > li { display: flex; }

.app-card {
  display: flex;
  flex-direction: column;
  width: 100%;
  background: var(--color-surface-card);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--elevation-raised);
  color: var(--color-text-primary);
  overflow: hidden;
  transition: border-color var(--motion-fast), box-shadow var(--motion-fast), transform var(--motion-fast);
}
.app-card:hover { text-decoration: none; color: var(--color-text-primary); border-color: var(--color-border-default); box-shadow: var(--elevation-overlay); transform: translateY(-2px); }
.app-card:focus-visible { outline: none; box-shadow: var(--elevation-focus-ring); }
.app-card__cover { position: relative; display: block; aspect-ratio: 16 / 9; background: var(--color-surface-muted); }
.app-card__cover img { display: block; width: 100%; height: 100%; object-fit: cover; }
.app-card.is-locked .app-card__cover img { filter: grayscale(0.7); opacity: 0.6; }
.app-card__lock,
.app-card__no {
  position: absolute;
  top: var(--space-x3);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-x10);
  height: var(--space-x10);
  border-radius: var(--radius-pill);
}
.app-card__lock { right: var(--space-x3); background: var(--color-surface-inverse); color: var(--color-text-inverse); }
.app-card__no { left: var(--space-x3); background: var(--color-surface-card); color: var(--color-text-primary); font: var(--type-title-sm); box-shadow: var(--elevation-raised); }
.app-card__body { display: flex; flex-direction: column; gap: var(--space-x2); padding: var(--space-x4); flex: 1; }
.app-card__badges { display: flex; flex-wrap: wrap; gap: var(--space-x2); }
.app-card__title { font: var(--type-title-md); }
.app-card__summary { font: var(--type-body-sm); color: var(--color-text-secondary); }
.app-card__note { margin-top: auto; padding-top: var(--space-x2); font: var(--type-label-sm); letter-spacing: var(--tracking-label-sm); color: var(--color-text-secondary); }
.app-card__progress { margin-top: auto; padding-top: var(--space-x2); display: flex; flex-direction: column; gap: var(--space-x2); }

/* Страница продукта */
.app-hero { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: var(--space-x8); align-items: center; }
.app-hero__cover { border-radius: var(--radius-lg); overflow: hidden; aspect-ratio: 16 / 9; background: var(--color-surface-muted); box-shadow: var(--elevation-overlay); }
.app-hero__cover img { display: block; width: 100%; height: 100%; object-fit: cover; }
.app-hero__body { display: flex; flex-direction: column; gap: var(--space-x3); min-width: 0; }
.app-main { margin-top: var(--space-x3); display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-x2); }
.app-main__note { max-width: 52ch; }

.app-prose { display: flex; flex-direction: column; gap: var(--space-x4); font: var(--type-body-md); max-width: 68ch; }
.app-prose--reading { font: var(--type-body-lg); }
.app-prose > p { white-space: pre-line; }
.app-prose__foot { margin-top: var(--space-x4); }

.app-lessons__progress { margin-bottom: var(--space-x4); }
.lesson-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--space-x4);
  align-items: center;
  min-height: var(--row-height);
  padding: var(--space-x3) 0;
  border-bottom: 1px solid var(--color-border-subtle);
}
.lesson-row:last-child { border-bottom: 0; padding-bottom: 0; }
.lesson-row__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-x8);
  height: var(--space-x8);
  border-radius: var(--radius-pill);
  background: var(--color-surface-sunken);
  color: var(--color-text-secondary);
  font: var(--type-label-md);
  font-variant-numeric: tabular-nums;
}
.lesson-row.is-done .lesson-row__mark { background: var(--color-success-surface); color: var(--color-success); }
.lesson-row__body { display: flex; flex-direction: column; gap: var(--space-x1); min-width: 0; }
.lesson-row__title { font: var(--type-title-sm); color: var(--color-text-primary); }
a.lesson-row__title:hover { color: var(--color-text-link-hover); }
.lesson-row.is-locked .lesson-row__title { color: var(--color-text-secondary); }
.lesson-row__lock { display: inline-flex; color: var(--color-text-secondary); }

/* Страница материала */
.app-media { display: flex; flex-direction: column; gap: var(--space-x3); }
.app-media__foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-x3); }
/* Плеер видео (E16-F10-T02): из управления только наша полоса — пауза/плей, перемотка, скорость.
   Фрейм выше экрана на --player-crop сверху и снизу: видео встаёт по центру, а заголовок,
   логотип и полоса кнопок самого плеера уходят за обрез. После первого запуска фрейм не
   ловит мышь — родные кнопки не всплывают по наведению, клик по видео ловит обложка. */
.player {
  --player-crop: 64px;
  display: flex;
  flex-direction: column;
  width: 100%;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--gradient-brand-dark);
}
.player[hidden] { display: none; }
.player__screen {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 16 / 9;
  width: 100%;
  overflow: hidden;
}
.player__frame {
  position: absolute;
  left: 0;
  top: calc(-1 * var(--player-crop));
  width: 100%;
  height: calc(100% + 2 * var(--player-crop));
  border: 0;
}
.player.is-started .player__frame { pointer-events: none; }
.player__fallback { margin: var(--space-x4); }
.player__cover {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--color-text-on-accent);
  cursor: pointer;
  transition: background-color var(--motion-base);
}
.player.is-started .player__cover { display: flex; }
.player.is-started:not(.is-playing) .player__cover { background: color-mix(in srgb, var(--color-surface-inverse) 60%, transparent); }
.player.is-ended .player__cover { background: var(--gradient-brand-dark); }
.player__cover-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border-radius: var(--radius-pill);
  background: var(--color-accent-brand);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.35);
  transition: opacity var(--motion-base), transform var(--motion-base), background-color var(--motion-fast);
}
.player__cover:hover .player__cover-icon { background: var(--color-accent-brand-hover); }
.player.is-playing .player__cover-icon { opacity: 0; transform: scale(0.85); }
.player__bar {
  display: flex;
  align-items: center;
  gap: var(--space-x2);
  padding: var(--space-x2) var(--space-x3);
  background: var(--color-surface-inverse);
  color: var(--color-text-inverse);
}
.player__bar :focus-visible { outline: 2px solid var(--color-accent-cyan-bright); outline-offset: 2px; }
.player__btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--color-accent-brand);
  color: var(--color-text-on-accent);
  cursor: pointer;
  transition: background-color var(--motion-fast);
}
.player__btn:hover:not(:disabled) { background: var(--color-accent-brand-hover); }
.player__icon--pause { display: none; }
.player.is-playing .player__btn .player__icon--play { display: none; }
.player.is-playing .player__btn .player__icon--pause { display: block; }
.player__time {
  flex: none;
  min-width: 4ch;
  font: var(--type-label-md);
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: var(--color-text-on-inverse-muted);
}
.player__seek {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
  height: 24px;
  margin: 0;
  accent-color: var(--color-accent-cyan-bright);
  cursor: pointer;
}
.player__speed {
  flex: none;
  min-height: 32px;
  padding: var(--space-x1) var(--space-x2);
  border: 1px solid var(--color-inverse-a40);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-inverse);
  font: var(--type-label-md);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.player__speed option { color: var(--color-text-primary); background: var(--color-surface-card); }
/* Громкость: кнопка вкл/выкл и регулятор. На узком экране регулятор всплывает над кнопкой
   по наведению или фокусу (Tab с кнопки ведёт прямо в него). */
.player__volume { position: relative; flex: none; display: flex; align-items: center; gap: var(--space-x1); }
.player__mute {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-inverse);
  cursor: pointer;
  transition: background-color var(--motion-fast);
}
.player__mute:hover:not(:disabled) { background: var(--color-inverse-a20); }
.player__muted-mark { display: none; }
.player.is-muted .player__muted-mark { display: inline; }
.player.is-muted .player__wave,
.player.is-quiet .player__wave--far { display: none; }
.player__level {
  width: 72px;
  height: 24px;
  margin: 0;
  accent-color: var(--color-accent-cyan-bright);
  cursor: pointer;
}
.player__btn:disabled,
.player__seek:disabled,
.player__mute:disabled,
.player__level:disabled,
.player__speed:disabled { opacity: 0.5; cursor: default; }
@media (max-width: 420px) {
  .player { --player-crop: 56px; }
  .player__bar { gap: var(--space-x1); padding: var(--space-x2); }
  .player__speed { padding: var(--space-x1); }
  .player__volume-pop {
    position: absolute;
    bottom: calc(100% + var(--space-x2));
    left: 50%;
    z-index: 2;
    padding: var(--space-x2) var(--space-x3);
    border-radius: var(--radius-sm);
    background: var(--color-surface-inverse);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
    opacity: 0;
    visibility: hidden;
    transform: translate(-50%, var(--space-x1));
    transition: opacity var(--motion-fast), transform var(--motion-fast), visibility var(--motion-fast);
  }
  .player__volume:hover .player__volume-pop,
  .player__volume:focus-within .player__volume-pop { opacity: 1; visibility: visible; transform: translate(-50%, 0); }
  .player__level { width: 96px; }
}
.app-audio { width: 100%; }
.app-pdf { width: 100%; height: 70vh; min-height: 360px; border: 1px solid var(--color-border-subtle); border-radius: var(--radius-sm); background: var(--color-surface-sunken); }
.app-done { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-x4); }
.app-pager { display: flex; justify-content: space-between; gap: var(--space-x3); }
.app-pager .btn { min-width: 0; max-width: 100%; }
.app-pager__title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 28ch; }
.app-locked { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-x3); }
.app-locked__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-x16);
  height: var(--space-x16);
  border-radius: var(--radius-pill);
  background: var(--color-warning-surface);
  color: var(--color-warning);
}

/* Трек */
.track-hero { position: relative; border-radius: var(--radius-lg); overflow: hidden; background: var(--gradient-brand-dark); color: var(--color-text-inverse); }
.track-hero__cover { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0.28; }
.track-hero__body { position: relative; display: flex; flex-direction: column; gap: var(--space-x3); padding: var(--space-x10) var(--space-x8); }
.track-hero__label { font: var(--type-label-caps); letter-spacing: var(--tracking-label-caps); text-transform: uppercase; color: var(--color-accent-gold); }
.track-hero__title { font: var(--type-headline-lg); color: var(--color-text-inverse); }
.track-hero__text { font: var(--type-body-md); max-width: 60ch; color: var(--color-text-inverse); }
.track-hero__meta { font: var(--type-label-md); color: var(--color-text-on-inverse-muted); }
.track-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--space-x5); }
.track-list__item { display: flex; flex-direction: column; gap: var(--space-x2); }
.track-list__item .app-card { flex: 1; }
.track-list__action { padding: 0 var(--space-x1); }

/* Главная: баннеры треков — стопкой на телефоне, рядом на широком экране. */
.track-banners { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--space-x4); }
.track-banner {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: 160px;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--gradient-brand-dark);
  color: var(--color-text-inverse);
  transition: box-shadow var(--motion-fast), transform var(--motion-fast);
}
.track-banner:hover { text-decoration: none; color: var(--color-text-inverse); box-shadow: var(--elevation-modal); transform: translateY(-2px); }
.track-banner__cover { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0.32; }
.track-banner__body { position: relative; display: flex; flex-direction: column; gap: var(--space-x1); padding: var(--space-x5) var(--space-x6); }
.track-banner__title { font: var(--type-headline-md); }
.track-banner__meta { font: var(--type-label-sm); letter-spacing: var(--tracking-label-sm); color: var(--color-text-on-inverse-muted); }

/* Главная: «Продолжить» — самый заметный блок после мысли дня. */
.resume-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-x5);
  padding: var(--space-x6) var(--space-x8);
  border-radius: var(--radius-lg);
  background: var(--gradient-brand-dark);
  color: var(--color-text-inverse);
}
.resume-card__body { display: flex; flex-direction: column; gap: var(--space-x1); min-width: 0; }
.resume-card__label { font: var(--type-label-caps); letter-spacing: var(--tracking-label-caps); text-transform: uppercase; color: var(--color-accent-gold); }
.resume-card__title { font: var(--type-title-lg); }
.resume-card__meta { font: var(--type-body-sm); color: var(--color-text-on-inverse-muted); }
.resume-card__btn { flex: none; background: var(--color-surface-card); color: var(--color-accent-brand); }
.resume-card__btn:hover { background: var(--color-surface-accent-soft); color: var(--color-accent-brand-hover); }

/* Главная: мысль дня — картинка целиком, без обрезки. */
.thought-card {
  display: flex;
  flex-direction: column;
  max-width: 420px;
  margin: 0 auto;
  width: 100%;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-surface-card);
  border: 1px solid var(--color-border-subtle);
  box-shadow: var(--elevation-overlay);
  color: var(--color-text-primary);
  transition: box-shadow var(--motion-fast), transform var(--motion-fast);
}
.thought-card:hover { text-decoration: none; color: var(--color-text-primary); box-shadow: var(--elevation-modal); transform: translateY(-2px); }
.thought-card__image { display: block; width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover; }
.thought-card__foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-x3); padding: var(--space-x3) var(--space-x5); }
.thought-card__open { font: var(--type-label-md); color: var(--color-text-link); }

.thought-full { display: flex; flex-direction: column; gap: var(--space-x5); align-items: center; }
.thought-full .app-back { align-self: flex-start; }
.thought-full__figure { margin: 0; display: flex; flex-direction: column; align-items: center; gap: var(--space-x3); width: 100%; }
.thought-full__image { display: block; width: 100%; max-width: min(100%, 72vh); height: auto; border-radius: var(--radius-lg); box-shadow: var(--elevation-modal); }
.thought-full__actions { display: flex; flex-direction: column; align-items: center; gap: var(--space-x3); text-align: center; }

/* Главная: события */
.event-row { display: grid; grid-template-columns: auto 1fr auto; gap: var(--space-x4); align-items: center; padding: var(--space-x3) 0; border-bottom: 1px solid var(--color-border-subtle); }
.event-row:first-child { padding-top: 0; }
.event-row:last-child { border-bottom: 0; padding-bottom: 0; }
.event-row__body { display: flex; flex-direction: column; gap: var(--space-x1); min-width: 0; }
.event-row__title { font: var(--type-title-sm); }
.event-row__side { display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-x2); }

.invite-card { display: flex; align-items: center; justify-content: space-between; gap: var(--space-x5); flex-wrap: wrap; }
.invite-card__text { margin-top: var(--space-x2); max-width: 56ch; }
.invite-card__url { margin: var(--space-x2) 0; overflow-wrap: anywhere; color: var(--color-text-primary); }

@media (max-width: 768px) {
  .app-hero { grid-template-columns: 1fr; gap: var(--space-x5); }
  .app-main { align-items: stretch; }
  .app-main .btn,
  .app-main .action-form,
  .app-main .action-form .btn { width: 100%; }
  .app-done { flex-direction: column; align-items: stretch; }
  .app-done .btn { width: 100%; }
  .resume-card { flex-direction: column; align-items: stretch; padding: var(--space-x5); }
  .resume-card__btn { width: 100%; }
  .track-hero__body { padding: var(--space-x6) var(--space-x5); }
  .track-hero__title { font: var(--type-headline-md); }
  .event-row { grid-template-columns: auto 1fr; }
  .event-row__side { grid-column: 1 / -1; flex-direction: row; align-items: center; justify-content: space-between; }
  .invite-card .btn { width: 100%; }
  .app-pager { flex-direction: column-reverse; }
  .app-pager .btn { width: 100%; }
  .app-pager > span:empty { display: none; }
  .app-pdf { height: 60vh; }
  .thought-full__actions .btn { width: 100%; }
  .app-strip .btn { width: 100%; }
  /* Галочка в кружке уже говорит «пройдено»: бейдж на узком экране только сжимает заголовок. */
  .lesson-row.is-done .lesson-row__side { display: none; }
  .lesson-row.is-done { grid-template-columns: auto 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .app-card, .track-banner, .thought-card { transition: none; }
  .app-card:hover, .track-banner:hover, .thought-card:hover { transform: none; }
}

/* --------------------------------------------------------------------------
   Sincera Start, второй проход (E16-F06, F07, F11, F13, F16, F23, F24, F25)
   → views/app-subscription.ejs, app-events.ejs, app-welcome.ejs, app-help.ejs,
     блоки приглашения, уведомлений и событий в cabinet-summary.ejs
   -------------------------------------------------------------------------- */

/* Подписка: тёмная карточка тарифа — слева тариф и срок, справа что открыто. */
.sub-card {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: var(--space-x8);
  padding: var(--space-x8);
  border-radius: var(--radius-lg);
  background: var(--gradient-brand-dark);
  color: var(--color-text-inverse);
  box-shadow: var(--elevation-overlay);
}
.sub-card.is-expired { background: var(--color-surface-inverse); }
.sub-card__main { display: flex; flex-direction: column; gap: var(--space-x3); min-width: 0; }
.sub-card__side { display: flex; flex-direction: column; gap: var(--space-x3); min-width: 0; }
.sub-card__label { font: var(--type-label-caps); letter-spacing: var(--tracking-label-caps); text-transform: uppercase; color: var(--color-accent-gold); }
.sub-card__title-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-x3); }
.sub-card__name { font: var(--type-display); letter-spacing: var(--tracking-display); color: var(--color-text-inverse); }
.sub-card__date { display: flex; flex-direction: column; gap: var(--space-x1); margin: 0; }
.sub-card__date dt { font: var(--type-body-sm); color: var(--color-text-on-inverse-muted); }
.sub-card__date dd { margin: 0; font: var(--type-title-lg); }
.sub-card__note { font: var(--type-body-xs); color: var(--color-text-on-inverse-muted); }

.sub-actions { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-x3); }
.sub-actions__row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-x3); }

/* Список «что входит»: галочка-маркер, без буллитов браузера. */
.tariff-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-x2); font: var(--type-body-sm); }
.tariff-list li { position: relative; padding-left: var(--space-x6); }
.tariff-list li::before { content: "✓"; position: absolute; left: 0; top: 0; color: var(--color-success); font: var(--type-label-md); line-height: inherit; }
.tariff-list--inverse li::before { color: var(--color-accent-gold); }

/* Карточки тарифов: стопкой на телефоне, рядом на широком экране. */
.tariff-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--space-x5); }
.tariff-grid__note { margin-top: var(--space-x4); max-width: 68ch; }
.tariff-card {
  display: flex;
  flex-direction: column;
  background: var(--color-surface-card);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-raised);
  overflow: hidden;
}
.tariff-card__head { position: relative; display: flex; align-items: flex-end; min-height: 128px; background: var(--gradient-brand-dark); color: var(--color-text-inverse); }
.tariff-card__cover { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0.3; }
.tariff-card__name { position: relative; padding: var(--space-x5) var(--space-x6); font: var(--type-headline-lg); }
.tariff-card--pro .tariff-card__name { color: var(--color-accent-gold); }
.tariff-card__body { display: flex; flex-direction: column; gap: var(--space-x4); padding: var(--space-x5) var(--space-x6) var(--space-x6); flex: 1; }
.tariff-card__foot { margin-top: auto; display: flex; flex-direction: column; gap: var(--space-x2); padding-top: var(--space-x2); }

/* События: замок у закрытого, описание на странице списка. */
.event-row__lock { display: inline-flex; align-items: center; gap: var(--space-x2); }
.event-row__lock svg { flex: none; }
.event-row__text { max-width: 60ch; }
.event-list--full .event-row { align-items: flex-start; padding: var(--space-x4) 0; }
.event-list--full .event-row:first-child { padding-top: 0; }
.event-list--full .event-row:last-child { padding-bottom: 0; }
.event-row.is-locked .date-block { opacity: 0.6; }

/* Главная: уведомления на телефон. */
.push-card { display: grid; grid-template-columns: auto 1fr auto; gap: var(--space-x4); align-items: center; }
.push-card__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-x12);
  height: var(--space-x12);
  border-radius: var(--radius-pill);
  background: var(--color-surface-accent-soft);
  color: var(--color-accent-brand);
}
.push-card__body { display: flex; flex-direction: column; gap: var(--space-x2); min-width: 0; }
.push-card__steps { margin: 0; padding-left: var(--space-x5); display: flex; flex-direction: column; gap: var(--space-x1); font: var(--type-body-sm); }

/* Главная: приглашение — слева ссылка и кнопки, справа счётчик. */
.invite-card { align-items: stretch; flex-wrap: nowrap; gap: var(--space-x8); }
.invite-card__body { display: flex; flex-direction: column; gap: var(--space-x2); flex: 1 1 auto; min-width: 0; }
.invite-card__link { display: flex; flex-wrap: wrap; gap: var(--space-x3); margin-top: var(--space-x2); }
.invite-card__link .invite-card__url { flex: 1 1 260px; min-width: 0; margin: 0; background: var(--color-surface-card); }
.invite-card__actions { flex: none; }
.invite-card__stat {
  flex: 0 0 200px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-x1);
  padding-left: var(--space-x8);
  border-left: 1px solid var(--color-border-default);
}
.invite-card__count { font: var(--type-display); letter-spacing: var(--tracking-display); color: var(--color-accent-brand); }
.invite-card__bonus { margin-top: var(--space-x2); font: var(--type-label-md); letter-spacing: var(--tracking-label-md); color: var(--color-text-primary); }

/* Первый вход: одна карточка по центру, без меню. */
.onboard { display: flex; justify-content: center; }
.onboard__card {
  display: flex;
  flex-direction: column;
  gap: var(--space-x6);
  width: 100%;
  max-width: 480px;
  padding: var(--space-x8);
  border-radius: var(--radius-lg);
  background: var(--color-surface-card);
  border: 1px solid var(--color-border-subtle);
  box-shadow: var(--elevation-overlay);
  text-align: center;
}
.onboard__art { border-radius: var(--radius-md); overflow: hidden; aspect-ratio: 16 / 9; background: var(--gradient-brand-dark); }
.onboard__art img { display: block; width: 100%; height: 100%; object-fit: cover; }
.onboard__art--logo { display: flex; align-items: center; justify-content: center; aspect-ratio: 4 / 3; }
.onboard__art--logo img { width: auto; height: auto; max-width: 56%; max-height: 56%; object-fit: contain; }
.onboard__body { display: flex; flex-direction: column; gap: var(--space-x3); }
.onboard__label { font: var(--type-label-caps); letter-spacing: var(--tracking-label-caps); text-transform: uppercase; color: var(--color-text-secondary); }
.onboard__title { font: var(--type-headline-lg); }
.onboard--intro .onboard__title { font: var(--type-display); letter-spacing: var(--tracking-display); }
.onboard__text { font: var(--type-body-md); color: var(--color-text-secondary); }
.onboard__dots { list-style: none; margin: 0; padding: 0; display: flex; justify-content: center; gap: var(--space-x2); }
.onboard__dot { width: var(--space-x2); height: var(--space-x2); border-radius: var(--radius-pill); background: var(--color-border-default); transition: width var(--motion-fast), background var(--motion-fast); }
.onboard__dot.is-current { width: var(--space-x6); background: var(--color-accent-brand); }
.onboard__actions { display: flex; flex-direction: column; gap: var(--space-x3); }
.onboard__minor { display: flex; align-items: center; justify-content: space-between; gap: var(--space-x3); }

/* Помощь: вопросы раскрываются нативным <details>. */
.help-topic { border-bottom: 1px solid var(--color-border-subtle); }
.help-topic:last-child { border-bottom: 0; }
.help-topic__title { display: flex; align-items: center; justify-content: space-between; gap: var(--space-x4); min-height: var(--row-height); padding: var(--space-x3) 0; font: var(--type-title-sm); cursor: pointer; list-style: none; }
.help-topic__title::-webkit-details-marker { display: none; }
.help-topic__title::after { content: "+"; flex: none; font: var(--type-title-lg); color: var(--color-text-secondary); }
.help-topic[open] .help-topic__title::after { content: "−"; }
.help-topic__title:focus-visible { outline: none; box-shadow: var(--elevation-focus-ring); border-radius: var(--radius-sm); }
.help-topic__body { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-x2); padding-bottom: var(--space-x4); max-width: 68ch; }

@media (max-width: 768px) {
  .sub-card { grid-template-columns: 1fr; gap: var(--space-x6); padding: var(--space-x6) var(--space-x5); }
  .sub-actions__row { flex-direction: column; align-items: stretch; }
  .sub-actions__row .btn { width: 100%; }
  .push-card { grid-template-columns: auto 1fr; align-items: flex-start; }
  .push-card__action { grid-column: 1 / -1; }
  .push-card__action,
  .push-card__action .btn { width: 100%; }
  .invite-card { flex-direction: column; flex-wrap: nowrap; gap: var(--space-x5); }
  .invite-card__actions { width: 100%; }
  .invite-card__actions .btn { flex: 1 1 0; width: auto; }
  .invite-card__stat { flex: none; padding-left: 0; padding-top: var(--space-x4); border-left: 0; border-top: 1px solid var(--color-border-default); }
  .onboard__card { padding: var(--space-x6) var(--space-x5); }
  .event-list--full .event-row__side .btn { flex: 1 1 auto; }
}

@media (prefers-reduced-motion: reduce) {
  .onboard__dot { transition: none; }
}

/* --------------------------------------------------------------------------
   Раздел «Приложение» у команды (E16-F05)  →  views/app-admin-thoughts.ejs
   Остальные вкладки раздела рисует общий `screen.ejs` — своих стилей у них нет.
   -------------------------------------------------------------------------- */

/* Поле загрузки файла: браузерная кнопка внутри обычного поля формы. */
.field__control--file { height: auto; min-height: var(--control-height); padding: var(--space-x2) var(--space-x3); font: var(--type-body-sm); cursor: pointer; }
.field__control--file::file-selector-button {
  margin-right: var(--space-x3);
  padding: var(--space-x1) var(--space-x3);
  border: 1px solid var(--color-border-control);
  border-radius: var(--radius-sm);
  background: var(--color-surface-card);
  color: var(--color-text-primary);
  font: var(--type-label-md);
  cursor: pointer;
}
.field__control--file:hover::file-selector-button { border-color: var(--color-border-strong); }

/* Мысль дня: календарь на месяц вперёд — сетка недель, пустой день заметен сразу. */
.thought-cal { display: flex; flex-direction: column; gap: var(--space-x2); }
.thought-cal .card__head { align-items: baseline; }
.thought-cal__weekdays,
.thought-cal__week { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: var(--space-x2); }
.thought-cal__weekdays li { font: var(--type-label-caps); letter-spacing: var(--tracking-label-caps); text-transform: uppercase; color: var(--color-text-secondary); padding: 0 var(--space-x2); }
.thought-cal__note { margin-top: var(--space-x2); }

.thought-day {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-x2);
  min-height: 148px;
  padding: var(--space-x2);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  background: var(--color-surface-card);
}
.thought-day--outside { border-style: dashed; background: transparent; min-height: 0; }
.thought-day--empty { background: var(--color-surface-sunken); border-style: dashed; border-color: var(--color-border-default); }
.thought-day.is-today { border-color: var(--color-accent-brand); box-shadow: inset 0 0 0 1px var(--color-accent-brand); }
.thought-day__date { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-x1) var(--space-x2); margin: 0; }
.thought-day__num { font: var(--type-title-sm); }
.thought-day__weekday { display: none; font: var(--type-body-xs); color: var(--color-text-secondary); }
.thought-day__month { font: var(--type-body-xs); color: var(--color-text-secondary); }
.thought-day__image { display: block; width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover; border-radius: var(--radius-sm); }
.thought-day__file { margin: 0; font: var(--type-body-xs); color: var(--color-text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.thought-day__none { margin: auto 0; font: var(--type-body-xs); color: var(--color-text-secondary); text-align: center; }
.thought-day__remove { position: absolute; top: var(--space-x1); right: var(--space-x1); }
.thought-day__remove .btn { background: var(--color-surface-card); }

.thought-forms { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-x6); align-items: start; }

/* Планшет и телефон: неделя не помещается в семь колонок — дни идут плиткой, день назван в клетке. */
@media (max-width: 1024px) {
  .thought-cal__weekdays { display: none; }
  .thought-cal__week { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }
  .thought-day--outside { display: none; }
  .thought-day__weekday { display: inline; }
  .thought-forms { grid-template-columns: 1fr; }
}

/* --------------------------------------------------------------------------
   Подсказка «как установить приложение»  (E16-F02)  →  components/install-hint.ejs
   Стоит в потоке между шапкой и содержимым: внизу экрана живут чат и липкие панели записи,
   перекрывать их нельзя. Показывает её `public/js/pwa.js` и только на телефоне; до этого корень
   несёт `hidden` и места не занимает.
   -------------------------------------------------------------------------- */
.install-hint {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--space-x3) var(--space-x4);
  align-items: start;
  margin: var(--space-x4) var(--page-padding) 0;
  padding: var(--space-x4);
  background: var(--color-surface-card);
  border: 1px solid var(--color-border-subtle);
  border-left: 3px solid var(--color-accent-brand);
  border-radius: var(--radius-md);
  box-shadow: var(--elevation-overlay);
  animation: banner-in var(--motion-slow) both;
}
/* Следом идёт содержимое со своим верхним отступом — вдвоём они дали бы пустой экран до заголовка. */
.install-hint:not([hidden]) + .page { padding-top: var(--space-x6); }
.install-hint__icon { width: var(--space-x12); height: var(--space-x12); border-radius: var(--radius-md); flex: none; }
.install-hint__body { display: flex; flex-direction: column; gap: var(--space-x2); min-width: 0; }
.install-hint__title { font: var(--type-title-md); color: var(--color-text-primary); }
.install-hint__variant { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-x3); }
.install-hint__text { font: var(--type-body-sm); color: var(--color-text-secondary); }
.install-hint__steps { margin: 0; padding-left: var(--space-x5); display: flex; flex-direction: column; gap: var(--space-x2); font: var(--type-body-sm); color: var(--color-text-primary); }
.install-hint__share { display: inline-block; vertical-align: -3px; margin: 0 var(--space-x1); color: var(--color-accent-brand); }
/* Крестик прижат к углу: палец попадает в 40px, а на глаз он не спорит с заголовком. */
.install-hint__dismiss { margin: calc(-1 * var(--space-x2)) calc(-1 * var(--space-x2)) 0 0; border-color: transparent; color: var(--color-text-secondary); }

@media (max-width: 480px) {
  .install-hint { gap: var(--space-x3); padding: var(--space-x3); }
  .install-hint__icon { width: var(--space-x10); height: var(--space-x10); }
  /* Кнопка установки — во всю ширину подсказки, под иконкой тоже: так в неё проще попасть. */
  .install-hint__variant { align-items: stretch; }
  .install-hint__action { width: 100%; }
}

/* На компьютере подсказки нет: это решает скрипт, а правило — страховка на случай, если атрибут
   `hidden` с корня снимет кто-то ещё. Планшет с пальцем под правило не попадает. */
@media (min-width: 1025px) and (hover: hover) and (pointer: fine) {
  .install-hint { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .install-hint { animation: none; }
}

/* --------------------------------------------------------------------------
   Явные дефекты на телефоне и планшете (E16-F03-T02, список — qa/E16-F03-mobile-defects.md).
   Всё ниже — только до 1024 px: вид на компьютере не меняется.
   -------------------------------------------------------------------------- */
.btn__icon-label { display: none; }

@media (max-width: 1024px) {
  /* D01: адрес без пробелов (`https://www.googleapis.com/auth/calendar.freebusy` в «Политике»)
     не переносился и уводил страницу вбок. */
  .summary__value { min-width: 0; overflow-wrap: anywhere; }

  /* D03: название соседнего материала обрезалось многоточием даже там, где место есть.
     Кнопки переносят название, а не режут его (на телефоне они и так друг под другом). */
  .app-pager .btn { white-space: normal; text-align: center; padding-top: var(--space-x2); padding-bottom: var(--space-x2); }
  .app-pager__title { white-space: normal; overflow: visible; max-width: none; }

  /* D05: галка — вся ячейка, вместе с подписью строки. */
  .table__cell--check { position: relative; }
  .table__check-hit { cursor: pointer; }
  .table__check-hit::after { content: ""; position: absolute; inset: 0; }
  .table__check { width: 24px; height: 24px; }

  /* D06, D08: кнопки в строках таблиц — не ниже 44 px. */
  .table__cell--actions .btn { min-height: 44px; }
  /* `.btn.` — вес 0-2-0, ниже по файлу: одиночный класс проигрывал `.density-compact .btn--icon
     { width: 32px }`, и у команды и коуча знак оставался кружком 32 px, а подпись на телефоне
     вываливалась из круга (E14-F53-T06: «Решить», «История сессий», «Поднять трек выше»,
     «Переименовать группу»). */
  .btn.btn--icon-action { min-height: 44px; min-width: 44px; width: auto; }
  .btn--icon-action,
  .table__cell--actions .btn--icon-action { padding: 0; }

  /* D07: крестик попапа и чата — 44 × 44 вместо 30 × 24. Отрицательный отступ держит его
     на прежнем месте у угла, шапка попапа не растёт. */
  .chat__close {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    min-width: 44px; min-height: 44px;
    margin: calc(-1 * var(--space-x2)) calc(-1 * var(--space-x2)) calc(-1 * var(--space-x2)) 0;
  }
}

/* D04: 769–1024 px получают настольную таблицу, а она не помещалась в карточку: в «Заявках»
   за краем были статус и действия, в «Моих почтах» — кнопки. Ячейки переносят текст,
   действия переносятся на вторую строку, поля ячеек уже. */
@media (min-width: 769px) and (max-width: 1024px) {
  .table th, .table td { padding-left: var(--space-x3); padding-right: var(--space-x3); }
  .table th { white-space: normal; }
  .table--wide-first th:first-child,
  .table--wide-first td:first-child { width: auto; }
  .table--wide-first th:not(:first-child),
  .table--wide-first td:not(:first-child) { white-space: normal; }
  .table__cell--actions { white-space: normal; }
  /* Бейдж переносится по словам, а не по буквам: «на рассмотрени / и» читалось как сбой. */
  .table .badge { overflow-wrap: normal; }
}

@media (max-width: 768px) {
  /* D02: плашка «Заявка уже у менеджера · отправлена 7 октября» не переносилась и была
     шире карточки. */
  .sub-actions__row .badge { white-space: normal; }

  /* D06: на касании подсказки при наведении нет — рядом со знаком встаёт подпись. */
  .btn--icon-action,
  .table__cell--actions .btn--icon-action { padding: var(--space-x2) var(--space-x4); height: auto; white-space: normal; }
  .btn--icon-action .btn__icon-label { display: inline; }
  .table td.table__cell--check { min-height: 44px; align-items: center; }
}

/* E14-F09-T11: на планшете и телефоне подписи не мельче 14 px. Кнопка в строке таблицы
   («Запись встречи» в «Моих сессиях») — label-md вместо label-sm (44 px высоты — из D06),
   мелкие абзацы `.meta-xs` (главная кабинета) — body-sm. Плотный вид команды не трогается:
   там мелкий шрифт — сознательная плотность. Остальные кнопки только крупнее, не мельче. */
@media (max-width: 1024px) {
  body:not(.density-compact) .table__cell--actions .btn { font: var(--type-label-md); letter-spacing: var(--tracking-label-md); }
  body:not(.density-compact) .meta-xs { font: var(--type-body-sm); }
}

/* E14-F09-T12: плавающая кнопка «Помощник» (`.chat`, низ справа) не накрывает последнюю строку
   страницы, домотанной до конца: снизу место под кнопку и отступ над ней. */
body:has(.chat) .page { padding-bottom: max(var(--space-x16), calc(var(--space-x6) + var(--touch-target) + var(--space-x6))); }
@media (max-width: 768px) {
  body:has(.chat) .page { padding-bottom: max(var(--space-x16), calc(var(--space-x4) + var(--touch-target) + var(--space-x6))); }
}
/* E14-F53-T04: кнопка «Помощник» не накрывает и подвал (вторая строка «Что-то не сходится…»
   на телефоне), а поле или кнопка, к которым страницу прокручивает фокус или якорь, встают
   выше неё. Безопасная зона iPhone — снизу у самой кнопки. */
html:has(.chat) { scroll-padding-bottom: calc(var(--space-x6) + var(--touch-target) + var(--space-x4)); }
body:has(.chat) .site-footer { padding-bottom: max(var(--space-x10), calc(var(--space-x6) + var(--touch-target) + var(--space-x4))); }
.chat { bottom: max(var(--space-x6), calc(env(safe-area-inset-bottom) + var(--space-x2))); }
@media (max-width: 640px) {
  .chat { bottom: max(var(--space-x4), calc(env(safe-area-inset-bottom) + var(--space-x2))); }
}

/* --------------------------------------------------------------------------
   Приложение с порога (E16-F29): гость на главной и витрине, блок «Начните с бесплатного».
   Только существующие токены; тёмная панель — gradients.brand-dark (на ней текст #fffcfa, 6.30:1
   к светлому стопу), вторичный текст на ней — text-on-inverse-muted.
   -------------------------------------------------------------------------- */
.guest-actions { display: flex; align-items: center; gap: var(--space-x2); margin-left: var(--space-x4); flex: none; }
.site-header__brand + .guest-actions { margin-left: auto; }
.guest-actions__login {
  display: inline-flex; align-items: center; min-height: var(--touch-target); padding: 0 var(--space-x4);
  border-radius: var(--radius-pill); color: var(--color-text-inverse); font: var(--type-label-md);
  white-space: nowrap; transition: background var(--motion-fast);
}
.guest-actions__login:hover { background: var(--color-inverse-a12); color: var(--color-text-inverse); text-decoration: none; }
.btn--on-dark-primary { background: var(--color-text-inverse); color: var(--color-primary); border-color: transparent; white-space: nowrap; }
.btn--on-dark-primary:hover { background: var(--color-surface-accent-soft); color: var(--color-primary); text-decoration: none; }
.btn--on-dark { background: transparent; color: var(--color-text-inverse); border: 1px solid var(--color-inverse-a40); }
.btn--on-dark:hover { background: var(--color-inverse-a12); color: var(--color-text-inverse); text-decoration: none; }

.guest-hero {
  background: var(--gradient-brand-dark, linear-gradient(0.125turn, #326386 0%, #123f50 100%));
  color: var(--color-text-inverse);
  border-radius: var(--radius-lg);
  padding: var(--space-x10) var(--space-x8);
}
.guest-hero__label { margin: 0 0 var(--space-x2); font: var(--type-label-caps); letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-on-inverse-muted); }
.guest-hero__title { margin: 0; font-family: var(--font-display, 'Oranienbaum', serif); font-weight: 400; font-size: 48px; line-height: 1.1; }
.guest-hero__lead { margin: var(--space-x4) 0 0; max-width: 56ch; font-size: 18px; line-height: 1.6; color: var(--color-text-inverse); }
.guest-hero__actions { margin-top: var(--space-x6); }
.guest-hero__note { margin: var(--space-x3) 0 0; font-size: 13px; color: var(--color-text-on-inverse-muted); }

.guest-legal { text-align: center; }
.app-guest-cta { margin-top: var(--space-x4); }


@media (max-width: 768px) {
  .guest-hero { padding: var(--space-x8) var(--space-x5); }
  .guest-hero__title { font-size: 36px; }
  .guest-hero__actions .btn { flex: 1 1 100%; }
}
@media (max-width: 480px) {
  /* На телефоне в шапке остаётся «Войти»: «Завести кабинет» стоит крупно в герое и на витрине. */
  .guest-actions__register { display: none; }
  .guest-actions { margin-left: var(--space-x2); }
}

/* Раунд 2 (E16-F29, правки владельца 16.10): одна сетка на главные. Ряды карточек — ровно три
   колонки на широком экране, две на планшете, одна на телефоне; верхний ряд «мысль дня +
   события» стоит на тех же колонках (1 + 2), поэтому края всех рядов совпадают. */
.app-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: stretch; }
.app-grid > li > .app-card { width: 100%; }
.app-section-head { margin-bottom: var(--space-x2); }
.app-section-head .section-title { margin: 0; }
.app-section-lead { margin: 0 0 var(--space-x4); }

.home-top { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-x5); align-items: stretch; }
.home-top__thought { grid-column: span 1; max-width: none; margin: 0; }
.home-top .home-events { grid-column: span 2; margin: 0; }
.home-top .home-events:only-child { grid-column: 1 / -1; }
figure.home-top__thought:hover { transform: none; box-shadow: var(--elevation-overlay); }
.home-top__thought .thought-card__image { flex: 1; min-height: 0; }
.home-events .event-row__body { min-width: 0; }
.home-events .event-row__title { overflow-wrap: anywhere; }

.app-card--cta { background: var(--gradient-brand-dark); border-color: transparent; color: var(--color-text-inverse); }
.app-card--cta:hover { color: var(--color-text-inverse); }
.app-card--cta .app-card__body { padding: var(--space-x6) var(--space-x5); justify-content: flex-start; }
.app-card__eyebrow { font: var(--type-label-caps); letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-on-inverse-muted); }
.app-card__title--display { font: var(--type-headline-md); }
.app-card--cta .app-card__summary { color: var(--color-text-inverse); }
.app-card--cta .app-card__note { color: var(--color-text-inverse); font: var(--type-label-md); }

.tariff-card__price { margin: 0; font: var(--type-title-lg); color: var(--color-text-primary); }

.thought-card__quote { display: none; }
/* В колонке трети подпись и ссылка в одну строку не помещаются — стопкой, ровно по левому краю. */
.home-top__thought .thought-card__foot { flex-direction: column; align-items: flex-start; justify-content: flex-start; gap: var(--space-x1); }

/* Планшет: мысль дня и события — каждая во всю ширину. Мысль дня ложится горизонтально:
   картинка квадратом слева, подпись и сама мысль справа — без растяжения картинки. */
@media (max-width: 1024px) {
  .home-top { grid-template-columns: 1fr; }
  .home-top__thought, .home-top .home-events { grid-column: 1 / -1; }
  .home-top__thought { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .home-top__thought .thought-card__image { flex: none; height: auto; aspect-ratio: 1 / 1; }
  .home-top__thought .thought-card__foot { flex-direction: column; align-items: flex-start; justify-content: center; gap: var(--space-x4); padding: var(--space-x6); }
  .home-top__thought .thought-card__quote { display: block; font: var(--type-headline-md); color: var(--color-text-primary); }
}
/* Две колонки: плитка подписки встаёт во всю ширину ряда, чтобы не висеть одна. */
@media (max-width: 900px) {
  .app-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .app-grid > .app-grid__wide { grid-column: 1 / -1; }
}
@media (max-width: 640px) {
  .home-top__thought { display: flex; }
  .home-top__thought .thought-card__foot { padding: var(--space-x3) var(--space-x5); }
  .home-top__thought .thought-card__quote { display: none; }
  .app-grid { grid-template-columns: 1fr; }
}

/* Заголовок ряда не ломается в две строки рядом с кнопкой; кнопка не сжимается. Нижняя пара
   карточек главной («Остаток пакета» и «Мои группы») — одной высоты. */
.app-section-head .section-title { flex: 1 1 auto; min-width: 0; }
.app-section-head > .btn { flex: none; white-space: nowrap; }
.panel-grid { align-items: stretch; }
