/* ==================================================================
   «Рядом» · дизайн-система (единственный css-файл приложения)

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

   Три правила, которым подчинён весь файл:

   1. Ни одной видимой рамки. Объём даётся только заливками и мягкими
      тенями; --hair допустим ИСКЛЮЧИТЕЛЬНО как внутренний контур
      (inset box-shadow) на стеклянных поверхностях — так сделано
      в референсе.
   2. Все размеры в rem или clamp. Исключения два, и оба не размеры:
      толщина линий (1px у --hair) — волосяная линия обязана оставаться
      волосяной и при «крупном тексте», и --r-pill (999px) — это не
      величина скругления, а команда «скругли до упора».
   3. Масштаб задан таблицей FIXES6 Д1 и живёт в токенах ниже. Прежняя
      нижняя граница «кнопка не ниже 72px, шрифт не мельче 17px»
      ОТМЕНЕНА: приложение перестало быть «для очень пожилых» и стало
      деловым инструментом для двоих, а такими размерами им неудобно
      пользоваться. Кому нужно крупнее — включает «Крупный текст»
      (html.big), и он по-прежнему растит весь интерфейс разом.

   Контракт разметки (что ждут стили от html/js):
     html.big                 — «крупный текст» и ЕДИНСТВЕННЫЙ его механизм:
                                js только переключает этот класс, инлайновый
                                style.fontSize не ставится никогда
     section.screen.on        — видимый экран (ровно один)
     .ctl > :first-child      — круг с иконкой, .ctl > :last-child — подпись
     .waves > svg и svg.waves — инлайновый SVG волн, 3 <path>; класс висит
                                либо на обёртке, либо прямо на svg
     .code-slots .slot        — шесть ячеек набранного номера
     .switch                  — переключатель целиком на псевдоэлементах,
                                внутри него разметки нет
     .wave-meter              — уровень громкости через style.setProperty('--level', 0..1)
   ================================================================== */

/* ------------------------------------------------------------------
   1. Токены
   ------------------------------------------------------------------ */

:root {
  color-scheme: dark;
  accent-color: #CDF56F;

  /* --- палитра (раздел 2.1 спецификации) --- */
  --bg-0: #0B0F0C;              /* почти чёрный с зелёным подтоном */
  --bg-1: #131A14;              /* на тон светлее, для градиента */
  --glow-1: #55702F;            /* оливковое пятно свечения */
  --glow-2: #7E9F4A;            /* светлое оливково-лаймовое пятно */
  --glow-3: #24361E;            /* холодное тёмно-зелёное пятно снизу */

  --lime: #CDF56F;              /* главный акцент, заливка первичных кнопок */
  --bad: #FF7A6B;               /* ошибка в поле: обводка, которую видно и без чтения подписи */
  --lime-soft: #D9EFA8;         /* пастельный лайм — вторая строка заголовка */
  --lime-deep: #B7E84F;         /* нажатое состояние лайма */

  --card: #23271F;              /* тёмная карточка */
  --gray: #454544;              /* серые плашки: строки списков на главном, карточка «Новая встреча» */
  --card-2: #2C3128;            /* вложенные плашки, поля ввода */
  --glass: rgba(255, 255, 255, 0.1);
  --glass-2: rgba(255, 255, 255, 0.16);
  --hair: rgba(255, 255, 255, 0.09);

  --white: #FFFFFF;             /* белая карточка — самый громкий элемент */
  --white-deep: #EEF1E9;        /* белая карточка/кнопка под пальцем */
  --ink: #10160C;               /* текст на лайме и на белом */

  --text: #FFFFFF;
  --text-2: rgba(255, 255, 255, 0.66);
  --text-3: rgba(255, 255, 255, 0.4);

  /* Чёрный под видео — не часть палитры интерфейса, а «нет сигнала»:
     любой другой цвет в этом месте выглядит как включённый экран
     с испорченной картинкой. Имя нужно, чтобы hex не расползался
     по инлайновым стилям в js. */
  --video-bg: #000000;

  /* --- скругления (2.3) ---
     в rem, чтобы форма не «плющилась» при увеличенном тексте:
     карточка вырастает — вырастает и её угол */
  --r-card: 1.5rem;             /* 24px — карточка по референсу */
  --r-card-s: 1.375rem;         /* 22px — строки списка, вложенные плашки, видео */
  /* Пилюля — единственное скругление в px: это не размер, а «скругли
     до упора», и расти вместе с крупным текстом ему незачем. */
  --r-pill: 999px;
  --r-bar: 1.875rem;            /* 30px — нижняя панель */

  /* --- типографика (2.4) ---
     запасная цепочка обязана быть полной: без интернета шрифт Onest
     не загрузится, и приложение всё равно должно выглядеть достойно */
  --font: 'Onest', -apple-system, BlinkMacSystemFont, 'Segoe UI Variable Text',
    'Segoe UI', 'Golos Text', Roboto, 'Helvetica Neue', Arial, sans-serif;

  /* Шкала FIXES6 Д1. Заголовок остаётся clamp — двухстрочный герой
     обязан ужиматься на 280px и не расползаться на планшете; всё
     остальное фиксировано, потому что вширь оно не растягивается. */
  --fs-hero: clamp(1.625rem, 7vw, 2.375rem);      /* 26…38px */
  --fs-h2: 1.25rem;                               /* 20px */
  --fs-lead: 1rem;                                /* 16px */
  --fs-btn: 1rem;                                 /* 16px */
  --fs-body: 0.9375rem;                           /* 15px */
  --fs-cap: 0.8125rem;                            /* 13px */
  --fs-code: clamp(1.875rem, 8.5vw, 2.625rem);    /* 30…42px */

  /* --- вырезы экрана (iPhone, Android с «чёлкой») ---
     собраны в переменные, чтобы не повторять env() в двадцати местах */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);

  /* --- экран разговора: величины, от которых отсчитывается карточка ожидания ---

     Шапка .callTop и своё окно .self лежат над сценой абсолютно, а
     карточка ожидания должна оставаться между ними. Пока их размеры
     стояли числами в собственных правилах, а отступы карточки — своими
     числами в другом файле, две стороны расходились: окно ужималось до
     8.75rem, а карточка продолжала отступать снизу фиксированные 12rem,
     и наоборот — верх карточки не отступал от шапки вовсе и уезжал под
     неё. Теперь обе стороны читают одни и те же переменные, и media-
     запросы меняют их в одном месте — здесь. */
  --calltop-top: calc(var(--safe-t) + 0.625rem);   /* отступ шапки от верха сцены */

  /* Высота шапки, посчитанная из её собственных величин: поля 0.5rem
     сверху и снизу, строка состояния (--fs-lead × 1.35 — межстрочное
     body), промежуток 0.125rem и строка имени (1.125rem × 1.35). Итого
     около 4rem. Записана формулой, а не числом, чтобы «крупный
     текст» растил её вместе с самой шапкой: обе стороны считаются
     от одних и тех же rem.

     Строка имени в этой сумме — запас, и вот почему он честный: пока
     видна карточка ожидания, собеседника нет, имя пустое и скрыто
     (screens.css гасит .who:empty), а в шапке остаётся одно состояние.
     Значит эта строка достаётся длинному состоянию — «Связь пропала,
     восстанавливаем…», — когда оно переносится на узком экране.
     Появился собеседник — карточка ожидания уже скрыта. */
  --calltop-h: calc(1rem + var(--fs-lead) * 1.35 + 0.125rem + 1.125rem * 1.35);

  /* Своё окно: размер и отступ от низа сцены. Звонок стал голосовым, и
     окна в разметке может не быть вовсе — тогда правило .stage без
     .self ниже обнуляет этот резерв, и карточка ожидания получает всю
     высоту сцены. Величины остаются на случай, если окно вернут. */
  --self-w: 5.5rem;
  --self-h: 7.25rem;
  --self-b: 0.75rem;

  /* --- сетка ---
     поля и промежутки ужаты примерно на треть вместе со всей шкалой
     (FIXES6 Д1): при кнопках 48–56px прежний воздух в 20px читался
     как разрыв между блоками, а не как поле экрана */
  --pad: 0.875rem;              /* поля экрана */
  --gap: 0.625rem;              /* расстояние между соседними блоками */
  --shell: 34rem;               /* максимальная ширина контента на планшете */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* --- кривые движения (FIXES7 Д7) ---

     Словарь взят у присланного образца (lectorium.bytewell.ru) и лежит
     здесь, рядом с --ease, а не в motion.css: кривая — это часть языка
     интерфейса, ею пользуются и переходы кнопок отсюда, и появление
     экранов из screens.css. Разложенные по двум файлам, они разъехались
     бы при первой же правке.

     Чем отличаются: --e-out тормозит у цели и никогда не перелетает —
     это выход, появление, «встало на место». --e-overshoot перелетает
     и возвращается — это отпущенная кнопка и доехавший переключатель,
     ощущение щелчка. --e-snap трогается резко — это нажатие, где любая
     мягкость читается как задумчивость. */
  --e-spring: cubic-bezier(0.32, 0.72, 0, 1);      /* основная пружина */
  --e-out: cubic-bezier(0.16, 1, 0.3, 1);          /* выход, появление */
  --e-in-out: cubic-bezier(0.65, 0, 0.35, 1);      /* непрерывное движение */
  --e-emph: cubic-bezier(0.2, 0.9, 0.1, 1);        /* подчёркнутое */
  --e-overshoot: cubic-bezier(0.34, 1.42, 0.64, 1); /* с перелётом */
  --e-snap: cubic-bezier(0.5, 0, 0, 1);            /* резкое начало */

  /* --- свечение капсулы (FIXES7 Д7) ---

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

     Белая середина градиента берётся из --white — своего третьего
     значения свечению не нужно. */
  --capsule-lilac: #B79BE8;     /* сиреневый край свечения */
  --capsule-sky: #A8D6F7;       /* голубой край свечения */
}

/* ------------------------------------------------------------------
   2. Сброс
   ------------------------------------------------------------------ */

*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  border: 0;                    /* рамок в проекте нет вообще */
  outline: 0;                   /* фокус рисуем сами, ниже, через :focus-visible */
  -webkit-tap-highlight-color: transparent;
}

[hidden] {
  display: none !important;
}

html {
  /* база 16px; «крупный текст» меняет ровно эту строку через .big,
     и вместе с ней — весь интерфейс, потому что всё в rem */
  font-size: 100%;
  height: 100%;
  -webkit-text-size-adjust: 100%;   /* iOS иначе сам «улучшает» размеры и ломает шкалу */
  text-size-adjust: 100%;
  /* Потянуть страницу вниз на Android — это перезагрузка, и посреди
     подписания она стоила человеку договора (docs/FIXES8.md Д3). На
     корневом элементе, а не только на body: именно его значение
     Chrome читает для жеста обновления. */
  overscroll-behavior-y: none;
}

/*
 * На телефоне и в установленном приложении полосы прокрутки нет вовсе —
 * ни у страницы, ни у полей и списков внутри неё (docs/FIXES13.md).
 *
 * Приложение стоит на экране телефона рядом с обычными и должно вести себя
 * так же, а серая полоса вдоль правого края при каждом движении пальца —
 * примета страницы в браузере. Прокрутка при этом никуда не девается: палец
 * двигает экран как двигал, пропадает только сама полоса.
 *
 * Условие — сенсорный экран или запуск с домашнего экрана. На компьютере с
 * мышью полоса остаётся: там это не украшение, а единственный способ
 * прокрутить страницу тому, у кого мышь без колеса.
 *
 * Правил два, потому что одного мало: scrollbar-width — стандартное свойство,
 * ::-webkit-scrollbar — то, что понимают Safari и старые Chrome. Звёздочка
 * нужна затем, что полоса есть не только у страницы: её рисуют и поле
 * расшифровки, и текст договора, и лента диктовки.
 */
@media (pointer: coarse), (display-mode: standalone) {
  * {
    scrollbar-width: none;
  }

  *::-webkit-scrollbar {
    display: none;
  }
}

html.big {
  /* 19px базы: кнопки, поля и подписи растут пропорционально,
     вёрстка не рассыпается, потому что нигде нет px-размеров.

     Это единственное место, где живёт размер «крупного текста».
     js только вешает и снимает класс: пока значение лежит в одной
     строке css, шкала не может разъехаться с вёрсткой, а инлайновый
     style.fontSize на <html> перебил бы её молча и навсегда. */
  font-size: 118.75%;
}

body {
  min-height: 100%;
  /* Столбец из одного растягивающегося блока — #app. Так он получает высоту
     окна без единиц vh/dvh; почему это важно — у правила #app ниже. */
  display: flex;
  flex-direction: column;
  font-family: var(--font);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.35;
  color: var(--text);
  background-color: var(--bg-0);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
  overscroll-behavior-y: none;  /* без «резинки»: пожилые часто тянут страницу случайно */
}

img,
svg,
video,
canvas {
  display: block;
  max-width: 100%;
}

svg {
  flex: 0 0 auto;               /* иконка внутри flex-кнопки не должна сжиматься */
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
  background: none;
  font-family: inherit;
}

button {
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
}

a {
  color: inherit;
  text-decoration: none;
}

::selection {
  background: var(--lime);
  color: var(--ink);
}

/* ------------------------------------------------------------------
   3. Фон (2.2)
   Слоями: три радиальных пятна + вертикальный градиент, всё зафиксировано
   и вынесено в псевдоэлемент — чтобы фон не перерисовывался при скролле
   и не «дёргался» на слабых телефонах.
   ------------------------------------------------------------------ */

body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    /* --glow-2 .42 */ radial-gradient(58% 42% at 74% 12%, rgba(126, 159, 74, 0.42) 0%, transparent 68%),
    /* --glow-1 .34 */ radial-gradient(72% 52% at 22% 30%, rgba(85, 112, 47, 0.34) 0%, transparent 72%),
    /* --glow-3 .60 */ radial-gradient(90% 60% at 50% 104%, rgba(36, 54, 30, 0.6) 0%, transparent 76%),
    linear-gradient(180deg, var(--bg-1) 0%, var(--bg-0) 62%, #090C09 100%);
  /* Никакого filter здесь: пятна мягкие за счёт transparent-стопов, а
     filter — даже blur(0) — заводит на весь экран слой с фильтром. */
}

/* Виньетка по краям: слегка гасит углы, чтобы белые и лаймовые элементы
   в центре читались как самые яркие точки экрана. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(120% 90% at 50% 45%, transparent 52%, rgba(0, 0, 0, 0.42) 100%);
}

/* ------------------------------------------------------------------
   4. Каркас и экраны (раздел 3)
   ------------------------------------------------------------------ */

/*
 * Высота экрана — ровно высота окна, и берётся она по цепочке, а не из
 * единиц окна (docs/FIXES14.md).
 *
 * Было `min-height: 100dvh` у #app и у экрана. В установленном приложении
 * Android эта единица у #app вычислялась на 56 пикселей больше окна (Chrome
 * считал её вместе со своей скрытой панелью и после этого не пересчитывал;
 * замерено на телефоне: окно 779, #app — 835). Каждый экран, даже тот, где
 * всё помещается, получал 56 пикселей пустой прокрутки и «ездил» вверх-вниз
 * под пальцем, как страница в браузере.
 *
 * Теперь html занимает окно (height: 100%, выше), body — не меньше html, а
 * #app и видимый экран растягиваются внутри как гибкие блоки. Эта высота —
 * результат раскладки, а не посчитанная заранее длина: устареть ей нечем, и
 * при любом изменении окна она пересчитывается вместе с ним. Экран, на
 * котором всё поместилось, теперь не прокручивается вовсе.
 */
#app {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.screen {
  display: none;
}

/* Видим ровно один экран — тот, у которого класс on. */
.screen.on {
  display: flex;
  flex-direction: column;
  flex: 1 0 auto;
  gap: var(--gap);
  width: 100%;
  max-width: var(--shell);
  margin: 0 auto;
  padding:
    calc(var(--safe-t) + var(--pad))
    calc(var(--safe-r) + var(--pad))
    calc(var(--safe-b) + var(--pad))
    calc(var(--safe-l) + var(--pad));
}

/* Экран разговора — единственный полноэкранный: видео идёт под вырезы,
   а собственные отступы расставляют .callTop и .controls. */
#s-call.on {
  padding: 0;
  max-width: none;
  gap: 0;
}

/*
 * Экраны «под большой палец» — вход, регистрация, логин и пароль, загрузка
 * данных (docs/FIXES14.md).
 *
 * Телефон держат одной рукой, и большой палец достаёт до нижних двух третей
 * экрана; верхняя треть — это уже перехват. Поэтому на этих экранах всё, от
 * заголовка до кнопки, начинается не от верхнего края, а ниже — тем же
 * приёмом, что в оболочке Samsung: сверху пустое место, под пальцем — дело.
 *
 * Отступ один и тот же на всех этих экранах — шесть rem, 96 пикселей. Сначала
 * он был «сколько останется места, но не больше трети экрана», и вход
 * начинался заметно ниже регистрации; владелец попросил одно расстояние
 * везде и выбрал то, что получилось у регистрации. Экран, который каждый раз
 * начинается с другой высоты, заставляет искать заголовок глазами заново.
 *
 * Устроен он гибким блоком перед содержимым: растёт до шести rem и не
 * дальше. Растёт, а не стоит постоянно, ради одного случая — маленький
 * телефон, на котором содержимое почти помещается: постоянный отступ дал бы
 * там лишнюю прокрутку на несколько пикселей, а гибкий просто станет чуть
 * меньше. Задан в rem, а не в долях окна, по той же причине, что высота
 * экрана выше: считать от окна — значит однажды посчитать не от того окна.
 *
 * Отрицательный отступ снизу гасит промежуток (--gap), который столбец
 * ставит между блоком-отступом и первым настоящим элементом.
 */
.screen.reach.on::before {
  content: '';
  flex: 1 1 0;
  max-height: 6rem;
  margin-bottom: calc(-1 * var(--gap));
}

/* Регистрация — единственный из этих экранов, который заведомо выше окна:
   пять карточек не помещаются ни на одном телефоне. Свободного места там
   нет вовсе, и гибкий отступ сжался бы до нуля — имя пришлось бы набирать у
   самого верхнего края. Поэтому здесь те же шесть rem заданы жёстко: экран
   всё равно прокручивается. */
#s-reg[data-mode='register'].on::before {
  flex: 0 0 6rem;
}

/* Заголовок входа и регистрации крупнее обычного (docs/FIXES14.md): на этих
   экранах он — первое, что видит человек, открыв приложение, и стоит один над
   формой. Остальным экранам такой размер не дан: там заголовки длиннее
   («Для работы приложения необходимо загрузить данные») и в этом кегле
   заняли бы пол-экрана. */
#s-reg .hero,
#s-cred .hero {
  font-size: clamp(2rem, 8.75vw, 2.75rem);   /* 32…44px вместо 26…38 */
}

/* Поле пароля с глазом у края (docs/FIXES14.md). Глаз лежит поверх поля, а
   не рядом: рядом он отнимал бы ширину у пароля, который и так набирают
   вслепую. Справа у поля оставлено место ровно под него. */
.pass-field {
  position: relative;
}

.pass-field > .field {
  padding-right: 3.25rem;
}

/* Попасть в глаз должно быть так же легко, как в кнопку: зона нажатия — во
   всю высоту поля и 48px в ширину, хотя сам значок вдвое меньше. */
.pass-eye {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 3rem;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--r-card-s);
  background: none;
  color: var(--text-2);
  cursor: pointer;
}

.pass-eye .ic > svg {
  width: 1.375rem;
  height: 1.375rem;
}

/* Пароль показан — глаз лаймовый: видно, что поле сейчас читается с экрана. */
.pass-eye[aria-pressed='true'] {
  color: var(--lime);
}

/* Пояснение под полем принадлежит полю, а не следующей карточке. */
.card > .field + .cap,
.card > .pass-field + .cap {
  margin-top: -0.25rem;
}

/* Отступ сверху перед заголовком: на экране с «Назад» его даёт сама
   кнопка, на экранах без неё — этот отбив, иначе заголовок липнет к вырезу. */
.screen.on > .hero:first-child {
  margin-top: 0.25rem;
}

.row {
  display: flex;
  align-items: center;
  gap: var(--gap);
}

/* ------------------------------------------------------------------
   5. Типографика
   ------------------------------------------------------------------ */

/* Двухстрочный заголовок — фирменный приём референса.
   Вторая строка обёрнута в <em>: em выбран потому, что это смысловое
   выделение, а не декоративный <span>, и без css оно всё равно читается. */
.hero {
  font-size: var(--fs-hero);
  font-weight: 400;
  line-height: 1.03;
  letter-spacing: -0.025em;
  color: var(--text);
  text-wrap: balance;
  /* «Пользовательское», «договорённость», «местоположение» — одно такое
     слово шире экрана телефона уже при минимальных 38px, и без разрешения
     переноса оно вылезает за край и тащит за собой горизонтальную прокрутку
     всей страницы. Уменьшать заголовок ради него нельзя (правило 1),
     переносить по слогам браузер не умеет: hyphens: auto для русского
     в Chrome ничего не делает — словаря нет. Остаётся разрыв внутри слова,
     и он включается ровно тогда, когда слово физически не помещается.
     Здесь достаточно break-word: заголовок — блок во всю ширину колонки,
     его ширину назначает не содержимое, и уменьшать её не нужно. */
  overflow-wrap: break-word;
  hyphens: auto;
}

.hero em {
  font-style: normal;           /* курсив в геометрическом гротеске выглядит чужеродно */
  color: var(--lime-soft);
}

h1,
h2,
h3 {
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.015em;
}

/* Русское слово легко бывает шире карточки: «Распознавание»,
   «Ответственность», «Константинопольский». На экране 280px это верно
   даже для обычного текста, а при «крупном тексте» — и на экране втрое
   шире. Разрешение переносить слово внутри себя ничего не меняет там,
   где оно и так помещается: правило срабатывает ровно в тот момент,
   когда иначе строка вылезла бы за край. */
h1,
h2,
h3,
p,
li,
dt,
dd {
  overflow-wrap: break-word;
}

h2,
.h2 {
  font-size: var(--fs-h2);
}

h3,
.h3 {
  font-size: var(--fs-lead);
  font-weight: 500;
}

.lead {
  font-size: var(--fs-lead);
  font-weight: 400;
  line-height: 1.35;
  color: var(--text-2);
  text-wrap: pretty;
}

/* Внутри белой карточки текст тёмный, поэтому приглушённый оттенок
   берётся от чернил, а не от белого: белый с прозрачностью на белом
   фоне попросту исчезает. */
.card.light .lead {
  color: rgba(16, 22, 12, 0.62);
}

.cap {
  font-size: var(--fs-cap);
  color: var(--text-2);
}

/* Номер встречи. Цвет наследуется, чтобы один и тот же класс работал
   и на тёмной карточке (белым), и на белой (чернилами). */
.code {
  font-size: var(--fs-code);
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: 0.06em;
  color: inherit;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Пояснение под полем/кнопкой: «Такой встречи нет» и подобные. */
.hint {
  font-size: var(--fs-body);
  line-height: 1.3;
  color: var(--text-2);
  min-height: 1.25rem;          /* место зарезервировано заранее: текст появляется
                                   и исчезает, а кнопки под ним не должны прыгать */
}

.hint:empty {
  min-height: 1.25rem;
}

/* ------------------------------------------------------------------
   6. Кнопки (2.5)
   ------------------------------------------------------------------ */

.btn {
  min-height: 3rem;             /* 48px — обычная кнопка по шкале Д1 */
  border-radius: var(--r-pill);
  font-size: var(--fs-btn);
  font-weight: 500;
  line-height: 1.15;
  display: flex;
  gap: 0.625rem;
  align-items: center;
  justify-content: center;
  padding: 0 1.25rem;
  border: 0;
  cursor: pointer;
  transition: transform 0.12s var(--ease), background 0.16s;
  background: var(--glass);
  color: var(--text);
  text-align: center;
}

/* Главное действие экрана — лаймовая, а на тёмно-зелёных экранах белая
   («Завершить», «Войти», «Вернуться к встрече»). Оно выше остальных на
   8px: разница видна, но не превращает второстепенные кнопки в мелочь.
   Шкала Д1: главная 56px, обычная 48px. */
.btn.primary,
.btn.white {
  min-height: 3.5rem;           /* 56px — главная кнопка по шкале Д1 */
}

.btn.primary {
  background: var(--lime);
  color: var(--ink);
}

/* «Стекло» — полупрозрачный белый и волосяная обводка, без backdrop-filter.
   Размытие подложки на прокручиваемом элементе пересчитывается каждый
   кадр прокрутки, и десяток таких кнопок на экране договора превращали
   прокрутку на телефоне в рывки. Над нашим фоном — ровным градиентом —
   размывать нечего, глазом разницы нет. */
.btn.glass {
  background: var(--glass);
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--hair);   /* единственная допустимая «обводка» */
}

.btn.white {
  background: var(--white);
  color: var(--ink);
}

.btn.wide {
  width: 100%;
}

/* Нажатие: кнопка ощутимо «проваливается». Для человека, который не уверен,
   сработало ли касание, это главный отклик интерфейса. */
.btn:active {
  transform: scale(0.975);
}

.btn.primary:active {
  background: var(--lime-deep);
}

.btn.glass:active {
  background: var(--glass-2);
}

.btn.white:active {
  background: var(--white-deep);
}

.btn[disabled] {
  opacity: 0.38;
  pointer-events: none;
}

/* --- размер значков ---

   icons.js рисует svg в 30px — величина прежней шкалы, где кнопка была
   88px, а надпись в ней 22px. Рядом с надписью в 16px и в кнопке 56px
   тот же значок читается как главное в кнопке, хотя смысл несёт слово;
   а нижняя панель со значком 30px не умещается в свои 60px из таблицы
   Д1 — её распирает изнутри содержимое вкладки.

   Уменьшаем здесь, а не в js: размер значка — оформление, и менять его
   должен css. Атрибуты width и height у svg презентационные, поэтому
   любое правило ниже их перебивает, и js остаётся единственным местом,
   где живёт форма значка.

   Волны (.waves, .wave-meter) сюда не попадают намеренно: это не значок,
   а рисунок во всю ширину блока, и своя ширина у него уже есть. */
.ic > svg {
  width: 1.375rem;              /* 22px */
  height: 1.375rem;
}

/* В круге панели звонка значок остаётся крупнее: круг 52px — главная
   цель того экрана, и пустое кольцо вокруг мелкого значка выглядело бы
   как незагруженная кнопка. */
.ctl .ic > svg {
  width: 1.5rem;
  height: 1.5rem;
}

/* «Назад» — самая тихая кнопка экрана (40px), и значок в ней тише всех:
   рядом со словом «Назад» он подсказка, а не действие. */
.back .ic > svg,
.top-home .ic > svg {
  width: 1.125rem;
  height: 1.125rem;
}

/* Кнопка «Назад» — единственная, где иконка есть всегда, и рядом с ней
   обязательно слово «Назад» (правило раздела 1).
   40px по шкале Д1: «Назад» стоит в углу над заголовком, ниже
   остальных кнопок по смыслу — и теперь ниже по росту, чтобы не
   спорить с главным действием экрана.
   `.top-home` — «На главную» той же формы, в той же строке справа от
   «Назад» (`.top-nav`): у неё свой класс, потому что на `.back` app.js вешает
   переход назад по истории. */
.back,
.top-home {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  height: 2.5rem;
  padding: 0 0.875rem 0 0.625rem;
  border-radius: var(--r-pill);
  background: var(--glass);
  color: var(--text);
  font-size: var(--fs-body);
  font-weight: 500;
  box-shadow: inset 0 0 0 1px var(--hair);
  transition: transform 0.12s var(--ease), background 0.16s;
}

.back:active,
.top-home:active {
  transform: scale(0.97);
  background: var(--glass-2);
}

/* Строка наверху экрана: «Назад» слева, «На главную» справа. */
.top-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.top-nav > .back,
.top-nav > .top-home {
  align-self: auto;
}

/* Круглая кнопка. Диаметр — те же 48px, что и у обычной кнопки:
   круглая форма не повод выпадать из общей шкалы. */
.round {
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  background: var(--glass);
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--hair);
  transition: transform 0.12s var(--ease), background 0.16s;
}

.round.lime {
  background: var(--lime);
  color: var(--ink);
  box-shadow: none;             /* на плотной заливке контур не нужен */
}

.round:active {
  transform: scale(0.94);
}

.round.lime:active {
  background: var(--lime-deep);
}

/* ------------------------------------------------------------------
   7. Карточки
   ------------------------------------------------------------------ */

.card {
  position: relative;           /* волны позиционируются относительно карточки */
  background: var(--card);
  border-radius: var(--r-card);
  padding: 1.125rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  color: var(--text);
}

.card.light {
  background: var(--white);
  color: var(--ink);
}

/* Зелёная карточка референса. overflow: hidden — чтобы волны,
   прижатые к низу, обрезались точно по её скруглению. */
.card.accent {
  background: linear-gradient(160deg, #4E7C3F, #2E5A2C);
  color: var(--text);
  overflow: hidden;
  padding-bottom: 1.5rem;       /* запас под волны, чтобы текст на них не наезжал */
}

/* Номер шага на карточках помощи. */
.num {
  width: 2.25rem;
  height: 2.25rem;
  flex: 0 0 auto;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--lime);
  color: var(--ink);
  font-size: var(--fs-btn);
  font-weight: 500;
}

.card.light .num {
  background: var(--ink);
  color: var(--white);
}

.card.accent .num {
  background: var(--lime);
  color: var(--ink);
}

/* Место под 3D-рендер из public/img. Здесь только форма и размер:
   конкретную картинку каждому месту назначает screens.css отдельным
   слоем background-image, поверх нарисованной там же заглушки.
   Файла может не быть (раздел 7) — слой просто не отрисуется,
   и экран останется целым. */
.art {
  border-radius: var(--r-card-s);
  aspect-ratio: 4 / 3;
  background-color: var(--card-2);
  background-image: none;
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  box-shadow: inset 0 0 0 1px var(--hair);
}

/* Логотип-лотос и слово «рядом» на первом экране. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--lime-soft);
  font-size: var(--fs-lead);
  font-weight: 500;
  letter-spacing: 0.01em;
}

/* ------------------------------------------------------------------
   8. Чипы
   ------------------------------------------------------------------ */

/* min-height вместо height: при «крупном тексте» подпись обязана
   раздвинуть чип, а не обрезаться в нём. */
.chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  min-height: 2.625rem;         /* 42px — чип-фильтр референса */
  padding: 0.25rem 1.125rem;
  border-radius: var(--r-pill);
  background: rgba(24, 29, 21, 0.88);   /* плотнее прежнего: без размытия подложки */
  box-shadow: inset 0 0 0 1px var(--hair);
  font-size: var(--fs-body);
  font-weight: 500;
  color: rgba(255, 255, 255, 0.92);
  white-space: nowrap;
  transition: background 0.16s, color 0.16s, transform 0.12s var(--ease);
}

button.chip {
  border: 0;
  cursor: pointer;
}

button.chip:active {
  transform: scale(0.96);
}

/* Активный — белый с чернильной надписью: самый контрастный из возможных
   на тёмном фоне, чтобы было видно, что показано ниже. */
.chip.on {
  background: var(--white);
  color: var(--ink);
  box-shadow: none;
}

/* Кружок-счётчик у правого верхнего края чипа («All ⑪»). На тёмном чипе
   белый, на белом активном — лаймовый, иначе он сливается с чипом. */
.chip.on .cnt {
  background: var(--lime);
  color: var(--ink);
}

.chip .cnt {
  position: absolute;
  top: -0.3125rem;
  right: -0.25rem;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.375rem;
  border-radius: var(--r-pill);
  display: grid;
  place-items: center;
  background: var(--white);
  color: var(--ink);
  font-size: 0.7rem;
  font-weight: 700;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.625rem;
}

/* Ряд чипов главного экрана: одна строка, прокручивается вбок, полосы нет. */
.chips.scroll {
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  /* Выпуск за поля экрана — ровно на ширину этих полей (0.875rem, см.
     .screen), чтобы ряд прокручивался от края до края, но не шире окна. */
  margin: 0 -0.875rem;
  padding: 0.375rem 0.875rem 0.25rem;
}
.chips.scroll::-webkit-scrollbar {
  display: none;
}
.chips.scroll .chip {
  flex: 0 0 auto;
}

/* ------------------------------------------------------------------
   9. Нижняя панель разделов
   ------------------------------------------------------------------ */

/* sticky, а не fixed: панель прилипает к низу экрана, но остаётся частью
   потока — значит, никогда не накрывает последнюю кнопку контента
   и не требует «магического» padding-bottom у каждого экрана. */
/* Панель — приподнятая плита, а не стекло. Низ экрана — самое тёмное
   место фона (градиент уходит в #090C09 плюс виньетка), и панель того
   же тона на нём терялась. Поэтому она светлее подложки на два шага
   (тон --card-2), непрозрачная, с волосяной обводкой и тенью под собой.

   Никакого backdrop-filter: у липкого элемента размытие подложки
   пересчитывается на каждом кадре прокрутки — это главная причина,
   по которой прокрутка на телефоне шла рывками. */
.tabbar {
  position: sticky;
  /* Отступ снизу равен полю экрана (--pad): тогда панель стоит на одном
     и том же месте и когда содержимое короче экрана (она внизу колонки),
     и когда длиннее (она прилипла к низу окна). Иначе при переходе между
     разделами она прыгала бы на разницу этих отступов. */
  bottom: calc(var(--safe-b) + var(--pad));
  z-index: 20;
  margin-top: auto;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  --tab-gap: 0.25rem;           /* промежуток между вкладками; тот же шаг у шайбы */
  gap: var(--tab-gap);
  align-items: stretch;
  /* 60px: вкладка 52px (значок и подпись словами) плюс поля панели по 4px.
     Подпись под значком обязательна по SPEC §1 — см. .tab .cap ниже. */
  min-height: 3.75rem;
  padding: 0.25rem;
  border-radius: var(--r-pill);
  background: var(--card-2);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.12),
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 0.75rem 2rem rgba(0, 0, 0, 0.55);
  /* Шайба не выходит за плиту ни на одном кадре (`docs/FIXES18.md`, п. 5).
     Движение само построено так, чтобы не выходить (`app.js`,
     `animateTabSwitch`), а обрезка — второй замок: свечение шайбы шире её
     самой, и у края плиты оно торчало бы наружу. */
  overflow: hidden;
}

.tabbar button,
.tabbar .tab {
  display: flex;
  flex-direction: column;       /* значок, под ним подпись словами */
  align-items: center;
  justify-content: center;
  gap: 0.125rem;
  /* 52px: значок 20px, подпись 17px и поля. Ниже палец промахивается, а
     подпись под значком обязательна — см. .cap ниже. */
  min-height: 3.25rem;
  padding: 0.25rem 0.125rem;
  border-radius: var(--r-pill);
  border: 0;
  background: none;
  color: rgba(255, 255, 255, 0.78);
  position: relative;
  z-index: 1;                   /* над шайбой */
  transition: color 0.22s var(--e-out), transform 0.22s var(--e-out);
}

/* Неактивные разделы — чуть меньше и тусклее (`docs/FIXES18.md`, п. 5):
   выбранный читается не только цветом и шайбой, но и размером. Уменьшается
   вкладка целиком, значок вместе с подписью, — у значка transform занят
   подбросом при переключении. Тусклее, но не бледнее читаемого: подпись
   17px на этой плите при 0.56 даёт контраст около 6:1. */
.tabbar .tab:not(.on) {
  color: rgba(255, 255, 255, 0.56);
  transform: scale(0.93);
}

/*
 * Шайба — подложка активного раздела, одна на панель. Не у каждой
 * вкладки своя, а одна общая, которая при переключении переезжает под
 * новую вкладку: так подсветка не мигает «тут погасло — там зажглось»,
 * а идёт по панели как тело, с пружиной и растяжением по дороге
 * (`app.js`, `animateTabSwitch`). Положение в покое задаёт --i
 * (0, 1, 2) через transform, поэтому переезд целиком композитный.
 *
 * Ширина — треть панели без её полей и двух промежутков между
 * вкладками; шаг — своя ширина плюс промежуток.
 */
.tabbar .puck {
  position: absolute;
  top: 0.25rem;
  bottom: 0.25rem;
  left: 0.25rem;
  width: calc((100% - 0.5rem - 2 * var(--tab-gap)) / 3);
  border-radius: var(--r-pill);
  background: rgba(205, 245, 111, 0.14);
  box-shadow: inset 0 0 0 1px rgba(205, 245, 111, 0.14);
  pointer-events: none;
  will-change: transform;
  transform: translateX(calc(var(--i, 1) * (100% + var(--tab-gap))));
}

/* Свечение шайбы: загорается только на время переезда (класс moving
   ставит js) и гаснет по прибытии. Только opacity — никакого blur. */
.tabbar .puck::after {
  content: '';
  position: absolute;
  inset: -0.5rem -0.625rem;
  border-radius: inherit;
  background: radial-gradient(55% 60% at 50% 50%, rgba(205, 245, 111, 0.38) 0%, rgba(205, 245, 111, 0) 72%);
  opacity: 0;
  transition: opacity 0.32s var(--e-out);
}
.tabbar.moving .puck::after {
  opacity: 1;
  transition-duration: 0.12s;
}

/* Подпись словами под каждым значком — обязательна, а не украшение.
   SPEC §1: «у каждой иконки есть текстовая подпись, кнопок „только иконка“
   нет» (кроме «Назад»), FIXES Р2/Р3 — то же, и активная вкладка красит
   лаймом «значок И подпись». В референсе подписи не было, и она была
   спрятана; для аудитории 75+ безымянные «сетка» и «человек» в главной
   навигации — ровно то, что дизайн-код запрещает. 17px — нижняя граница
   размера подписи по SPEC §1. */
.tabbar .tab .cap {
  display: block;
  font-size: 1.0625rem;
  line-height: 1;
  font-weight: 500;
  letter-spacing: -0.01em;
  white-space: nowrap;
}

.tabbar .tab .ic > svg,
.tabbar button .ic > svg {
  width: 1.25rem;
  height: 1.25rem;
}

.tabbar .tab:active {
  transform: scale(0.9);
}

/* Активный раздел — лаймовый значок, как в референсе; подложка под ним —
   это шайба (.puck), а не фон вкладки. Заливать лаймом целиком нельзя —
   прямо над панелью на экранах настроек и помощи стоит лаймовая главная
   кнопка. */
.tabbar .on {
  color: var(--lime);
}

/* Значок — отдельный слой, чтобы его можно было «подбросить» при
   переключении (js), не трогая саму вкладку. */
.tabbar .tab .ic {
  will-change: transform;
}

/* ------------------------------------------------------------------
   10. Переключатель
   ------------------------------------------------------------------ */

/* Переключатель 52×32 по шкале Д1. Прежде нажимаемая область (78×72)
   была втрое толще самой дорожки и жила отдельно от рисунка; на новой
   шкале дорожка занимает переключатель целиком, и разводить их больше
   не на что. Ползунок по-прежнему центрируется отрицательным margin,
   а не transform: transform у него занят ходом влево-вправо, и
   вертикальное центрирование, записанное туда же, обнулилось бы
   при включении.

   Состояние читается двумя способами — класс .on и aria-checked, —
   чтобы вид не зависел от того, какой из них js обновит первым. */
.switch {
  position: relative;
  flex: 0 0 auto;
  width: 3.25rem;               /* 52px */
  height: 2rem;                 /* 32px */
  background: none;
  box-shadow: none;
}

/* Дорожка во всю кнопку: 52×32. */
.switch::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: 2rem;                 /* 32px */
  margin-top: -1rem;
  border-radius: var(--r-pill);
  background: var(--glass);
  box-shadow: inset 0 0 0 1px var(--hair);
  transition: background 0.18s var(--ease);
}

.switch::after {
  content: '';
  position: absolute;
  left: 0.1875rem;
  top: 50%;
  width: 1.625rem;              /* 26px */
  height: 1.625rem;
  margin-top: -0.8125rem;
  border-radius: 50%;
  background: var(--white);
  box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.35);
  transition: transform 0.18s var(--ease);
}

.switch.on::before,
.switch[aria-checked='true']::before {
  background: var(--lime);
  box-shadow: none;
}

/* 52 − 26 − 3 − 3 = 20px хода */
.switch.on::after,
.switch[aria-checked='true']::after {
  transform: translateX(1.25rem);
}

/* ------------------------------------------------------------------
   11. Своя цифровая клавиатура (3.3)
   ------------------------------------------------------------------ */

.keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.5rem;
}

/* 56px: системную клавиатуру мы не используем именно потому, что её
   клавиши мелкие; своя остаётся крупнейшей кнопкой шкалы Д1. */
.key {
  min-height: 3.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-pill);
  background: var(--glass);
  color: var(--text);
  font-size: 1.5rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  box-shadow: inset 0 0 0 1px var(--hair);
  transition: transform 0.12s var(--ease), background 0.16s;
}

.key:active {
  transform: scale(0.96);
  background: var(--glass-2);
}

/* ------------------------------------------------------------------
   12. Ячейки номера встречи
   ------------------------------------------------------------------ */

.code-slots {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 0.375rem;
}

/* 52px: ячейка не кнопка, но должна оставаться крупной; выше делать нельзя —
   шесть узких колонок превратились бы в неестественно вытянутые капсулы.

   Селектор по классу, а не по «любому ребёнку»: в группу набора можно
   добавить служебный элемент (подпись для экранного диктора, разделитель),
   и он не обязан выглядеть как ячейка с цифрой. */
.code-slots .slot {
  min-height: 3.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-card-s);
  background: var(--glass);
  color: var(--text);
  font-size: clamp(1.25rem, 5.4vw, 1.75rem);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  box-shadow: inset 0 0 0 1px var(--hair);
  transition: background 0.16s;
}

/* Заполненность определяется наличием текста, а не классом: js просто
   пишет цифру в ячейку, и подсветка появляется сама. */
.code-slots .slot:not(:empty) {
  background: var(--glass-2);
}

/* В пустой ячейке — короткая чёрточка: пустой прямоугольник читается
   как «сломано», чёрточка — как «сюда впишется цифра». */
.code-slots .slot:empty::after {
  content: '';
  width: 0.625rem;
  height: 0.125rem;
  border-radius: var(--r-pill);
  background: var(--text-3);
}

/* ------------------------------------------------------------------
   13. Поля ввода и списки устройств
   ------------------------------------------------------------------ */

.field,
select,
input[type='text'],
input[type='search'] {
  width: 100%;
  min-height: 3rem;             /* 48px */
  padding: 0 0.875rem;
  border-radius: var(--r-card-s);
  background: var(--card-2);
  color: var(--text);
  font-size: var(--fs-lead);
  font-weight: 400;
}

input::placeholder {
  color: var(--text-3);
}

/* Поле, которое не подошло (`docs/FIXES19.md`, дополнение): красная обводка.
   Строка «Логин или пароль не подошли» стоит под кнопкой, внизу формы, а
   смотрит человек в эту секунду на поле, в которое только что писал, — и
   без обводки видит форму, в которой ничего не изменилось. Обводка внутрь,
   тенью: поле не меняет размера и соседей не двигает. Снимается первой же
   набранной буквой (`app.js`, `paintRegHint`). */
.field.bad {
  box-shadow: inset 0 0 0 2px var(--bad);
}

/* Своя стрелка вместо системной: системная в тёмной теме на разных
   платформах выглядит по-разному и часто почти не видна.
   SVG зашит в data-uri — внешних файлов у css нет. */
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 2.5rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23FFFFFF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.875rem center;
  background-size: 1.25rem 1.25rem;
  text-overflow: ellipsis;
}

/* Выпадающий список рисует система, поэтому его пункты красим отдельно:
   иначе на Windows получается белый текст на белом фоне. */
select option {
  background: var(--card);
  color: var(--text);
}

.card.light select,
.card.light input {
  background: rgba(16, 22, 12, 0.06);
  color: var(--ink);
}

/* ------------------------------------------------------------------
   14. Экран проверки и экран разговора (3.4, 3.5)
   ------------------------------------------------------------------ */

.preview {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: var(--r-card-s);
  overflow: hidden;
  background: var(--card-2);
}

.preview video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Своё изображение зеркалим: непривычно видеть себя «как в кино»,
   человек не узнаёт собственные движения. Собеседника не зеркалим. */
#checkVideo,
#selfVideo {
  transform: scaleX(-1);
}

/* Сцена звонка растягивается на всё свободное место, а панель управления
   остаётся под ней в потоке — поэтому панель физически не может
   спрятаться или наехать на видео. */
.stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  background: var(--video-bg);
  overflow: hidden;
}

/* Фон задан здесь, а не инлайновым стилем из js: пока кадр не пришёл,
   место собеседника обязано быть чёрным, и это свойство картинки,
   а не решение, которое принимает код звонка. */
#remoteVideo {
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--video-bg);
}

/* Своё окно — снизу справа, внутри сцены: оно автоматически оказывается
   выше панели управления, без подгонки отступов под её высоту. */
.self {
  position: absolute;
  right: calc(var(--safe-r) + 0.75rem);
  bottom: var(--self-b);
  width: var(--self-w);
  height: var(--self-h);
  border-radius: var(--r-card-s);
  overflow: hidden;
  background: var(--card-2);
  box-shadow: 0 0.75rem 1.75rem rgba(0, 0, 0, 0.5), inset 0 0 0 1px var(--hair);
}

.self video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

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

   Отступ сверху вынесен в --calltop-top, а высота посчитана в --calltop-h
   (раздел 1): от них отсчитывает свой верхний отступ карточка ожидания.
   Меняя здесь поля, промежуток или размер строк, поправьте и формулу
   --calltop-h — иначе карточка снова уедет под шапку. */
.callTop {
  position: absolute;
  top: var(--calltop-top);
  left: 50%;
  transform: translateX(-50%);
  max-width: calc(100% - 1.5rem);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.125rem;
  padding: 0.5rem 1rem;
  border-radius: var(--r-bar);
  background: rgba(11, 15, 12, 0.55);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  text-align: center;
}

.callTop .status {
  font-size: var(--fs-lead);
  font-weight: 500;
  color: var(--text);
}

.callTop .who {
  font-size: 1.125rem;
  font-weight: 500;
  color: var(--lime-soft);
  /* Имя собеседника приходит из его настроек, и длина его ничем не
     ограничена: «Константинопольский» одним словом шире шапки на узком
     телефоне. Шапка стоит absolute и ширину берёт по содержимому, поэтому
     обычного break-word мало — он не влияет на минимальную ширину блока,
     и шапка всё равно раздувается за край экрана. anywhere уменьшает и её:
     сначала имя переносится целиком, и только если не влезает и так —
     разрывается. */
  overflow-wrap: anywhere;
}

/* Ожидание второго участника — карточка по центру сцены.

   Центрирование сделано парой «flex-start + margin: auto», а не
   align-items: center. Разница видна ровно тогда, когда карточка выше
   области: центрирование раздаёт недостающее место поровну и уводит
   верх карточки ЗА начало прокрутки — туда, куда прокрутить нельзя, и
   первые строки («Ждём собеседника», номер встречи) пропадали совсем.
   Автоматические поля работают иначе: пока свободное место есть, они
   делят его пополам и карточка стоит по центру; как только места не
   стало, поля обнуляются, и карточка прижимается к началу прокрутки —
   доступной остаётся вся.

   Отступы считаются от соседей, а не назначены на глаз:
   сверху — место шапки .callTop целиком (иначе карточка под ней),
   снизу — место своего окна .self (иначе оно накрывает «Отправить
   ссылку», единственное действие экрана), по бокам — вырезы экрана,
   которые в альбомной ориентации учитывает и соседняя .controls. */
/*
 * Резерв под соседей — это ГЕОМЕТРИЯ области, а не её внутренний отступ.
 *
 * Отступом это уже пробовали, и не сработало: padding сдвигает содержимое, но
 * прямоугольник обрезки остаётся во весь экран. Стоило карточке не поместиться,
 * как она прокручивалась внутрь отступа — прямо под своё окно `.self` и под
 * шапку `.callTop`. Измерения показывали перекрытие на восьми размерах из
 * двенадцати: на 375x667 накрыто 33 380 px², из кнопки «Отправить ссылку»
 * (тогда, на прежней шкале, 88px высотой) было видно 4 пикселя. Человек
 * видел зелёную полоску без надписи и не знал, что это кнопка.
 *
 * Инсеты решают это в корне: область прокрутки начинается ПОД шапкой и
 * заканчивается НАД своим окном, поэтому наехать не на что ни в одном
 * положении прокрутки. Боковые поля остаются в padding — там резервировать
 * нечего, кроме выреза экрана.
 */
.waiting {
  position: absolute;
  inset:
    calc(var(--calltop-top) + var(--calltop-h) + 0.5rem)
    0
    calc(var(--self-h) + var(--self-b) + 0.5rem)
    0;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 0 calc(var(--safe-r) + var(--pad)) 0 calc(var(--safe-l) + var(--pad));
}

/*
 * Звонок стал голосовым (FIXES6 Д4), и своё окно из разметки убирают.
 * Резерв под него держится на переменных --self-*, а не на самом элементе,
 * — значит без этого правила карточка ожидания продолжала бы отступать
 * снизу на 8rem от пустоты и потеряла бы треть сцены. Условие смотрит на
 * разметку, а не на настройку: вернут окно — вернётся и резерв, разводить
 * их снова неоткуда.
 *
 * Резерв под шапку при этом остаётся: .callTop с состоянием связи
 * никуда не делся и по-прежнему лежит над сценой.
 *
 * :has() — прогрессивное улучшение: где его нет, правило не применится
 * и раскладка останется прежней, тесной, но не сломанной.
 */
.stage:not(:has(> .self)) {
  --self-h: 0rem;
  --self-b: 0rem;
}

/*
 * Пока второй не вошёл, своё окно уступает место карточке.
 *
 * Область ожидания живёт между шапкой и своим окном, и на телефоне 375x667
 * ей оставалось 99 пикселей при карточке в 329: номер встречи — то
 * единственное, ради чего экран существует, — приходилось выискивать
 * прокруткой в щель. Резерв под своё окно съедал экран целиком.
 *
 * Пока собеседника нет, смотреть в своё окно почти не на что: проверить,
 * как ты выглядишь, человек пошёл бы на «Проверить камеру и звук». Поэтому
 * на время ожидания оно уменьшается вдвое, а карточка получает высоту.
 * Как только второй вошёл, окно возвращается к обычному размеру само.
 *
 * :has() — прогрессивное улучшение: где его нет, правило просто не применится
 * и останется прежняя раскладка. Она тесная, но не сломанная.
 *
 * Условие «> .self» в начале обязательно: без него это правило, будучи
 * специфичнее, возвращало бы ненулевой резерв даже там, где окна в разметке
 * нет вовсе, и отменяло бы обнуление выше.
 */
.stage:has(> .self):has(> .waiting:not([hidden])) {
  --self-w: 3.75rem;
  --self-h: 5rem;
}

.waiting .card {
  width: 100%;
  max-width: var(--shell);
  margin: auto;                 /* по горизонтали свободного места нет
                                   (ширина 100%), поэтому поля работают
                                   только по вертикали — см. выше */
}

/* Панель управления. Раскладка в три колонки решает сразу две задачи:
   три .ctl встают в ряд по 1fr, а «Завершить» растягивается на всю
   ширину простым grid-column — без лишних обёрток в разметке. */
.controls {
  flex: 0 0 auto;
  display: grid;
  /* minmax(0, …), а не просто 1fr: иначе колонка сетки не имеет права
     стать уже своего содержимого, и самая длинная надпись панели
     («Зафиксировать договорённость») расталкивает всю панель шире
     экрана — в альбомной ориентации она уезжала за правый край
     вместе с «Завершить». С нулевым минимумом колонка держится
     границ панели, а длинная надпись переносится по словам. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.5rem;
  padding:
    0.75rem
    calc(var(--safe-r) + 0.75rem)
    calc(var(--safe-b) + 0.75rem)
    calc(var(--safe-l) + 0.75rem);
  background: var(--card);
  border-radius: var(--r-bar) var(--r-bar) 0 0;
}

.controls > .btn,
.controls > .volrow {
  grid-column: 1 / -1;
}

/* Вертикальная кнопка звонка: круг с иконкой и подпись словами под ним.
   Подпись обязательна — состояние нельзя показывать одной лишь
   перечёркнутой иконкой. */
.ctl {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
  padding: 0;
  background: none;
  color: var(--text);
}

.ctl > :first-child {
  width: 3.25rem;               /* 52px */
  height: 3.25rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--glass);
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--hair);
  transition: background 0.16s, transform 0.12s var(--ease), color 0.16s;
}

/* Если иконку положили прямо в кнопку, без обёртки, — она сама
   становится кругом, и внутренний отступ не даёт ей упереться в края. */
.ctl > svg:first-child {
  padding: 0.75rem;
}

.ctl > :last-child {
  font-size: var(--fs-cap);
  font-weight: 500;
  line-height: 1.15;
  color: var(--text-2);
  text-align: center;
  text-wrap: balance;
}

/* Включено — лайм, выключено — стекло. Цвет дублирует подпись,
   а не заменяет её. */
.ctl.on > :first-child {
  background: var(--lime);
  color: var(--ink);
  box-shadow: none;
}

.ctl.on > :last-child {
  color: var(--text);
}

.ctl:active > :first-child {
  transform: scale(0.94);
}

/* Строка громкости, раскрываемая кнопкой «Громкость». */
.volrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.375rem 0.5rem;
  border-radius: var(--r-pill);
  background: var(--glass);
  box-shadow: inset 0 0 0 1px var(--hair);
  font-size: var(--fs-body);
  font-weight: 500;
}

/* ------------------------------------------------------------------
   15. Волны (2.6)
   Мотив референса. Разметка — инлайновый SVG с тремя <path>:

     <div class="waves" aria-hidden="true">
       <svg viewBox="0 0 600 120" preserveAspectRatio="none">
         <path class="w1" d="…"/><path class="w2" d="…"/><path class="w3" d="…"/>
       </svg>
     </div>

   Обёртка необязательна: класс .waves может стоять прямо на svg,
   и в разметке приложения он стоит именно так. Оба варианта живые.

   Движение сделано так, чтобы не зависеть от геометрии путей:
   сам svg растянут на 200% ширины контейнера и сдвигается на −50%
   своей ширины — контейнер при любом кадре анимации закрыт содержимым
   целиком, «пустого края» не бывает. Отдельные волны дополнительно
   покачиваются по вертикали с разными периодами — это даёт ощущение
   слоёв без второй горизонтальной анимации.
   ------------------------------------------------------------------ */

.waves {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 0;
  height: 4.5rem;
  overflow: hidden;
  pointer-events: none;
  border-bottom-left-radius: inherit;
  border-bottom-right-radius: inherit;
}

/* Волны — украшение, поэтому содержимое карточки поднимается над ними.
   Без этого абсолютно спозиционированные волны накрывали бы кнопку
   и заметно её пачкали. */
.card > *:not(.waves) {
  position: relative;
  z-index: 1;
}

.waves > svg,
.wave-meter > svg,
.wave-meter .waves > svg {
  display: block;
  width: 200%;
  /* общий сброс ограничивает svg шириной родителя; здесь это ограничение
     надо снять, иначе двойная ширина схлопнется до одинарной и при сдвиге
     на −50% справа откроется пустота */
  max-width: none;
  height: 100%;
  animation: wave-drift 18s linear infinite;
}

/* Класс может висеть и прямо на svg — так он и стоит в разметке.
   Тогда один элемент работает сразу за обёртку и за содержимое:
   положение и высоту ему уже задал блок .waves выше, и трогать их
   нельзя (иначе height: 100% растянул бы волны на всю карточку).
   Здесь добавляется только то, без чего не идёт дрейф. */
svg.waves {
  width: 200%;
  max-width: none;
  animation: wave-drift 18s linear infinite;
}

.waves path,
.wave-meter path {
  fill: var(--white);
  transform-box: fill-box;      /* иначе проценты и origin считаются от всего холста */
  transform-origin: 50% 100%;
}

/* Прозрачности по спецификации. Селекторы по порядку следования нужны,
   чтобы волны выглядели правильно даже без классов; варианты с классом
   написаны через `path.wN` — так у них та же специфичность, и при
   одновременном наличии выигрывает класс как более поздний. */
.waves path:nth-of-type(1) { opacity: 0.07; }
.waves path:nth-of-type(2) { opacity: 0.11; }
.waves path:nth-of-type(3) { opacity: 0.16; }

.waves path.w1 { opacity: 0.07; }
.waves path.w2 { opacity: 0.11; }
.waves path.w3 { opacity: 0.16; }

.waves path:nth-of-type(1) { animation: wave-bob 5.5s ease-in-out infinite alternate; }
.waves path:nth-of-type(2) { animation: wave-bob 7.2s ease-in-out -1.4s infinite alternate; }
.waves path:nth-of-type(3) { animation: wave-bob 9.1s ease-in-out -2.8s infinite alternate; }

@keyframes wave-drift {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

@keyframes wave-bob {
  from { transform: translateY(0.125rem); }
  to   { transform: translateY(-0.125rem); }
}

/* Индикатор громкости микрофона.
   js пишет уровень так:  el.style.setProperty('--level', 0.42)
   Значение 0…1; при отсутствии js остаётся спокойная волна минимальной
   амплитуды — экран проверки не выглядит сломанным.

   Здесь волны лаймовые и заметно плотнее, чем декоративные .07/.11/.16:
   это не украшение, а обратная связь «вас слышно», и она обязана
   читаться человеку со слабым зрением. */
.wave-meter {
  --level: 0;
  position: relative;
  height: 3.75rem;
  border-radius: var(--r-card-s);
  overflow: hidden;
  background: var(--glass);
  box-shadow: inset 0 0 0 1px var(--hair);
}

.wave-meter > svg,
.wave-meter .waves > svg {
  position: absolute;
  left: 0;
  bottom: 0;
}

/* Селекторы намеренно записаны через :nth-of-type: у них та же
   специфичность, что у покачивания в блоке .waves выше, но они идут
   позже — значит, внутри измерителя вертикальное «дыхание» отключается
   и transform целиком отдан уровню громкости. Это работает и когда
   svg лежит в .wave-meter напрямую, и когда он завёрнут в .waves. */
.wave-meter path,
.wave-meter path:nth-of-type(1),
.wave-meter path:nth-of-type(2),
.wave-meter path:nth-of-type(3),
.wave-meter path.w1,
.wave-meter path.w2,
.wave-meter path.w3 {
  fill: var(--lime);
  animation: none;
  /* clamp защищает от значений вне диапазона, .16 — «тихий» минимум,
     чтобы полоса не исчезала совсем */
  transform: scaleY(calc(0.16 + 0.84 * clamp(0, var(--level), 1)));
  transition: transform 0.09s linear;
}

.wave-meter path:nth-of-type(1) { opacity: 0.28; }
.wave-meter path:nth-of-type(2) { opacity: 0.5; }
.wave-meter path:nth-of-type(3) { opacity: 0.85; }

.wave-meter path.w1 { opacity: 0.28; }
.wave-meter path.w2 { opacity: 0.5; }
.wave-meter path.w3 { opacity: 0.85; }

/* ------------------------------------------------------------------
   16. Фокус с клавиатуры
   Кольцо лаймовое — его видно на любом фоне приложения, включая белые
   карточки и видео. Толщина в px по той же причине, что и у --hair:
   линия обязана остаться линией и при «крупном тексте». Два пикселя,
   а не три: на кнопке в 48px прежнее кольцо занимало заметную часть
   её собственной высоты и читалось как вторая заливка.
   ------------------------------------------------------------------ */

/* Радиус кольца браузер берёт у самого элемента — задавать его здесь нельзя:
   `border-radius: inherit` подменил бы форму кнопки формой родителя. */
:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 0.1875rem;
}

.card.light :focus-visible,
.btn.white:focus-visible {
  outline-color: var(--ink);    /* на белом лайм почти сливается */
}

/* Мышью и пальцем кольцо не показываем — иначе после каждого касания
   на экране остаётся «залипшая» рамка, и человек думает, что сломал. */
:focus:not(:focus-visible) {
  outline: 0;
}

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

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  .waves > svg,
  svg.waves,
  .wave-meter > svg,
  .wave-meter .waves > svg {
    animation: none !important;
    transform: none !important;
  }

  .waves path {
    animation: none !important;
    transform: none !important;
  }

  /* Индикатор громкости оставляем рабочим: это не декорация, а показание
     прибора. Убираем только сглаживание, чтобы не было плавного «дыхания»,
     а саму реакцию на голос возвращаем — правило выше её погасило. */
  .wave-meter path {
    transform: scaleY(calc(0.16 + 0.84 * clamp(0, var(--level), 1))) !important;
    transition: none !important;
  }
}

/* ------------------------------------------------------------------
   18. Мелкая правка под узкие экраны
   ------------------------------------------------------------------ */

@media (max-width: 22.5rem) {
  :root {
    --pad: 0.75rem;

    /* Своё окно уменьшается — вместе с ним уменьшается и место,
       которое оставляет ему карточка ожидания: обе величины
       берутся отсюда. */
    --self-w: 4.5rem;
    --self-h: 6rem;
  }

  /* На самых узких телефонах ячейки номера становятся невозможно тесными,
     поэтому переходим на две строки по три ячейки. */
  .code-slots {
    grid-template-columns: repeat(3, 1fr);
  }

  /*
   * Код из сообщения — четыре цифры, и три колонки ломают его на 3+1:
   * одинокая ячейка под тремя читается как сбой вёрстки. Номер встречи
   * шестизначный, ему три колонки как раз (две ровные строки), поэтому
   * правило выше остаётся, а здесь оно отменяется для одного экрана.
   */
  #s-otp .code-slots {
    grid-template-columns: repeat(4, 1fr);
  }

  /* Поля карточки ужимаются с каждой стороны. Это то место, которого
     не хватало номеру встречи: «827 418» набран --fs-code и переносу
     не подлежит (его диктуют по телефону одной строкой), а на 280px
     он не влезал в карточку и молча обрезался её overflow. Место
     взято у пустоты, а не у цифр. */
  .card {
    padding-left: 0.75rem;
    padding-right: 0.75rem;
  }
}

/* ------------------------------------------------------------------
   18.1. Внешний экран складных телефонов (280px)

   На новой шкале Д1 круги панели звонка стали 52px, а подписи —
   13px, и запас здесь появился. Правила ниже всё равно остаются:
   они срабатывают при «крупном тексте», когда 19% роста возвращают
   ширину подписей туда же, откуда её убрали. Отбирается только то,
   что не является целью для пальца: поля экрана, поля панелей,
   промежутки между кнопками. Там, где и этого мало, слово
   переносится внутри себя: разорванная подпись читается,
   уехавшая за край — нет.
   ------------------------------------------------------------------ */

@media (max-width: 18.75rem) {
  :root {
    --pad: 0.5rem;
  }

  /* Панель звонка: поля с боков вдвое уже — весь выигрыш достаётся
     трём подписям («Микрофон», «Звук включён», «Громкость»). */
  .controls {
    padding-left: calc(var(--safe-l) + 0.375rem);
    padding-right: calc(var(--safe-r) + 0.375rem);
  }

  /* Вкладкам при «крупном тексте» не хватает даже так. min-width
     снимает запрет сетки сжимать колонку ниже слова, а anywhere
     разрешает самому длинному слову («Настройки») перенестись внутри
     вкладки. Обе строки помещаются в её 48px — высота не теряется.

     Именно anywhere, а не break-word: break-word разрешает разрыв,
     но не уменьшает минимальную ширину блока, поэтому подпись так и
     оставалась одной строкой и вылезала из вкладки. anywhere
     уменьшает и её — слово переносится тогда, и только тогда,
     когда иначе не помещается. */
  .tabbar {
    --tab-gap: 0.1875rem;
    padding: 0.25rem;
  }

  .tabbar button,
  .tabbar .tab,
  .tabbar .tab .cap {
    min-width: 0;
    overflow-wrap: anywhere;
  }

}

/* ------------------------------------------------------------------
   18.2. Узкий экран плюс включённый «крупный текст»

   Крупный текст честно растит на 19% всё, что задано в rem, — а экран
   шире от этого не становится. Два места этого не переживают, и оба
   правила ниже заперты сразу двумя условиями: узкий экран И включённый
   крупный текст. В обычном режиме ни одно из них не действует.
   ------------------------------------------------------------------ */

/* Номер встречи переносить нельзя: его диктуют по телефону одной
   строкой. При крупном тексте шесть цифр набраны 36px и на экране до
   320px рискуют не поместиться в карточку — край карточки их обрежет,
   и человек не прочитает то единственное, ради чего экран существует.
   Здесь номер остаётся при своих 30px: он не растёт вместе с
   остальным текстом, но и не пропадает. */
@media (max-width: 20rem) {
  html.big .code {
    font-size: clamp(1.5rem, 8.5vw, 2.625rem);
  }
}

/* Три подписи вкладок при крупном тексте перестают помещаться в
   панель на узких экранах, и панель вылезает за край целиком.
   Механика та же, что в разделе 18.1: сетке разрешается сжать
   колонку ниже слова, слову — перенестись внутри себя. */
@media (max-width: 21.5rem) {
  html.big .tabbar {
    --tab-gap: 0.1875rem;
    padding: 0.25rem;
  }

  html.big .tabbar button,
  html.big .tabbar .tab,
  html.big .tabbar .tab .cap {
    min-width: 0;
    overflow-wrap: anywhere;
  }
}

/* На альбомной ориентации телефона экран разговора обязан отдать
   максимум высоты сцене. Место отбирается у полей панели и у своего
   окна — но не у кнопок: шкала Д1 одна на все ориентации, палец в
   альбомном режиме не становится тоньше. */
@media (orientation: landscape) and (max-height: 30rem) {
  :root {
    /* Окно ложится горизонтально и становится ниже — ровно на эту
       величину опускается и низ карточки ожидания. */
    --self-w: 4.75rem;
    --self-h: 3.5rem;
  }

  .controls {
    padding-top: 0.5rem;
    padding-bottom: calc(var(--safe-b) + 0.5rem);
  }
}

/* ------------------------------------------------------------------
   16. Главный экран по референсу: строка бренда с аватаром, две
       карточки, строки списка, круглые кнопки, галочки
   ------------------------------------------------------------------ */

.top-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

/* Круглый аватар справа в строке бренда: фотография или инициалы. */
.avatar-btn {
  width: 2.75rem;
  height: 2.75rem;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 0;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: linear-gradient(150deg, #7fa056, #3f5a2c);
  color: var(--white);
  font-size: var(--fs-body);
  font-weight: 600;
  cursor: pointer;
}
.avatar-btn img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.home-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem;
}
.home-cards .card {
  min-height: 11.5rem;          /* 184px — заголовок, подпись и воздух; без списка галочек */
  padding: 1rem;
  gap: 0;
}

/* Обе карточки — кнопки целиком: попасть можно в любую точку, а не
   только в кружок. Сброс кнопочного вида — как у .home-join ниже. */
.home-cards button.card {
  border: 0;
  cursor: pointer;
  text-align: left;
  align-items: stretch;
  justify-content: flex-start;
  font: inherit;
  transition: transform 0.12s var(--ease);
}
.home-cards button.card:active {
  transform: scale(0.98);
}

/* Левая карточка — то, что нажимают первым, и это должно быть видно
   без слов: лаймовый «+» и тонкая лаймовая кайма с мягким свечением
   под карточкой. Ровно столько, чтобы рука потянулась к ней, а не к
   соседней; заливать лаймом целиком нельзя — это тон главной кнопки,
   и второй такой же на экране спорил бы с первой. */
.home-new {
  background: var(--gray);
  box-shadow:
    inset 0 0 0 1px rgba(205, 245, 111, 0.32),
    0 0.875rem 2rem -0.875rem rgba(205, 245, 111, 0.28);
}
.home-new .circ {
  background: var(--lime);
  color: var(--ink);
}

/* Левая карточка: заголовок и кружок «+» в шапке, подпись под ними. */
.home-new-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.625rem;
}
.home-new-head b {
  font-size: 1.25rem;
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.02em;
  padding-top: 0.25rem;
}
.home-new .cap {
  margin-top: 0.5rem;
  color: var(--text-2);
}

/* Круглая кнопка: стеклянная на тёмной карточке, лаймовая или чёрная в
   строках списка — три вида из референса, четвёртого нет. */
.circ {
  width: 2.75rem;
  height: 2.75rem;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: var(--glass-2);   /* без backdrop-filter: кружков на экране до шести, и размытие под каждым дорого */
  color: var(--text);
  cursor: pointer;
  transition: transform 0.12s var(--ease), background 0.16s;
}
.circ .ic > svg {
  width: 1.25rem;
  height: 1.25rem;
}
.circ:active {
  transform: scale(0.92);
}
.circ.lime {
  background: var(--lime);
  color: var(--ink);
}
.circ.ink {
  background: var(--ink);
  color: var(--white);
}

/* Правая карточка: зелёная, с волнами у низа. */
.home-join {
  justify-content: flex-start;
  align-items: flex-start;
  text-align: left;
  border: 0;
  cursor: pointer;
  transition: transform 0.12s var(--ease), filter 0.16s;
}
.home-join:active {
  transform: scale(0.98);
}
.home-join b {
  display: block;
  font-size: 1.5rem;
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.02em;
  color: var(--white);
}
.home-join .cap {
  margin-top: 0.5rem;
  color: rgba(255, 255, 255, 0.72);
}
/* Волны на карточке «Войти по номеру» (docs/FIXES16.md, п. 4).

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

   - у каждого слоя своя скорость, и ближний идёт быстрее дальнего — гребни
     расходятся и сходятся, рисунок ни в один момент не повторяет прежний;
   - каждый слой ещё и дышит по высоте со своим, третьим периодом — гребень
     растёт и опадает на ходу, как у воды, а не у вырезанного силуэта;
   - слой сдвигается ровно на один свой период (холст вдвое шире карточки,
     периоды — 1/2, 1/4 и 1/3 холста), поэтому круг замыкается без шва.

   Обрезает волны сама карточка (.card.accent, overflow). */
.home-join .waves {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 42%;
  pointer-events: none;
}
/* Каждый слой — отдельный svg с одним путём, и меняются у него только
   transform и scale самого svg: обе анимации рисует композитор, главный
   поток не перерисовывает карточку ни на одном кадре. Покачивание путей
   внутри svg (wave-bob) перерисовывало бы svg 60 раз в секунду — и шло
   вразрез с прокруткой.

   Ход и дыхание разведены по разным свойствам нарочно: у хода ровный шаг
   (linear) и свой период, у дыхания — плавный и свой; в одном наборе
   кадров их пришлось бы привести к общему периоду, и волна снова стала бы
   заученной. Где отдельного свойства scale нет (движок старше 2022 года),
   остаётся один ход — рисунок тот же, только без дыхания. */
.home-join .waves > svg {
  position: absolute;
  left: 0;
  top: 0;
  width: 200%;
  height: 100%;
  max-width: none;
  transform-origin: 50% 100%;   /* дышит от нижней кромки: низ волны стоит на месте */
  will-change: transform;
}
.home-join .waves > svg:nth-of-type(1) {
  animation: wave-run-a 11s linear infinite, wave-swell 3.7s ease-in-out infinite alternate;
}
.home-join .waves > svg:nth-of-type(2) {
  animation: wave-run-b 4.4s linear infinite, wave-swell 2.3s ease-in-out -0.8s infinite alternate;
}
.home-join .waves > svg:nth-of-type(3) {
  animation: wave-run-c 4s linear infinite, wave-swell 2.9s ease-in-out -1.7s infinite alternate;
}
.home-join .waves path {
  fill: #fff;
  animation: none;              /* покачивание путей отключено — см. выше */
}
.home-join .waves > svg:nth-of-type(1) path { opacity: 0.07; }
.home-join .waves > svg:nth-of-type(2) path { opacity: 0.11; }
.home-join .waves > svg:nth-of-type(3) path { opacity: 0.16; }

/* Сдвиг — ровно один период слоя в долях ширины svg: 320, 160 и 640/3
   единиц из 640. Меняя пути в index.html, меняйте и эти числа — иначе
   вернётся прыжок в конце круга. */
@keyframes wave-run-a {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
@keyframes wave-run-b {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-25%, 0, 0); }
}
@keyframes wave-run-c {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-33.3333%, 0, 0); }
}
@keyframes wave-swell {
  from { scale: 1 0.74; }
  to   { scale: 1 1.1; }
}

/* Строки под чипами. Заголовка над ними нет — его роль играет нажатый чип.
   (Прежнее правило .list-title отсюда убрано: тот же класс носит заголовок
   строки в списках «Мои встречи»/«Мои договоры», и flex с отрицательным
   полем ему только мешал.) */
.rows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}

/* Строка референса: заголовок, теги-пилюли, круглая кнопка. На главном все
   строки одного цвета: белая — это статус («отправлено» в телефонной
   книге), а не украшение первой строки. */
.row-card {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  width: 100%;
  min-height: 5.5rem;           /* 88px */
  padding: 1rem 1rem 1rem 1.25rem;
  border: 0;
  border-radius: var(--r-card-s);
  background: var(--gray);
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: transform 0.12s var(--ease);
}
.row-card:active {
  transform: scale(0.985);
}
.row-card.plain {
  cursor: default;
}
.row-card .t {
  flex: 1;
  min-width: 0;
}
.row-card .t b {
  display: block;
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.row-card .t .note {
  display: block;
  margin-top: 0.375rem;
  font-size: var(--fs-cap);
  line-height: 1.4;
  color: var(--text-3);
  white-space: normal;
}
.row-card .tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin-top: 0.5625rem;
}
.row-card.white {
  background: var(--white);
  color: var(--ink);
}
.row-card.white .t .note {
  color: rgba(0, 0, 0, 0.55);
}

/* Тег под заголовком: маленькая пилюля. На белой строке — чёрный
   заливкой и контурный, как в референсе. */
.tag {
  display: inline-flex;
  align-items: center;
  min-height: 1.75rem;
  padding: 0 0.75rem;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.1);
  font-size: var(--fs-cap);
  font-weight: 500;
  color: rgba(255, 255, 255, 0.86);
  white-space: nowrap;
}
.tag.lime {
  background: var(--lime);
  color: var(--ink);
}
.row-card.white .tag {
  background: transparent;
  box-shadow: inset 0 0 0 1.4px rgba(0, 0, 0, 0.18);
  color: var(--ink);
}
.row-card.white .tag.ink {
  background: var(--ink);
  color: var(--white);
  box-shadow: none;
}
.row-card.white .tag.lime {
  background: var(--lime);
  color: var(--ink);
  box-shadow: none;
}

.rows-empty {
  padding: 1.5rem 1.125rem;
  border-radius: var(--r-card-s);
  background: rgba(255, 255, 255, 0.06);
  font-size: var(--fs-body);
  line-height: 1.45;
  color: var(--text-3);
}

/* Главный экран — с нижней панелью: между списком и панелью нужен зазор. */
#s-home .rows {
  margin-bottom: 0.75rem;
}

/* ---------- приглашение из телефонной книги ---------- */

.inv-manual {
  display: flex;
  gap: 0.5rem;
}
.inv-manual .field {
  flex: 1;
  min-width: 0;
}
.inv-manual .btn {
  flex: 0 0 auto;
  padding: 0 1.375rem;
}
/* ---------- серые поверхности (#454544) ----------

   Панели настроек и профиля, карточка загрузки модели, тёмные карточки
   помощи и условий — то, что не документ и не действие, а поверхность с
   полями и переключателями. Тёмно-зелёный --card на них сливался с
   фоном; серый читается как отдельная плита, при этом не спорит ни с
   лаймом, ни с белым. Поля на серой плите — темнее её, а не светлее:
   углубление, в которое пишут. */
#s-settings .card,
#s-profile .card,
#s-model .card,
#s-terms .card,
#s-help .card:not(.accent):not(.light) {
  background: var(--gray);
}
#s-settings .card .field,
#s-settings .card select,
#s-profile .card .field {
  background: rgba(0, 0, 0, 0.24);
}
#s-settings .card .switch::before {
  background: rgba(0, 0, 0, 0.28);
}

/* Карточка приглашённого: имя, номер, что с ним, и одна кнопка во всю
   ширину. Не строка с кружком: кружок со значком «поделиться» или «чат»
   ничего не говорил о том, что произойдёт по нажатию, и человек не
   понимал, куда жать. Слово на кнопке — говорит. */
.inv-list {
  gap: 0.625rem;
}
.inv-card {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  padding: 1rem 1.125rem 1.125rem;
  border-radius: var(--r-card-s);
  background: var(--gray);
}
.inv-card .inv-name {
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1.25;
}
.inv-card .inv-phone {
  font-size: var(--fs-cap);
  color: var(--text-3);
}
.inv-card .inv-status {
  margin-top: 0.125rem;
  font-size: var(--fs-body);
  line-height: 1.35;
  color: var(--text-2);
}
.inv-card .inv-status.ok {
  color: var(--lime-soft);
}
.inv-card .inv-status.warn {
  color: #ffce8f;
}
.inv-card .btn {
  margin-top: 0.5rem;
}
/* Сделано: кнопка гаснет, но остаётся на месте — раскладка не прыгает. */
.inv-card .btn.done {
  background: rgba(205, 245, 111, 0.16);
  color: var(--lime-soft);
  pointer-events: none;
}

/* Текст SMS — как он уйдёт: моноширинные цифры, чтобы номер встречи
   читался так же, как на белой карточке. */
.inv-sms .inv-sms-text {
  padding: 0.75rem 0.875rem;
  border-radius: var(--r-card-s);
  background: rgba(255, 255, 255, 0.07);
  font-size: var(--fs-body);
  line-height: 1.45;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* Входящее приглашение: белая строка сверху экрана, поверх всего. */
.invite-pop {
  position: fixed;
  z-index: 95;
  left: 50%;
  top: calc(var(--safe-t) + 0.875rem);
  transform: translateX(-50%);
  width: min(35rem, calc(100% - 1.5rem));
  display: flex;
  align-items: center;
  gap: 0.875rem;
  padding: 0.875rem 0.875rem 0.875rem 1.25rem;
  border-radius: var(--r-card-s);
  background: var(--white);
  color: var(--ink);
  box-shadow: 0 1.5rem 3.75rem -1.125rem rgba(0, 0, 0, 0.8);
}
.invite-pop .t {
  flex: 1;
  min-width: 0;
}
.invite-pop .t b {
  display: block;
  font-size: var(--fs-lead);
  font-weight: 600;
}
.invite-pop .t span {
  display: block;
  margin-top: 0.125rem;
  font-size: var(--fs-cap);
  color: rgba(0, 0, 0, 0.55);
}
.invite-pop .btn {
  min-height: 2.875rem;
  padding: 0 1.25rem;
  font-size: var(--fs-body);
  background: rgba(0, 0, 0, 0.07);
  color: var(--ink);
}
.invite-pop .btn.dark {
  background: var(--ink);
  color: var(--white);
}
