/*
 * Alter — телефон.
 *
 * Панель чата повторяет прототип «LifeCore 2» буквально: закрытая — плавающая
 * пилюля 128px со скруглением по всем углам, открытая — во весь экран с прямыми
 * углами. Анимируется ИМЕННО height (плюс left/right/bottom/border-radius):
 * попытка сделать это transform-ом уводит низ панели за экран, из-за чего в
 * закрытом состоянии не видно нижних углов, а открытое не доезжает до края.
 *
 * Ширина НЕ анимируется: панель растянута left/right. Иначе `100vw` в CSS и
 * `innerWidth` в JS расходятся на округлении, и края дрожат на каждом кадре.
 *
 * Ручка лежит поверх сообщений и занимает всю закрытую площадь, поэтому тянуть
 * можно откуда угодно, а из-под неё выглядывает конец последней реплики.
 */

:root {
  --bg: #000;
  --coral: #fe6344;
  --ink: #ffffff;
  --muted: rgba(255, 255, 255, .55);

  /*
   * Зелёный и красный. Отсюда их берут и плашки состояний, и движок кольца
   * (orb.js читает эти переменные), поэтому цвет «получилось» и «ошибка»
   * задан в одном месте, а не в двух похожих.
   */
  --ok: #6ee787;
  --danger: #ff8b83;

  --tile-bg: rgba(255, 255, 255, .06);
  --tile-border: rgba(255, 255, 255, .10);

  --sheet-closed: 128px;
  --sheet-gap: 4px;
  --radius: 44px;

  /*
   * Безопасные зоны. Работают как задумано ровно потому, что страница занимает
   * весь экран (см. `height: 100vh` ниже) — иначе система вычитает статус-бар
   * сама, а env() продолжает докладывать полную высоту острова, и отступ
   * применяется дважды.
   */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);

  /*
   * Высота приложения и подъём над клавиатурой. Те же числа берёт драг
   * (через пробник), поэтому CSS и JS не могут разойтись.
   *
   * Именно vh, а не dvh и не innerHeight: последние два меряют ОКНО, а оно на
   * iOS бывает ниже экрана — по ним панель не доезжала до края.
   *
   *   --app-h  ЗАМОРОЖЕННАЯ высота приложения: целый экран при закрытой
   *          клавиатуре. На неё посажены все слои, поэтому ни один из них не
   *          реагирует на то, что iOS ужал окно под клавиатуру (932 → 517).
   *          До первого замера работает 100vh; дальше JS кладёт сюда пиксели.
   *   --kb   сколько низа закрыто клавиатурой. Ноль, пока её нет. Ровно на
   *          столько поднимаются список сообщений и поле ввода — и больше
   *          в раскладке не двигается ничего.
   *   --doc-h  высота ДОКУМЕНТА. Равна экрану, пока клавиатуры нет, и видимой
   *          области, пока она есть. Смысл — чтобы документ никогда не был
   *          выше видимой области: иначе iOS есть куда его прокручивать, и он
   *          прокручивает, увозя страницу вслед за выезжающей клавиатурой.
   */
  --app-h: 100vh;
  --kb: 0px;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* `hidden` обязан побеждать любой display ниже: иначе `.chat { display:… }`
   перебивает `[hidden] { display: none }` из UA-стилей и панель остаётся видна. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  /*
   * Именно 100vh, а НЕ 100%. Ключевая строка всей полноэкранности.
   *
   * iOS 26 в портрете отдаёт установленному приложению окно ниже статус-бара
   * (932 → 873), но vh по-прежнему меряет целый экран. `height: 100%` равнялся
   * урезанному окну, и приложение аккуратно рисовало внутри него — отсюда
   * полоса сверху, которую не брал никакой мета-тег. С 100vh страница занимает
   * весь экран и заходит под остров, а env(safe-area-inset-top) снова означает
   * ровно то, что написано, и его снова можно применять как обычно.
   */
  /*
   * Высота — только замороженный экран (--app-h, то есть 100vh).
   *
   * Вебвью растягивается ПО ДОКУМЕНТУ: сделаешь документ ростом с окно (873) —
   * и вебвью станет 873, а внизу экрана появится чёрная полоса. Проверено
   * дважды, в том числе через `height: 100%` и через `position: fixed`.
   * Не менять без переустановки и проверки на телефоне.
   *
   * Прокрутка при этом запрещена не отсюда: на время клавиатуры документ
   * ужимается до видимой области через --doc-h, и увозить его становится
   * некуда.
   */
  height: var(--doc-h, var(--app-h));
  overflow: hidden;
  overscroll-behavior: none;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: Inter, -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  font-size: 17px;
  -webkit-font-smoothing: antialiased;
  /*
   * Никаких жестов прокрутки на самой странице.
   *
   * Когда клавиатура делает видимую область меньше раскладочной, iOS даёт
   * возить страницу пальцем внутри неё — и приложение перестаёт быть
   * приложением. Запрещаем здесь и разрешаем точечно там, где прокрутка нужна
   * по делу: список сообщений, главный экран, тело настроек.
   */
  touch-action: none;

  padding-top: env(safe-area-inset-top);

  padding-left: env(safe-area-inset-left);

  padding-right: env(safe-area-inset-right);

  padding-bottom: env(safe-area-inset-bottom);
}

button { font: inherit; color: inherit; cursor: pointer; }

/* Мерка для JS: ни env(), ни 100vh из скрипта не прочитать, а числа нужны —
   драгу панели и экрану «Геометрия экрана». Высота даёт 100vh в пикселях,
   отступы — безопасные зоны. Ширины нет, поэтому на раскладку не влияет. */
.probe {
  position: fixed;
  top: 0; left: 0;
  width: 0;
  height: 100vh;
  padding: env(safe-area-inset-top, 0px) 0 env(safe-area-inset-bottom, 0px);
  visibility: hidden;
  pointer-events: none;
}

/* ═══════════ Главный экран ═══════════ */

/* Слои сидят на замороженной высоте, а не на `inset: 0`: последний следует за
   ужатым под клавиатуру окном и утаскивает раскладку за собой. */
.surface {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--app-h);
  /* Снизу — место под закрытую пилюлю целиком, включая её отступ от края. */
  padding: calc(var(--safe-t) + 22px) 20px
           calc(var(--sheet-closed) + var(--sheet-gap) + 28px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* Прокрутка тут по делу: плитки не обязаны влезать в экран. */
  touch-action: pan-y;
}

.head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 26px;
}

.head__date { margin: 0 0 4px; color: var(--muted); font-size: 15px; font-weight: 500; }
.head__title { margin: 0; font-size: 40px; font-weight: 700; letter-spacing: -1.2px; }

.head__actions { display: flex; gap: 10px; flex: 0 0 auto; padding-top: 4px; }

/* Белые круглые кнопки: настройки и редактирование сетки. */
.round {
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  border: 0;
  border-radius: 50%;
  background: #fff;
  color: #1a1a1a;
  transition: transform .12s ease, background .14s ease;
}
.round svg { width: 22px; height: 22px; }
.round:active { transform: scale(.93); }
.round[aria-pressed="true"] { background: var(--coral); color: #fff; }
.round--dim { background: var(--tile-bg); color: var(--ink); }

.widgets { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

.tile {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-height: 132px;
  padding: 18px;
  border: 1px solid var(--tile-border);
  border-radius: 26px;
  background: var(--tile-bg);
  -webkit-backdrop-filter: blur(18px);
  backdrop-filter: blur(18px);
  text-align: left;
  transition: transform .14s ease, background .14s ease;
}
.tile:active { transform: scale(.97); background: rgba(255, 255, 255, .10); }

.tile__label { color: var(--muted); font-size: 14px; font-weight: 500; }
.tile__value { margin-top: auto; font-size: 26px; font-weight: 700; letter-spacing: -.6px; }
.tile__note { color: var(--muted); font-size: 12.5px; }

/* Режим редактирования — пока только affordance, сохранять нечего. */
.widgets.is-editing .tile {
  border-style: dashed;
  border-color: rgba(255, 255, 255, .35);
  animation: nudge 1.6s ease-in-out infinite;
}
@keyframes nudge {
  0%, 100% { transform: rotate(-.5deg); }
  50% { transform: rotate(.5deg); }
}

.hint {
  margin: 22px 2px 0;
  color: rgba(255, 255, 255, .38);
  font-size: 13.5px;
  line-height: 1.45;
}

/* ═══════════ Панель чата ═══════════ */

.chat {
  position: fixed;
  left: var(--sheet-gap);
  right: var(--sheet-gap);
  /*
   * Панель привязана СВЕРХУ, хотя стоит внизу.
   *
   * Привязка к низу выглядела бы естественнее, но низ — это низ окна, а окно
   * iOS ужимает под клавиатуру. Приклеенная низом панель дёргалась вместе с
   * ним, и переключателю приходилось возвращать её на место уже после того,
   * как рывок нарисован. Сверху же отсчёт идёт от замороженной высоты, которая
   * не меняется никогда.
   *
   * `--safe-b` в отступ не входит сознательно: пилюля должна стоять прижатой к
   * нижнему краю, домашний индикатор ложится на неё сверху. Это замысел, а не
   * недосмотр — не «исправлять» отступом под индикатор.
   */
  top: calc(var(--app-h) - var(--sheet-closed) - var(--sheet-gap));
  bottom: auto;
  height: var(--sheet-closed);
  background: var(--coral);
  border-radius: var(--radius);
  overflow: hidden;
  user-select: none;
  -webkit-user-select: none;
  will-change: height, top, left, right, border-radius;
  z-index: 20;
  transition:
    height .32s cubic-bezier(.22, 1, .36, 1),
    top .32s cubic-bezier(.22, 1, .36, 1),
    left .32s cubic-bezier(.22, 1, .36, 1),
    right .32s cubic-bezier(.22, 1, .36, 1),
    border-radius .32s cubic-bezier(.22, 1, .36, 1);
}

/*
 * Открытое состояние — ровно видимая область окна, край в край.
 *
 * Верхний край уходит ПОД динамический остров: фон панели там есть, а вот
 * ручка отступает на его высоту (см. `.chat.is-open .chat__handle`), иначе
 * панель нечем было бы закрыть — остров перехватывает касания.
 *
 * Задано классом, а не только инлайновой математикой драга: так «во весь
 * экран, углы прямые» гарантировано и не зависит от округлений.
 */
.chat.is-open {
  height: var(--app-h);
  top: 0;
  left: 0;
  right: 0;
  border-radius: 0;
}

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

/*
 * Список и поле уезжают вверх на высоту клавиатуры — одним движением и примерно
 * за то же время, что она выезжает.
 *
 * Именно transform, а не отступы: он не вызывает перекладку текста и работает
 * на композиторе, поэтому едет плавно. Через padding пришлось бы ещё и тянуть
 * позицию прокрутки следом, а это рывки.
 *
 * У поля к сдвигу добавлен `--safe-b`: в обычном положении оно отступает от
 * низа панели на домашний индикатор, а сейчас индикатор лежит на клавиатуре,
 * и без поправки поле повисло бы над ней на его высоту.
 */
.chat.is-keyboard .chat__messages { transform: translateY(calc(-1 * var(--kb))); }
.chat.is-keyboard.is-field-up .chat__field {
  transform: translateY(calc(var(--safe-b) - var(--kb)));
}

.chat__messages {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  /* Просвет соразмерен кеглю: при уменьшении реплик прежние 34px читались
     как разрыв между разговорами, а не между репликами. */
  gap: 22px;
  /* Сверху столько, чтобы реплики не лезли под ручку; снизу — место под поле. */
  padding: calc(var(--safe-t) + 96px) 26px 118px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  /* Скроллим только по вертикали; жесты панели живут на ручке. */
  touch-action: pan-y;
  /* Подъём над клавиатурой — той же кривой, что у поля ввода. */
  transition: transform .3s cubic-bezier(.22, 1, .36, 1);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 52px,
                      #000 calc(100% - 56px), transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 52px,
               #000 calc(100% - 56px), transparent 100%);
}

/*
 * Закрытая пилюля: из-под ручки выглядывает ХВОСТ последней реплики.
 *
 * Отступы открытого состояния (safe-t + 96px сверху, 118px снизу) в 128px не
 * влезают вообще — из-за этого реплика обрезалась посреди строки. Здесь свои:
 * последняя строка видна целиком, предыдущая гасится маской.
 */
.chat:not(.is-open) .chat__messages {
  padding: 46px 26px 20px;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 30px);
  mask-image: linear-gradient(to bottom, transparent 0, #000 30px);
}

/*
 * Реплики.
 *
 * Было clamp(27px…38px) — плакатный кегль из референса. На коротком «привет»
 * он смотрелся, но настоящий ответ агента с перечислением занимал два экрана
 * и читался хуже, а не лучше. Уменьшено до размера, на котором ответ виден
 * целиком; интонацию держит вес и межстрочный, а не кегль.
 */
.msg {
  flex: 0 0 auto;
  font-size: clamp(19px, 5vw, 23px);
  font-weight: 600;
  line-height: 1.32;
  letter-spacing: -.3px;
  color: var(--ink);
  overflow-wrap: anywhere;
  -webkit-user-select: text;
  user-select: text;
  /* Ответы бывают со списками и переносами — сохраняем их. */
  white-space: pre-wrap;
}
.msg--me { text-align: right; color: rgba(255, 255, 255, .72); }
.msg--sys {
  font-size: 15.5px;
  font-weight: 500;
  color: rgba(255, 255, 255, .62);
  letter-spacing: 0;
}
/* Alter решил не отвечать (ADR-0034): тихая пометка, в отличие от служебных строк — не пропадает. */
.msg--quiet {
  font-size: 14px;
  font-weight: 500;
  color: rgba(255, 255, 255, .45);
  letter-spacing: 0;
}
.msg:last-child { margin-bottom: 8px; }

.typing { display: flex; align-items: center; gap: 12px; padding: 4px 0 8px; }
.typing__dots { display: flex; gap: 8px; }
/* Чем занят агент. Приезжает потоком событий, поэтому может быть пустым. */
.typing__what { color: rgba(255, 255, 255, .6); font-size: 15px; font-weight: 500; letter-spacing: 0; }
.typing i {
  width: 11px; height: 11px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .82);
  animation: blink 1.25s infinite;
}
.typing i:nth-child(2) { animation-delay: .16s; }
.typing i:nth-child(3) { animation-delay: .32s; }
@keyframes blink { 0%, 60%, 100% { opacity: .28; } 30% { opacity: 1; } }

.chat__handle {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: var(--sheet-closed);
  z-index: 10;
  cursor: ns-resize;
  /* Только здесь глушим жесты браузера — сообщения должны скроллиться. */
  touch-action: none;
}
.chat__handle::after {
  content: "";
  position: absolute;
  top: 15px;
  left: 50%;
  width: 68px;
  height: 5px;
  border-radius: 3px;
  background: rgba(255, 255, 255, .6);
  transform: translateX(-50%);
  transition: top .3s ease;
}
/* Открытая панель заходит под остров, а ручка — нет: и полоска, и площадь
   для перетаскивания начинаются ПОД ним, иначе закрыть панель нечем. */
.chat.is-open .chat__handle { height: calc(var(--safe-t) + 76px); }
.chat.is-open .chat__handle::after { top: calc(var(--safe-t) + 15px); }

.chat__field {
  position: absolute;
  left: 12px;
  right: 12px;
  /*
   * Поле просто лежит у нижнего края панели: подниматься над клавиатурой —
   * забота самой панели (`.chat.is-keyboard`), а не поля.
   */
  bottom: calc(var(--safe-b) + 18px);
  display: flex;
  align-items: center;
  gap: 8px;
  transform: translateY(130px);
  /* Одна кривая на два движения: выезд поля при раскрытии панели и подъём над
     клавиатурой. У списка сообщений она же — иначе поднимались бы вразнобой. */
  transition: transform .3s cubic-bezier(.22, 1, .36, 1);
}
.chat.is-field-up .chat__field { transform: translateY(0); }

/* Поле и микрофон в нём — одна таблетка. Микрофон позиционируется абсолютно,
   потому что внутри `input` вложенных элементов не бывает. */
.chat__inputwrap { position: relative; flex: 1 1 auto; min-width: 0; }

.chat__input {
  width: 100%;
  height: 52px;
  min-width: 0;
  /* Справа — место под микрофон. */
  padding: 0 50px 0 18px;
  border: 0;
  border-radius: 26px;
  background: rgba(255, 255, 255, .94);
  color: #1a1a1a;
  font: inherit;
  font-size: 17px;
}
.chat__input:focus { outline: none; }
.chat__input::placeholder { color: rgba(0, 0, 0, .36); }

.chat__mic {
  position: absolute;
  top: 50%;
  right: 5px;
  display: grid;
  place-items: center;
  width: 42px; height: 42px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: rgba(0, 0, 0, .5);
  transform: translateY(-50%);
  transition: transform .12s ease, color .14s ease, background .14s ease;
}
.chat__mic svg { width: 21px; height: 21px; }
.chat__mic:active {
  transform: translateY(-50%) scale(.9);
  background: rgba(0, 0, 0, .07);
  color: var(--coral);
}

.chat__send {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 52px; height: 52px;
  border: 0;
  border-radius: 26px;
  background: rgba(255, 255, 255, .94);
  color: #1a1a1a;
  transition: transform .12s ease, opacity .12s ease;
}
.chat__send svg { width: 22px; height: 22px; }
.chat__send:active { transform: scale(.92); }
.chat__send[disabled] { opacity: .45; }

/* ═══════════ Голосовой режим ═══════════ */

/*
 * Отдельный слой поверх панели чата, но под настройками.
 *
 * Фон чёрный, а не коралловый, хотя панель чата коралловая: кольцо красится
 * градацией от белого к коралловому, и на коралловом фоне тихие капсулы
 * исчезли бы ровно тогда, когда важнее всего видеть, что система жива.
 */
.voice {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--app-h);
  z-index: 30;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: calc(var(--safe-t) + 24px) 26px calc(var(--safe-b) + 28px);
  background: var(--bg);
  /* Жестов здесь нет вообще: экран не прокручивается и ничего не тянет. */
  touch-action: none;
  animation: voice-in .26s cubic-bezier(.22, 1, .36, 1);
}
@keyframes voice-in { from { opacity: 0; transform: scale(.96); } }

/*
 * Сцена: круг плюс место под содержимое.
 *
 * Круг не стоит в потоке, а лежит на сцене абсолютно, и его положение с
 * размером заданы переменными. Раскладка — это класс на сцене, то есть три
 * числа; добавить новую стоит одного правила, а не правки разметки и скриптов.
 *
 * Так сделано ради того, чего ещё нет: агент отвечает голосом и одновременно
 * показывает виджеты («что по планам на вечер» — и на экране календарь). Круг
 * при этом обязан уехать и уменьшиться, а не остаться поверх содержимого.
 *
 * Размеры — доли ШИРИНЫ сцены, поэтому одни и те же классы работают и на
 * полном экране, и в маленьком макете настроек.
 */
.orbstage {
  position: relative;
  flex: 1 1 auto;
  width: 100%;
  min-height: 0;
}

.orbstage__orb {
  position: absolute;
  left: var(--orb-x);
  top: var(--orb-y);
  width: var(--orb-size);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  /* Та же кривая, что у панели чата: движения в приложении должны быть
     сделаны одной рукой. */
  transition:
    left .42s cubic-bezier(.22, 1, .36, 1),
    top .42s cubic-bezier(.22, 1, .36, 1),
    width .42s cubic-bezier(.22, 1, .36, 1);
}

/* Круг один на всю сцену: говорит и слушает, показывать нечего. */
.orbstage--center { --orb-size: 74%; --orb-x: 50%; --orb-y: 46%; --content-top: 100%; }
/* Ушёл наверх, под ним место для виджетов. */
.orbstage--top { --orb-size: 34%; --orb-x: 50%; --orb-y: 19%; --content-top: 40%; }
/* Отошёл в угол: содержимое важнее, круг остаётся признаком жизни. */
.orbstage--corner { --orb-size: 20%; --orb-x: calc(100% - 11%); --orb-y: 12%; --content-top: 26%; }

.orbstage__content {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--content-top);
  bottom: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  /* На body жесты запрещены — здесь прокрутка по делу. */
  touch-action: pan-y;
  transition: top .42s cubic-bezier(.22, 1, .36, 1);
}

/* Макет сцены в настройках: те же классы, поэтому видно настоящее поведение.
   Квадратный, чтобы круг в 74% ширины гарантированно влезал по высоте. */
.orbstage--mock {
  flex: 0 0 auto;
  aspect-ratio: 1;
  max-width: 300px;
  margin: 0 auto 14px;
  border: 1px dashed var(--tile-border);
  border-radius: 24px;
}
.orbstage--mock .orbstage__content {
  display: grid;
  place-items: center;
  margin: 0 10px 10px;
  border-radius: 16px;
  background: var(--tile-bg);
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}

/* Кольцо и фирменная «A» без перекладины. Свечение задаёт сам SVG-фильтр. */
.orb { display: block; width: 100%; height: 100%; overflow: visible; }
.orb__mark {
  fill: none;
  stroke: var(--ink);
  stroke-width: 15;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.voice__status {
  margin: 6px 0 0;
  font-size: 24px;
  font-weight: 700;
  letter-spacing: -.6px;
  text-align: center;
}

.voice__hint {
  margin: 0;
  max-width: 320px;
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.45;
  text-align: center;
  /* Место под подпись держится всегда: без него кольцо прыгает, когда текст
     появляется после ответа на запрос разрешения. */
  min-height: 42px;
}

.voice__close {
  margin-top: auto;
  padding: 14px 30px;
  border: 1px solid var(--tile-border);
  border-radius: 999px;
  background: var(--tile-bg);
  font-size: 16px;
  font-weight: 600;
}
.voice__close:active { transform: scale(.97); }

.orb-modes { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.orb-mode {
  padding: 8px 14px;
  border: 1px solid var(--tile-border);
  border-radius: 999px;
  background: var(--tile-bg);
  font-size: 14px;
  font-weight: 600;
}
.orb-mode.is-on { background: var(--coral); border-color: transparent; }

/* Ползунок подстройки звука. Числа подбираются только на слух и на месте,
   поэтому значение видно прямо на ярлыке и меняется на ходу. */
.tune {
  display: block;
  padding: 13px 16px;
  margin-bottom: 10px;
  border: 1px solid var(--tile-border);
  border-radius: 20px;
  background: var(--tile-bg);
}
.tune__top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.tune__title { font-size: 16px; font-weight: 600; }
.tune__value {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 14px;
  color: var(--coral);
}
.tune__hint { display: block; color: var(--muted); font-size: 13px; }

.tune__range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  margin: 14px 0 10px;
  border-radius: 2px;
  background: rgba(255, 255, 255, .18);
  /* Ползунок обязан ловить жест сам: на body жесты запрещены, и без этого
     он просто не тянется пальцем. */
  touch-action: pan-x;
}
.tune__range::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--ink);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .5);
}

/* ═══════════ Полноэкранная панель ═══════════ */

.panel {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--app-h);
  z-index: 40;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  animation: slide-up .28s cubic-bezier(.22, 1, .36, 1);
}
@keyframes slide-up { from { transform: translateY(100%); } }

.panel__head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: calc(var(--safe-t) + 18px) 20px 14px;
  border-bottom: 1px solid var(--tile-border);
}
.panel__title {
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  font-size: 24px;
  font-weight: 700;
  letter-spacing: -.7px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Строка списка настроек. Раздел добавляется данными в settings.js,
   поэтому здесь ничего править не нужно. */
.nav {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 15px 16px;
  margin-bottom: 10px;
  border: 1px solid var(--tile-border);
  border-radius: 20px;
  background: var(--tile-bg);
  text-align: left;
  transition: transform .12s ease, background .14s ease;
}
.nav:active { transform: scale(.985); background: rgba(255, 255, 255, .10); }

.nav__text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.nav__title { font-size: 17px; font-weight: 600; }
.nav__note { color: var(--muted); font-size: 13.5px; line-height: 1.35; }

.nav__tag {
  flex: 0 0 auto;
  padding: 3px 9px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .08);
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
}

.nav__chevron { flex: 0 0 auto; width: 20px; height: 20px; color: rgba(255, 255, 255, .35); }

.panel__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* Прокрутка тут по делу: списки настроек длиннее экрана. */
  touch-action: pan-y;
  padding: 18px 20px calc(var(--safe-b) + 32px);
}

.row {
  padding: 15px 18px;
  margin-bottom: 10px;
  border: 1px solid var(--tile-border);
  border-radius: 20px;
  background: var(--tile-bg);
}
/* Строка-кнопка: та же карточка, но по ней можно провалиться внутрь. */
.row--tap {
  display: block;
  width: 100%;
  text-align: left;
  transition: transform .12s ease, background .14s ease;
}
.row--tap:active { transform: scale(.985); background: rgba(255, 255, 255, .10); }

/* Содержимое шага трассировки: аргументы и результаты как есть. */
.trace {
  margin: 8px 0 0;
  padding: 10px 12px;
  border-radius: 12px;
  background: rgba(0, 0, 0, .35);
  color: rgba(255, 255, 255, .75);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  line-height: 1.4;
  /* Длинный JSON не должен растягивать экран: переносим, а не скроллим вбок. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.trace--error { color: var(--danger); }

.row__top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.row__id { font-size: 17px; font-weight: 700; }
.row__meta { margin-top: 6px; color: var(--muted); font-size: 13.5px; display: flex; flex-wrap: wrap; gap: 4px 14px; }

.badge { padding: 3px 10px; border-radius: 999px; font-size: 12.5px; font-weight: 600; }
.badge--ok { background: rgba(52, 199, 89, .18); color: var(--ok); }
.badge--error { background: rgba(255, 69, 58, .18); color: var(--danger); }
.badge--running { background: rgba(255, 159, 10, .18); color: #ffc266; }

.kv { display: grid; grid-template-columns: auto 1fr; gap: 10px 16px; margin: 0; font-size: 16px; }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; text-align: right; font-weight: 500; }

.action {
  width: 100%;
  padding: 15px;
  margin-top: 12px;
  border: 1px solid var(--tile-border);
  border-radius: 20px;
  background: var(--tile-bg);
  font-weight: 600;
}
.action--danger { border-color: rgba(255, 69, 58, .38); color: #ff8b83; }
.action:active { transform: scale(.98); }

/*
 * Значок канала в карточке контакта.
 *
 * Метка и цвет приезжают с сервера из объявления пакета, поэтому здесь только
 * форма: ни одного имени канала в стилях нет. Буквы, а не логотипы, — страница
 * обязана быть самодостаточной (никаких внешних картинок), а два символа читаются
 * на любом размере и не требуют чужих брендовых файлов.
 */
.chmarks { display: inline-flex; gap: 5px; align-items: center; flex: 0 0 auto; }
.chmark {
  display: inline-grid;
  place-items: center;
  min-width: 26px;
  height: 20px;
  padding: 0 6px;
  border-radius: 7px;
  background: var(--chmark, rgba(255, 255, 255, .2));
  color: #fff;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .2px;
  /* Светлые фирменные цвета съедают белый текст — поэтому тень, а не тон. */
  text-shadow: 0 1px 1px rgba(0, 0, 0, .35);
}

/*
 * Переписка представителя с посторонним.
 *
 * Свой кегль, а не панельный `.msg`: тот плакатный, под одну реплику на экран,
 * а здесь надо просмотреть двадцать и понять, что происходило. Автор подписан у
 * КАЖДОЙ реплики нашей стороны — «Alter» и «ты» в чужом чате различать
 * обязательно, иначе не видно, где он говорил сам.
 */
.talk { display: flex; flex-direction: column; gap: 10px; margin-bottom: 14px; }
.talk__item {
  max-width: 88%;
  padding: 9px 13px 11px;
  border: 1px solid var(--tile-border);
  border-radius: 16px;
  background: var(--tile-bg);
  font-size: 15.5px;
  line-height: 1.4;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  -webkit-user-select: text;
  user-select: text;
}
.talk__item--contact { align-self: flex-start; border-bottom-left-radius: 6px; }
.talk__item--agent,
.talk__item--owner { align-self: flex-end; border-bottom-right-radius: 6px; }
.talk__item--agent { background: rgba(254, 99, 68, .13); border-color: rgba(254, 99, 68, .24); }
.talk__item--owner { background: rgba(110, 231, 135, .12); border-color: rgba(110, 231, 135, .22); }
/* «Не ответил» — не реплика, а отметка: без пузыря, мелко и по центру. */
.talk__item--silence {
  align-self: center;
  background: none;
  border-color: transparent;
  color: rgba(255, 255, 255, .5);
  font-size: 14px;
}
.talk__who {
  display: block;
  margin-bottom: 4px;
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 600;
}

/* Портрет: связный текст, а не строка метаданных. */
.portrait {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: 15.5px;
  line-height: 1.45;
  -webkit-user-select: text;
  user-select: text;
}

/* Поле «написать самому»: в переписке с посторонним и в форме ответа. */
.reply { margin-top: 12px; }
.reply__text {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--tile-border);
  border-radius: 16px;
  background: var(--field-bg, rgba(120, 120, 128, .12));
  color: inherit;
  font: inherit;
  /* Иначе iOS зумит страницу при фокусе в поле мельче 16px. */
  font-size: 16px;
  resize: none;
}
.reply .action { margin-top: 8px; }

/* Автоформа настроек: поля рисуются по объявлению раздела с сервера. */
.cfg {
  display: block;
  padding: 14px 16px;
  margin-bottom: 10px;
  border: 1px solid var(--tile-border);
  border-radius: 20px;
  background: var(--tile-bg);
}
.cfg--row { display: flex; align-items: center; justify-content: space-between; gap: 14px; }
.cfg__text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.cfg__title { display: block; font-size: 16px; font-weight: 600; }
.cfg__hint { display: block; margin-top: 2px; color: var(--muted); font-size: 13px; line-height: 1.35; }

.cfg__input {
  width: 100%;
  height: 46px;
  margin-top: 10px;
  padding: 0 14px;
  border: 1px solid var(--tile-border);
  border-radius: 14px;
  background: rgba(0, 0, 0, .35);
  color: var(--ink);
  font: inherit;
  font-size: 16px;
}
/* Многострочное поле: та же рамка, но высота по строкам и без обрезки. */
textarea.cfg__input {
  height: auto;
  min-height: 120px;
  padding: 12px 14px;
  line-height: 1.45;
  resize: vertical;
}
.cfg__input:focus { outline: 2px solid var(--coral); outline-offset: 1px; }
.cfg__input::placeholder { color: rgba(255, 255, 255, .3); }
/* Тумблер стоит в строке справа и в отступе сверху не нуждается. */
.cfg--row .cfg__input { width: 52px; height: 32px; margin: 0; flex: 0 0 auto; }

/* Кнопка в автоформе: тот же .action, но прижат к своему полю, а не к форме. */
.cfg > .action { margin-top: 10px; }
.cfg > .note:last-child { margin: 8px 0 0; }

/*
 * Список внутри раздела настроек: строки приезжают с сервера готовыми.
 *
 * Словарь общий (ADR-0007) — здесь нет ни слова про учётки, и это условие, а не
 * совпадение: первый же список чего-то другого должен встать сюда без правок.
 */
.lst { margin-top: 10px; }
.lst__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--tile-border);
}
.lst__row:first-child { border-top: 0; padding-top: 4px; }
.lst__text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.lst__title { display: flex; align-items: center; gap: 8px; font-size: 15px; font-weight: 600; }
.lst__note { color: var(--muted); font-size: 13px; line-height: 1.35; word-break: break-word; }
.lst__badge {
  flex: 0 0 auto;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
}
.lst__badge--ok { background: rgba(52, 199, 89, .18); color: var(--ok); }
.lst__badge--warn { background: rgba(255, 214, 10, .16); color: #ffd60a; }
.lst__badge--danger { background: rgba(255, 69, 58, .18); color: var(--danger); }
.lst__acts { display: flex; flex: 0 0 auto; gap: 8px; }
.lst__act {
  padding: 7px 12px;
  border: 1px solid var(--tile-border);
  border-radius: 12px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
}
.lst__act--danger { border-color: rgba(255, 69, 58, .38); color: var(--danger); }
.lst__act:disabled { opacity: .5; }
.lst__search { margin-top: 10px; }
.lst__row--img { justify-content: flex-start; }
.lst__row--img .lst__text { flex: 1 1 auto; }
.lst__thumb {
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: var(--tile-border) center / cover no-repeat;
}
.lst__thumb--broken { opacity: .35; }

/* Картинка целиком — поверх всего, нажатие закрывает. */
.viewer {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  padding: 16px;
  background: rgba(0, 0, 0, .92);
}
.viewer img { max-width: 100%; max-height: 100%; border-radius: 8px; }

.note { margin: 0 0 12px; color: var(--muted); font-size: 14.5px; line-height: 1.45; }
.empty { margin: 40px 0; color: var(--muted); text-align: center; }

/* ═══════════ Привязка ═══════════ */

.pair {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--app-h);
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 24px;
  background: var(--bg);
}

.pair__box { width: 100%; max-width: 360px; text-align: center; }
.pair__title { margin: 0 0 8px; font-size: 44px; font-weight: 700; letter-spacing: -1.4px; }
.pair__hint { margin: 0 0 26px; color: var(--muted); font-size: 14.5px; }
.pair__hint code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  padding: 2px 6px;
  border-radius: 6px;
  background: var(--tile-bg);
}

.pair__input {
  width: 100%;
  height: 60px;
  margin-bottom: 12px;
  border: 1px solid var(--tile-border);
  border-radius: 20px;
  background: var(--tile-bg);
  color: var(--ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 24px;
  font-weight: 600;
  letter-spacing: 4px;
  text-align: center;
  text-transform: uppercase;
}
.pair__input:focus { outline: 2px solid var(--coral); outline-offset: 2px; }

.pair__button {
  width: 100%;
  height: 56px;
  border: 0;
  border-radius: 20px;
  background: var(--coral);
  color: #fff;
  font-size: 17px;
  font-weight: 600;
}
.pair__button:active { transform: scale(.98); }
.pair__button[disabled] { opacity: .5; }

.pair__error { margin: 14px 0 0; color: #ff8b83; font-size: 14.5px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001s !important; transition-duration: .001s !important; }
}
