

/* ── GUESTRO — синий акцент, непрозрачные поверхности ──────────────────────
   Отдельной темой, а не заменой оформления. Так её видно рядом с нынешней,
   переключателем, и откат стоит одного нажатия — а не отката сборки.

   Задаём ВСЕ пятьдесят токенов, которыми пользуется клиент, даже те, чьё
   значение совпадает с общим. Незаданный токен не исчезает: он достаётся от
   `:root`, то есть от «Мілітарі», — и остаётся оранжевым посреди синего. На
   глаз такое ловится не сразу и не везде. */
[data-theme="guestro"] {
  --fg-rgb: 255, 255, 255;
  /* Тот же цвет тройкой без имени: им в `redesign.css` заданы переливы
     поверхностей — `rgba(var(--fg),.09)`. Раньше он приходил из блоков тем,
     которые убраны; с пустым значением заливка стала бы недействительной, и
     поверхности потеряли бы фон целиком. */
  --fg: 255, 255, 255;
  --c-bg: #0D0D12; --c-chatBg: #0D0D12;
  /* Поверхности непрозрачные и светлеют за слоем на известный шаг: подложка
     больше не просвечивает, форму держит волосяной кант, а не размытие. */
  --c-surface: #1A1A21; --c-surf2: #24242E; --c-surf3: #2A2A31; --c-input: #1A1A21;
  --c-groupedBg: #0D0D12;
  --c-text: #FFFFFF; --c-text2: #EEEEEE; --c-sub: #808898; --c-ph: #666D80;
  --c-accent: #3A5EFF; --c-accentRgb: 58, 94, 255; --c-accentD: rgba(58,94,255,0.16);
  --c-accentGrad: linear-gradient(180deg,#3A5EFF 0%,#0F4FE4 100%);
  /* Текст на акценте здесь белый, а не чёрный. Держим токеном: в клиенте
     чёрным литералом не написано ни одного такого места — проверено. */
  --c-onAccent: #FFFFFF;
  --c-red: #FD2222; --c-redD: rgba(253,34,34,0.16);
  --c-blue: #5F8EFC; --c-amber: #FBA71F;
  --c-bOut: #3A5EFF; --c-bIn: #1A1A21;
  --c-bOutGrad: linear-gradient(180deg,#3A5EFF 0%,#0F4FE4 100%);
  --c-bubbleOut: #3A5EFF;
  --c-bOutText: #FFFFFF; --c-bInText: #EEEEEE;
  --c-bOutMeta: rgba(255,255,255,0.72); --c-bInMeta: rgba(238,238,238,0.55);
  --c-bOutTick: #FFFFFF; --c-bInTick: #5F8EFC;
  /* Обводка исходящего пузыря. В оформлении SLOVO она есть всегда, и до сих
     пор была вписана числом — в жёлтой теме вышла бы синяя рамка. */
  --c-bOutBorder: #2E4794;
  /* Чем красить включённый тумблер. Обычно это акцент, но в чёрно-белой теме
     акцент белый, и кноб белый: включённый тумблер становился сплошным
     пятном без положения. */
  --c-switchOn: var(--c-accent);
  --c-tickRead: #5F8EFC; --c-tickSent: rgba(255,255,255,0.42);
  --c-sep: rgba(255,255,255,0.06);
  --c-bdr: rgba(255,255,255,0.08); --c-bdrHi: rgba(255,255,255,0.20);
  /* Стекла у Guestro нет: то, что раньше просвечивало, стало плотным цветом.
     Значения оставлены на месте, потому что ими крашены реальные поверхности
     — панель, меню, всплывающие подсказки, — и пустой токен обнулил бы их. */
  --c-glass: rgba(26,26,33,0.92);
  --c-glassDeep: rgba(13,13,18,0.96);
  --c-glassToast: rgba(36,36,46,0.98);
  --c-glassChat: #0D0D12;
  --c-glassInput: #1A1A21;
  --c-glassMenu: rgba(36,36,46,0.96);
  --c-glassPill: rgba(36,36,46,0.58);
  --c-scrim: rgba(0,0,0,0.55); --c-scrimDeep: rgba(0,0,0,0.88);
  --c-segTrack: #24242E; --c-segActive: #3A5EFF;
  --c-status-online: #2FCF6A; --c-status-field: #FBA71F;
  --c-status-silent: #808898; --c-status-off: rgba(255,255,255,0.18);
  /* Ширина пузыря — из прототипа. Держим токеном здесь, а не отдельным
     правилом: признак темы висит на том же элементе, что и .phone, и
     селектор «.phone внутри темы» не сработал бы. */
  --bubble-max: 268px;
  /* Всё, что пилюля, скруглено в полную дугу — композер, чипы, кнопки. */
  --radius-pill: 100px;
}

/* Светлый Guestro — тот же набор форм, другой набор цветов.
   Значения взяты из прототипа (`guestro-skin.css`, блок светлой темы) и
   развёрнуты до конечных: у нас нет его промежуточных имён вроде `bg-hover`.
   Переопределяем только то, что и он: акцент, красный, состояния присутствия
   и цвета исходящего пузыря на светлом те же — синий на белом читается. */
[data-theme="guestro"][data-mode="light"] {
  --fg-rgb: 13, 13, 18;
  --fg: 13, 13, 18;
  --c-bg: #F6F8FA; --c-chatBg: #F6F8FA; --c-groupedBg: #ECEFF3;
  --c-surface: #FFFFFF; --c-surf2: #FAFAFA; --c-surf3: #ECEFF3; --c-input: #FFFFFF;
  --c-text: #0D0D12; --c-text2: #37394A; --c-sub: #666D80; --c-ph: #A3ACB9;
  --c-sep: #EBEFF3; --c-bdr: #DFE1E6; --c-bdrHi: #C1C7D0;
  --c-bIn: #ECEFF3; --c-bInText: #0D0D12;
  --c-bInMeta: rgba(13,13,18,0.55);
  --c-tickSent: #A3ACB9;
  --c-glass: #FFFFFF; --c-glassDeep: #F6F8FA;
  --c-glassMenu: #FFFFFF; --c-glassPill: #F6F8FA;
  --c-glassChat: #F6F8FA; --c-glassInput: #FFFFFF;
  --c-glassToast: #FFFFFF;
  --c-scrim: rgba(13,13,18,0.34); --c-scrimDeep: rgba(13,13,18,0.72);
  --c-segTrack: #ECEFF3;
  --c-status-off: rgba(13,13,18,0.18);
}


/* Бурштин — тот же морок и та же форма, но жёлтый сигнальный вместо синего.
   Для тех, кому синий сливается с ночным небом на экране. Значения взяты из
   их темы (`Design/Theme.swift`, case .amber) и развёрнуты до конечных. */
[data-theme="guestro"][data-mode="amber"] {
  --c-bg: #0A0A08; --c-chatBg: #0A0A08; --c-groupedBg: #0A0A08;
  --c-surface: #17170F; --c-surf2: #22221B; --c-surf3: #2C2C22; --c-input: #17170F;
  --c-text: #FFFBF2; --c-text2: #F3EEE2; --c-sub: #8E887A; --c-ph: #6E6A5E;
  --c-accent: #F2D244; --c-accentRgb: 242, 210, 68; --c-accentD: rgba(242,210,68,0.16);
  --c-accentGrad: linear-gradient(180deg,#F2D244 0%,#D9B72A 100%);
  /* На жёлтом белая подпись не читается — поверх акцента тёмное. */
  --c-onAccent: #0D0D12;
  --c-blue: #F7DC5C;
  --c-bOut: #3A3214; --c-bIn: #17170F;
  --c-bOutGrad: linear-gradient(180deg,#3A3214 0%,#2E2810 100%);
  --c-bubbleOut: #3A3214;
  --c-bOutBorder: #5A4D1D;
  --c-bOutText: #F3EEE2; --c-bInText: #F3EEE2;
  --c-bOutMeta: #8E887A; --c-bInMeta: #8E887A;
  --c-bOutTick: #F7DC5C; --c-bInTick: #F7DC5C;
  --c-tickRead: #F7DC5C; --c-tickSent: rgba(243,238,226,0.42);
  --c-glass: rgba(23,23,15,0.92);
  --c-glassDeep: rgba(10,10,8,0.96);
  --c-glassToast: rgba(34,34,27,0.98);
  --c-glassChat: #0A0A08;
  --c-glassInput: #17170F;
  --c-glassMenu: rgba(34,34,27,0.96);
  --c-glassPill: rgba(34,34,27,0.58);
  --c-segTrack: #22221B; --c-segActive: #F2D244;
  --c-status-silent: #8E887A;
}

/* Контур — чёрно-белая. Когда цвет мешает читать, остаётся форма.
   Опасность здесь остаётся красной: она значит одно и то же во всех темах,
   и обесцветить её значило бы стереть единственное предупреждение. */
[data-theme="guestro"][data-mode="mono"] {
  --c-bg: #000000; --c-chatBg: #000000; --c-groupedBg: #000000;
  --c-surface: #141414; --c-surf2: #1F1F1F; --c-surf3: #2A2A2A; --c-input: #141414;
  --c-text: #FFFFFF; --c-text2: #E6E6E6; --c-sub: #9A9A9A; --c-ph: #6E6E6E;
  --c-accent: #FFFFFF; --c-accentRgb: 255, 255, 255; --c-accentD: rgba(255,255,255,0.14);
  --c-accentGrad: linear-gradient(180deg,#FFFFFF 0%,#D6D6D6 100%);
  --c-onAccent: #0D0D12;
  --c-blue: #D6D6D6;
  --c-bOut: #242424; --c-bIn: #141414;
  --c-bOutGrad: linear-gradient(180deg,#242424 0%,#1C1C1C 100%);
  --c-bubbleOut: #242424;
  --c-bOutBorder: #4A4A4A;
  --c-bOutText: #FFFFFF; --c-bInText: #FFFFFF;
  --c-bOutMeta: #9A9A9A; --c-bInMeta: #9A9A9A;
  --c-bOutTick: #D6D6D6; --c-bInTick: #D6D6D6;
  --c-tickRead: #D6D6D6; --c-tickSent: rgba(255,255,255,0.42);
  --c-glass: rgba(20,20,20,0.92);
  --c-glassDeep: rgba(0,0,0,0.96);
  --c-glassToast: rgba(31,31,31,0.98);
  --c-glassChat: #000000;
  --c-glassInput: #141414;
  --c-glassMenu: rgba(31,31,31,0.96);
  --c-glassPill: rgba(31,31,31,0.58);
  --c-segTrack: #1F1F1F; --c-segActive: #FFFFFF;
  /* Присутствие тоже теряет цвет: зелёная точка на чёрно-белом экране —
     единственное цветное пятно, и глаз цепляется за неё вместо имени. */
  --c-status-online: #FFFFFF; --c-status-field: #9A9A9A; --c-status-silent: #6E6E6E;
  /* Белый тумблер с белым кнобом — сплошное пятно: заливку берём серой. */
  --c-switchOn: #2A2A2A;
}

/* Материал — тоже частью темы, а не общей правкой.
   Так «Мілітарі» и остальные четыре остаются в точности прежними: у них
   стекло, зерно и подсветка на месте. Ровно тем же приёмом сделана «Сигнал»
   строкой выше — там отменены тени. */
[data-theme="guestro"] .glass,
[data-theme="guestro"] .glass-deep,
[data-theme="guestro"] .glass-card,
[data-theme="guestro"] .glass-menu,
[data-theme="guestro"] .glass-pill,
[data-theme="guestro"] .bubble-in,
[data-theme="guestro"] .bubble-out { backdrop-filter: none; -webkit-backdrop-filter: none; }
/* Зеркальный блик, кант-подсветка, зерно и виньетка — всё это про стекло. */
[data-theme="guestro"] .sheen::after,
[data-theme="guestro"] .rim::before,
[data-theme="guestro"] .phone::after,
[data-theme="guestro"] .screen::after { display: none; }
[data-theme="guestro"] .glow-accent,
[data-theme="guestro"] .glow-soft,
[data-theme="guestro"] .glow-text { box-shadow: none; }
[data-theme="guestro"] .chat-grid-bg { background-image: none; }
/* Две поверхности размытие сохраняют — нижняя панель и свёрнутая шапка.
   Это не послабление, а предел из нашего же гайдлайна: больше двух размытых
   поверхностей на экран телефона не держим. */
[data-theme="guestro"] .nav-bar,
[data-theme="guestro"] .collapse-bar {
  backdrop-filter: blur(30px) saturate(170%);
  -webkit-backdrop-filter: blur(30px) saturate(170%);
}
/* Нажатие — прозрачностью, никогда масштабом. */
[data-theme="guestro"] .pressable:active { transform: none; opacity: .72; }
/* Аватары круглые. Квадратных со скруглением у Guestro нет вовсе — это самая
   заметная перемена формы после цвета.
   `!important` здесь вынужденный, а не небрежность: радиус приходит из
   разметки, свойством элемента, и обычным правилом его не перебить. Зато
   правка не трогает ни одного экрана — значит и сломать в других темах
   нечего. */
[data-theme="guestro"] .avatar-mono { border-radius: 50% !important; }

/* Плашки аватаров — палитра Guestro, взятая из самого прототипа.
   Двенадцать плоских цветов синего семейства вместо наших тёмных переливов,
   и к каждому подобран свой цвет букв: на бледной плашке белым не прочитать.
   Номер плашки считает разметка тем же перебором имени, что и раньше, — то
   есть человек своего цвета не теряет, он лишь становится другим.
   `!important` вынужден: заливка приходит свойством прямо из разметки. */
[data-theme="guestro"] .avatar-mono[data-idx="0"] { background: #3A5EFF !important; color: #FFFFFF !important; }
[data-theme="guestro"] .avatar-mono[data-idx="1"] { background: #0F4FE4 !important; color: #FFFFFF !important; }
[data-theme="guestro"] .avatar-mono[data-idx="2"] { background: #4079FC !important; color: #FFFFFF !important; }
[data-theme="guestro"] .avatar-mono[data-idx="3"] { background: #0B3EB2 !important; color: #FFFFFF !important; }
[data-theme="guestro"] .avatar-mono[data-idx="4"] { background: #5F8EFC !important; color: #0D0D12 !important; }
[data-theme="guestro"] .avatar-mono[data-idx="5"] { background: #00C4E4 !important; color: #0D0D12 !important; }
[data-theme="guestro"] .avatar-mono[data-idx="6"] { background: #91B2FD !important; color: #0D0D12 !important; }
[data-theme="guestro"] .avatar-mono[data-idx="7"] { background: #24242E !important; color: #EEEEEE !important; }
[data-theme="guestro"] .avatar-mono[data-idx="8"] { background: #263CAB !important; color: #FFFFFF !important; }
[data-theme="guestro"] .avatar-mono[data-idx="9"] { background: #7686F5 !important; color: #0D0D12 !important; }
[data-theme="guestro"] .avatar-mono[data-idx="10"] { background: #2A2A31 !important; color: #EEEEEE !important; }
[data-theme="guestro"] .avatar-mono[data-idx="11"] { background: #B5CBFE !important; color: #0D0D12 !important; }
/* Заголовки — Tektur, и цепляется он за то, что уже есть.
   Шрифт заголовков в приложении давно вынесен в свой токен, и пользуются им
   ровно два места: крупное название вкладки и та же надпись в свёрнутой
   полосе. Обе — шапка экрана. Значит достаточно подменить сам токен, и ни
   одной строки разметки трогать не надо. */
[data-theme="guestro"] {
  --font-display: 'Tektur', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif, var(--font-emoji);
}
/* Межбуквенное поджатие снимаем: у нас оно от Inter Tight, а Tektur узкий
   сам по себе, и с поджатием буквы в заголовке начинают липнуть.
   `!important` вынужден — расстояние задано свойством прямо в разметке. */
[data-theme="guestro"] .collapse-title h1,
[data-theme="guestro"] .collapse-bar > span { letter-spacing: -0.005em !important; }

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

   Признак `data-min` разметка ставит всем темам одинаково. Правила на него
   есть только здесь, поэтому у остальных четырёх не меняется ничего. */
[data-theme="guestro"] .nav-cap { border-radius: 100px !important; }
/* Внутренняя кнопка скругляется по правилу концентричности: внешний радиус
   минус отступ между ними. Иначе угол кнопки спорит с дугой капсулы. */
[data-theme="guestro"] .nav-cap > button { border-radius: 95px !important; }
/* Свёрнутая панель: подписи уходят в ноль ширины, а не гаснут на месте, —
   иначе капсула осталась бы прежней высоты с пустой полосой под значками. */
[data-theme="guestro"] .nav-cap[data-min='true'] .nav-label {
  opacity: 0; max-width: 0; max-height: 0; overflow: hidden;
}
[data-theme="guestro"] .nav-cap .nav-label {
  max-width: 90px; max-height: 14px; overflow: hidden; white-space: nowrap;
  transition: opacity .2s cubic-bezier(.4,0,.2,1),
              max-width .2s cubic-bezier(.4,0,.2,1),
              max-height .2s cubic-bezier(.4,0,.2,1);
}
/* Высоту надо снимать и с самой подписи, и с промежутка над ней.
   Одной ширины мало: строка схлопывается в ноль по горизонтали, но своё
   межстрочное держит, и капсула теряет две точки вместо восемнадцати.
   Промежуток задан свойством в разметке, оттого `!important`. */
[data-theme="guestro"] .nav-cap > button {
  transition: padding .2s cubic-bezier(.4,0,.2,1), gap .2s cubic-bezier(.4,0,.2,1),
              background .2s ease, box-shadow .2s ease;
}
[data-theme="guestro"] .nav-cap[data-min='true'] > button {
  padding: 8px 0 !important; gap: 0 !important;
}
/* Вкладка профиля — лицо владельца, а не безымянный значок человека.
   Обе метки лежат в разметке всегда; здесь одна прячется, другая
   показывается. Прочим темам этих правил не написано, и у них остаётся
   прежний значок. */
/* Плавающая кнопка у нас была и раньше — 56 точек, с переливом и ореолом.
   Guestro хочет её мельче, ближе к краю и плоской. Заводить вторую такую же
   было бы ошибкой, и я её чуть не сделал: сперва написал свою и увидел на
   снимке две наложенные. Размеры заданы свойствами в разметке, оттого
   `!important`. */
/* Шапка разговора у нас той же формы, что в макете, — полоса со скруглённым
   низом. Расходились только мерки: аватар 40 против 36, имя 16 против 15.5. */
[data-theme="guestro"] .chat-head .avatar-box,
[data-theme="guestro"] .chat-head .avatar-mono {
  width: 36px !important; height: 36px !important;
}
[data-theme="guestro"] .chat-head .avatar-ini { font-size: 15px !important; }

/* Композер. Заливка, рамка и отдельная круглая кнопка у нас уже были — вся
   разница в скруглении и размерах. Пилюли Guestro скруглены в полную дугу,
   поэтому меняем сам токен: он же выпрямит и прочие пилюли разом. */
[data-theme="guestro"] .composer-shell { padding: 6px !important; }
[data-theme="guestro"] .send-btn { width: 52px !important; height: 52px !important; }

/* Строка списка. Наша заметно крупнее макетной: аватар 54 против 44, высота
   задана жёстко 74, имя 16 против 15. От этого список выглядит другим ещё до
   того, как посмотришь на цвета.
   Высоту снимаем вовсе — пусть считается по содержимому, как в макете. */
/* Метка группы в списке. По умолчанию её нет — она пришла с оформлением
   SLOVO и живёт только там; здесь стоит выключатель, чтобы разметка могла
   рисовать её всегда и не спрашивать, какая сейчас оболочка. */
.row-group-mark { display: none; align-items: center; flex-shrink: 0; margin-right: 5px; }

[data-theme="guestro"] .chat-row {
  height: auto !important; padding: 5px 0 !important; gap: 11px !important;
}
[data-theme="guestro"] .chat-row .avatar-box,
[data-theme="guestro"] .chat-row .avatar-mono {
  width: 44px !important; height: 44px !important;
}
[data-theme="guestro"] .chat-row .avatar-ini { font-size: 18.5px !important; }
[data-theme="guestro"] .row-name { font-size: 15px !important; }
[data-theme="guestro"] .row-prev { font-size: 13.5px !important; }
/* Время в списке — обычным начертанием, а не моноширинным.
   Моноширинный в макете отменён везде; мы отменяем его только здесь, в списке.
   Наше правило «моноширинный = телеметрия» остаётся в силе для координат и
   таймеров разговора: там разная ширина цифр и вправду мешает читать. */
[data-theme="guestro"] .row-time {
  font-family: var(--font-sans) !important;
  letter-spacing: 0 !important; font-weight: 500 !important;
}

/* Пузырь: числа взяты из прототипа. Скругление у нас уже совпадало — 14, —
   а вот ширина, отступы и место метки времени расходились.
   Метка встаёт справа ВНУТРИ пузыря и обтекается текстом: короткая фраза и
   время умещаются в одну строку, длинная обтекает метку снизу. Ради этого
   текст и сделан строчным. */
[data-theme="guestro"] .bubble-in,
[data-theme="guestro"] .bubble-out { padding: 8px 12px 6px !important; }
[data-theme="guestro"] .msg-text { font-size: 15px !important; line-height: 1.35 !important; }
[data-theme="guestro"] .msg-meta {
  float: right; display: inline-flex !important;
  margin: 8px 0 0 10px !important;
}

[data-theme="guestro"] .fab {
  width: 48px !important; height: 48px !important;
  right: 16px !important;
  bottom: calc(86px + env(safe-area-inset-bottom, 0px)) !important;
  background: var(--c-accent) !important;
  box-shadow: 0 6px 18px rgba(58,94,255,.5) !important;
}

*, *::before, *::after { box-sizing: border-box; }
html, body {
  margin: 0; padding: 0;
  background: var(--c-bg);
  color: var(--c-text);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior-y: none;
  min-height: 100dvh;
}
button, input { font-family: inherit; }
button { background: none; border: none; cursor: pointer; color: inherit; -webkit-tap-highlight-color: transparent; outline: none; transition: opacity .12s ease, background .15s ease; }
button:active { opacity: .7; }
input { border: none; outline: none; background: transparent; color: inherit; }
input::placeholder { color: var(--c-ph); }
::-webkit-scrollbar { width: 0; height: 0; }

/* ── App container — always phone-shaped (full width on phones, capped column on desktops) ── */
body {
  background: var(--c-bg);
  display: flex;
  align-items: stretch;
  justify-content: center;
  transition: background .25s ease, color .25s ease;
}
/* Ширина сообщения. На телефоне она почти во весь экран, на большом окне
   растёт вместе с ним, но не во всю ширину: строку длиной в монитор глаз не
   дочитывает — теряет начало следующей. */
:root { --bubble-max: 290px; }

/* Высоту обычно даёт 100dvh, но при поднятой клавиатуре на iPhone она не
   меняется: dvh считает окно, а клавиатура окно не уменьшает. Система вместо
   этого прокручивает вебвью вверх, чтобы показать поле ввода, — и утаскивает
   шапку под часы вместе с отступом безопасной зоны, потому что отступ у нас
   в вёрстке, а не в системе.

   Поэтому высоту задаёт --app-h: приложение подставляет туда настоящую
   видимую высоту (visualViewport). Экран ужимается до неё, поле ввода
   оказывается на виду само, и прокручивать системе нечего. Умолчание
   оставлено на случай, когда visualViewport недоступен. */
.phone {
  position: relative;
  width: 100%;
  height: var(--app-h, 100dvh);
  background: var(--c-bg);
  overflow: hidden;
}

.screen {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  padding-top: env(safe-area-inset-top, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* ── Motion ────────────────────────────────────────────────────────────── */

/* Base / improved bubble — spring overshoot (tactical pop) */
@keyframes msgIn {
  0%   { opacity: 0; transform: scale(.88) translateY(8px); }
  55%  { opacity: 1; transform: scale(1.03) translateY(-2px); }
  100% { opacity: 1; transform: scale(1)    translateY(0); }
}

/* Переход по цитате: показать, куда именно перенесло. Без пометки прыжок
   к старому сообщению выглядит как случайная прокрутка. */
@keyframes msgFlash {
  0%   { box-shadow: 0 0 0 0 rgba(var(--c-accentRgb),0); }
  25%  { box-shadow: 0 0 0 3px rgba(var(--c-accentRgb),0.55); }
  100% { box-shadow: 0 0 0 0 rgba(var(--c-accentRgb),0); }
}
.msg-flash { animation: msgFlash 1.4s ease-out; border-radius: 16px; }

/* Sheets and overlays */
@keyframes slideUp { from { opacity: 0; transform: translateY(100%); } to { opacity: 1; transform: translateY(0); } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* Заглушки, пока грузится список или переписка. Не мигание, а медленный
   перелив: мельтешение на экране читается как поломка. */
@keyframes skeletonPulse {
  0%, 100% { opacity: .45; }
  50%      { opacity: .8; }
}
.skeleton {
  background: rgba(var(--fg-rgb), 0.10);
  border-radius: 8px;
  animation: skeletonPulse 1.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; opacity: .55; }
}
@keyframes slideInRight { from { opacity: 0; transform: translateX(24px); } to { opacity: 1; transform: translateX(0); } }
@keyframes slideInLeft  { from { opacity: 0; transform: translateX(-18px); } to { opacity: 1; transform: translateX(0); } }

/* Status indicators */
@keyframes onlineGlow { 0%,100%{opacity:1} 50%{opacity:.6} }
@keyframes typingDot  { 0%,60%,100%{opacity:.3;transform:translateY(0)} 30%{opacity:1;transform:translateY(-3px)} }
@keyframes milPulse   { 0%{transform:scale(1);opacity:.55} 100%{transform:scale(2.0);opacity:0} }

/* NEW — list stagger entry */
@keyframes staggerIn {
  0%   { opacity: 0; transform: translateY(10px) scale(.98); }
  100% { opacity: 1; transform: translateY(0)    scale(1); }
}

/* NEW — tab body cross-fade */
@keyframes tabFadeIn {
  0%   { opacity: 0; transform: translateY(6px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* NEW — toast spring bounce */
@keyframes toastBounce {
  0%   { opacity: 0; transform: translateY(40px) scale(.92); }
  55%  { opacity: 1; transform: translateY(-4px) scale(1.03); }
  100% { opacity: 1; transform: translateY(0)    scale(1); }
}

/* NEW — reaction pop (used on Bubble reaction chip) */
@keyframes reactionPop {
  0%   { opacity: 0; transform: scale(.2) rotate(-12deg); }
  55%  { opacity: 1; transform: scale(1.25) rotate(4deg); }
  78%  { transform: scale(.92) rotate(-2deg); }
  100% { opacity: 1; transform: scale(1) rotate(0deg); }
}

/* NEW — scanner sweep (active call avatar / OPSEC card) */
@keyframes scannerSweep {
  0%   { top: -8%;  opacity: 0; }
  8%   { opacity: 1; }
  92%  { opacity: 1; }
  100% { top: 108%; opacity: 0; }
}

/* NEW — status dot pulse (online avatar ring) */
@keyframes statusPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(110,139,61,0.55); }
  50%      { box-shadow: 0 0 0 6px rgba(110,139,61,0); }
}

/* NEW — FAB tap pulse (orange ripple) */
@keyframes fabPulse {
  0%   { box-shadow: 0 10px 28px rgba(var(--c-accentRgb),0.45), 0 0 0 0 rgba(var(--c-accentRgb),0.55); }
  100% { box-shadow: 0 10px 28px rgba(var(--c-accentRgb),0.45), 0 0 0 22px rgba(var(--c-accentRgb),0); }
}

/* NEW — number ticker (unread badge) */
@keyframes numberRoll {
  0%   { opacity: 0; transform: translateY(-6px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* NEW — route marching ants (map lightbox) */
@keyframes routeMarch {
  to { stroke-dashoffset: -14; }
}

/* NEW — composer focus glow */
@keyframes composerGlow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(var(--c-accentRgb),0.25); }
  50%      { box-shadow: 0 0 0 3px rgba(var(--c-accentRgb),0.08); }
}

/* Utility classes for motion */
.motion-stagger      { animation: staggerIn .38s cubic-bezier(.34,1.4,.64,1) backwards; }
.motion-tab-fade     { animation: tabFadeIn .28s cubic-bezier(.2,.8,.4,1); }
.motion-toast-bounce { animation: toastBounce .35s cubic-bezier(.34,1.56,.64,1); }
.motion-reaction-pop { animation: reactionPop .35s cubic-bezier(.34,1.56,.64,1); }
.motion-status-pulse { animation: statusPulse 2.2s ease-out infinite; }
.motion-fab-pulse    { animation: fabPulse .55s cubic-bezier(.2,.8,.4,1); }
.motion-scanner      { position: absolute; left: 0; right: 0; height: 2px;
                       background: linear-gradient(90deg, transparent 0%, rgba(var(--c-accentRgb),0.85) 50%, transparent 100%);
                       filter: blur(1.5px); pointer-events: none;
                       animation: scannerSweep 2.6s ease-in-out infinite; }

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}


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

/* Окно шире телефона — приложение занимает его целиком.
   Раньше здесь была «карточка в рамке» посреди экрана: задумывалась как
   аккуратная витрина, а на деле выглядела обрезком — половина монитора
   пустая, переписка ютится в коробочке, и при любом изменении масштаба
   коробочка прыгала. Настольные программы так себя не ведут. */
@media (min-width: 640px) {
  :root { --bubble-max: 60%; }
  .phone { max-width: none; }
}

@media (min-width: 900px) {
  :root { --bubble-max: min(60%, 560px); }

}

/* Колонка с прокруткой, содержимое которой не сжимается.

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

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

   Распорки это не касается: у неё `flex: 1` прописан в разметке, а стиль в
   разметке сильнее таблицы стилей. Ей и положено схлопываться — на то она и
   распорка. */
.form-column > * { flex-shrink: 0; }

/* Горизонтальная ориентация телефона: шапка и композер съедали весь экран. */
@media (max-height: 480px) and (orientation: landscape) {
  .phone { height: 100dvh; }
}

/* ── Две колонки ──────────────────────────────────────────────────────────
   На большом экране держать переписку и список по очереди — расточительно:
   места хватает на оба. Ширину рамки поднимаем только здесь, чтобы на
   планшете осталась привычная одна колонка. */

@media (min-width: 1100px) {
  :root { --bubble-max: min(62%, 680px); }
}

.screen--wide {
  flex-direction: row;
  padding-bottom: 0;
}

.screen--wide .pane-list {
  /* Растёт вместе с окном, но в меру: слишком широкий столбец имён читается
     хуже узкого, а слишком узкий рвёт названия групп. */
  width: clamp(340px, 24vw, 460px);
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-right: 1px solid var(--c-bdr);
  position: relative;
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* Переписка занимает всё, что осталось от списка и панели. Ограничивать её
   посередине пробовали — на большом мониторе рядом со списком оставалась
   пустая полоса, и это читалось как недоделка. Длину строки держит в узде
   ширина самого сообщения, а не колонки. */
.screen--wide .chat-column {
  width: 100%;
}

/* Третья колонка: смайлики, стикеры и гифки. На широком окне место есть, и
   прятать её за кнопкой значит заставлять тянуться мышью ради каждого
   смайлика. Уже — панель всплывает над перепиской. */
.screen--wide .pane-extras {
  width: clamp(300px, 22vw, 380px);
  flex-shrink: 0;
}

.screen--wide .pane-chat {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
  position: relative;
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* Нижняя навигация позиционируется абсолютно — в двух колонках она
   прикрепится к левой колонке, потому что у неё своя система координат. */

/* Поля ввода на телефоне — не мельче 16 точек.

   Safari на iPhone приближает страницу, когда фокус попадает в поле со
   шрифтом меньше 16: экран прыгает и уезжает вбок, и обратно сам не
   возвращается. Так ломался поиск в переписке — открыл, ткнул в поле, и
   приложение оказывалось увеличенным и сдвинутым.

   Только там, где палец: на десктопе размеры остаются макетными. И через
   !important, потому что размеры проставлены в самой разметке, а спорить с
   каждым из шестнадцати мест дороже, чем закрыть причину одним правилом. */
@media (pointer: coarse) {
  input, textarea, select { font-size: 16px !important; }
}

/* ── Завеса скрытого фото ────────────────────────────────────────────────
   Рябь из фильтра SVG: без файлов и без единого запроса наружу — за картинкой
   шума пришлось бы ходить на чужой сервер, а мы этого не делаем нигде.
   Две накладки движутся с разной скоростью, поэтому под ними не читается
   силуэт: одна замершая сетка глазом «прочитывается» вместе с размытием. */
.spoiler-noise {
  position: absolute;
  inset: -30%;
  pointer-events: none;
  opacity: .55;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: spoilerDrift 7s linear infinite;
}
.spoiler-noise--slow {
  opacity: .35;
  animation-duration: 13s;
  animation-direction: reverse;
}
@keyframes spoilerDrift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-160px, -160px, 0); }
}
/* Кому движение мешает — рябь остаётся, но замирает. */
@media (prefers-reduced-motion: reduce) {
  .spoiler-noise { animation: none; }
}
