/* ===== 8-bit Claude Code vibe ===== */

@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400;500;700&family=Roboto+Mono:wght@400;500;700&family=IBM+Plex+Mono:wght@400;500;700&display=swap");

/* Liquid glass border: анимируемый CSS-угол для conic-gradient в .input-wrap */
@property --lqg-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

:root {
  --font-ui: "JetBrains Mono", "Menlo", "Monaco", "Consolas", monospace;
  --bg: #262624;
  --bg-soft: #1f1e1d;
  --bg-card: #30302e;
  --fg: #f0efe9;
  --fg-dim: #8e8d87;
  --accent: #d97757;       /* Claude warm coral */
  --accent-2: #e89a7c;
  --accent-dim: #6e6d68;   /* нейтральный серый вместо оранжевого в декоре */
  --accent-dim-2: #8e8d87;
  --green: #7fb87f;
  --red: #d96e6e;
  --border: #383735;
  --scroll-thumb: var(--fg-dim);
  --user-bg: #3a2a22;
  --user-fg: #f5d5c2;
  --code-bg: #1a1918;
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bot: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --tg-close-pad: 0px;
  --drawer-width: 280px;
}

:root.theme-light {
  --bg: #f5f3ee;
  --bg-soft: #ebe9e2;
  --bg-card: #ffffff;
  --fg: #2b2a28;
  --fg-dim: #6f6e6a;
  --accent: #c45f3e;
  --accent-2: #d97757;
  --accent-dim: #a8a6a0;
  --accent-dim-2: #6f6e6a;
  --green: #4f9e4f;
  --red: #c14545;
  --border: #d8d6d0;
  --scroll-thumb: #3a3935;
  --user-bg: #fbe5d8;
  --user-fg: #5a2e1a;
  --code-bg: #ebe9e2;
}

::selection {
  background: transparent;
  color: inherit;
}
::-moz-selection {
  background: transparent;
  color: inherit;
}

* { box-sizing: border-box; }
body:not(.maccode-native)::before {
  content: '';
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--safe-top);
  background: var(--bg);
  z-index: 9999;
  pointer-events: none;
}
body.maccode-native::before {
  content: '';
  position: fixed;
  top: 0; left: 0; right: 0;
  height: calc(var(--safe-top) + 8px);
  background: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.10) 0%,
    rgba(0, 0, 0, 0.03) 55%,
    rgba(0, 0, 0, 0) 100%
  );
  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 25%, rgba(0,0,0,0.45) 65%, rgba(0,0,0,0) 100%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 25%, rgba(0,0,0,0.45) 65%, rgba(0,0,0,0) 100%);
  z-index: 100;
  pointer-events: none;
}
body.maccode-native.image-viewer-open::before {
  display: none;
}
html, body {
  margin: 0; padding: 0;
  width: 100%;
  height: var(--vh, 100dvh);
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.45;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  overscroll-behavior: none;
  overflow: hidden;
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  touch-action: pan-x pan-y;
}
#app {
  display: flex; flex-direction: column;
  width: 100%;
  height: var(--vh, 100dvh);
  padding-top: var(--safe-top);
  padding-bottom: var(--safe-bot);
  padding-left: var(--safe-left);
  padding-right: var(--safe-right);
  position: relative;
}
body.maccode-native #app {
  padding-top: 0;
  padding-bottom: 0;
  height: 100vh;
}
/* С 1.66 (resize:none) WebView не схлопывается под клавиатуру, поднимаем #app
   через translate3d синхронно с iOS-анимацией клавы. --kb-h ставит tg-shim
   на keyboardWillShow/Hide. Кривая 240ms cubic-bezier(.4,0,.2,1) = стандарт
   iOS keyboard. На старых билдах (1.65-) этот класс не появляется. */
body.maccode-native.maccode-rn #app {
  transform: translate3d(0, calc(var(--kb-h, 0px) * -1), 0);
  transition: transform 240ms cubic-bezier(0.4, 0, 0.2, 1);
  will-change: transform;
}
/* В драйвере клавиатуру компенсирует сам драйвер (padding-bottom внутри
   скролла), а не translate всего #app — иначе #drawer (position:fixed под
   transformed parent) уезжает вверх и шапка с поиском улетает за верх
   экрана. */
body.maccode-native.maccode-rn.drawer-open #app {
  transform: none;
}
body.maccode-native.maccode-rn.drawer-open.kb-active .drawer-scroll,
body.maccode-native.maccode-rn.drawer-open.kb-active .drawer-home {
  padding-bottom: calc(var(--kb-h, 0px) + 8px);
  transition: padding-bottom 240ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* То же на странице рутин: глобальный transform #app задуман для composer'а
   в чате, на routines-page он гонит весь контент вверх при тапе в инпут
   названия. Отменяем transform, компенсируем низ паддингом — клавиатура
   просто перекрывает нижнюю часть списка, не двигая верх. */
body.maccode-native.maccode-rn.routines-open #app {
  transform: none;
}
body.maccode-native.maccode-rn.routines-open.kb-active #routines-scroll {
  padding-bottom: calc(var(--kb-h, 0px) + 20px);
  transition: padding-bottom 240ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* То же на странице «Техническое»: фокус в поле поиска не должен утаскивать
   шапку и сам инпут за верх. Отменяем translate, компенсируем низ скролла. */
body.maccode-native.maccode-rn.tech-open #app {
  transform: none;
}
body.maccode-native.maccode-rn.tech-open.kb-active #tech-scroll {
  padding-bottom: calc(var(--kb-h, 0px) + 20px);
  transition: padding-bottom 240ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* На стартовой странице (.start-page внутри #chat): тап в composer не должен
   утаскивать топбар и маскота вверх. Отменяем translate #app, вместо этого
   ужимаем высоту #app на --kb-h — composer естественно встаёт над клавой,
   а #chat (flex:1) ужимается тоже, маскот остаётся на месте. */
body.maccode-native.maccode-rn.start-shown #app {
  transform: none;
}
body.maccode-native.maccode-rn.start-shown.kb-active #app {
  height: calc(100vh - var(--kb-h, 0px));
  transition: height 240ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* Редактирование названия чата: input живёт прямо в хедере, поэтому при kb
   нельзя поднимать весь #app — хедер уедет за viewport вместе с input.
   Отменяем transform; клавиатура просто перекроет нижнюю часть чата. */
body.maccode-native.maccode-rn.editing-title #app {
  transform: none;
}
body.maccode-native #topbar {
  padding-top: calc(2px + var(--safe-top));
}
body.maccode-native #composer {
  padding-bottom: calc(40px + var(--safe-bot));
  /* Без transition: padding-bottom переключается мгновенно по .kb-active
     одновременно с нативным keyboardWillShow. Нативный resize WKWebView
     идёт своей iOS-кривой (spring), своя 240ms cubic-bezier на padding
     с ней не совпадала — давала визуальное «запаздывание» композера. */
}
body.maccode-native.kb-active #composer {
  /* 6px ставил маскот вплотную к клавиатуре — iOS WKWebView перехватывал
     tap в зоне над клавиатурой и кнопка отправки не отвечала, пока юзер
     не свернёт клаву. 20px даёт безопасный зазор. */
  padding-bottom: 20px;
  transform: none !important;
}
body.maccode-native.kb-active #chat {
  transform: none !important;
}
body.maccode-native #edge-handle { display: none !important; }

/* ===== Top bar ===== */
#topbar {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 8px 10px 8px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-card);
  position: sticky; top: 0; z-index: 10;
}
#topbar > #routines-btn { margin-right: 0; }
#topbar > #threads-btn,
#topbar > #routines-btn {
  width: 32px; height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  flex: 0 0 auto;
  position: relative;
}
.threads-btn__icon { display: inline-flex; line-height: 1; }
#threads-btn.has-badge .threads-btn__icon { display: none; }
.threads-btn__badge {
  position: absolute;
  inset: 0;
  min-width: 0;
  height: auto;
  padding: 0;
  box-sizing: border-box;
  background: transparent;
  color: var(--accent);
  font-size: 16px;
  font-weight: 800;
  line-height: 1;
  display: grid;
  place-items: center;
  text-align: center;
  border: 0;
  letter-spacing: 0;
  pointer-events: none;
  transform-origin: 50% 50%;
  will-change: transform;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
/* «1»…«9» — крупно. «10»–«99» — поменьше. «99+» — ещё меньше и тоньше:
   в круг 32×32 три глифа целиком не лезут. letter-spacing держим 0
   (tabular-nums + grid place-items центрует оптически одинаково
   для одно- и двузначных). */
.threads-btn__badge.len-2 { font-size: 14px; letter-spacing: 0; }
.threads-btn__badge.len-3 { font-size: 11px; font-weight: 700; letter-spacing: 0; }
.threads-btn__badge.busy-only {
  background: transparent;
  color: var(--fg-dim);
  opacity: 1;
  box-shadow: none;
  border-color: transparent;
}
.threads-btn__badge.hidden { display: none; }
/* Цвет цифры-бейджа непрочитанных = статус WS-связи. Источник правды один и
   тот же, что у дотов в «Диагностике связи» — _connWsStateKind() (ok/wait/err
   по state.ws.readyState). Цвета: всё ок — акцентный (коралл), подключение/
   авторизация — жёлтый (= цвет wait-дота _CONN_DOT_COLOR.wait в app.js),
   нет связи — красный. data-conn ставит setConn()/updateThreadsBtnBadge(). */
.threads-btn__badge[data-conn="ok"]   { color: var(--accent); }
.threads-btn__badge[data-conn="wait"] { color: #e0c34c; }
.threads-btn__badge[data-conn="err"]  { color: var(--red); }
/* busy-only (в непрочитанных только «думающие» треды, готового ничего нет)
   приглушает цвет — но лишь когда связь ок; при проблемах статус-цвет важнее. */
.threads-btn__badge.busy-only[data-conn="ok"] { color: var(--fg-dim); }
/* Появление: бейдж выезжает из 0 с лёгким overshoot — заметно, но не цирк. */
.threads-btn__badge.badge-pop {
  animation: threadsBadgePop 240ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Смена цифры: короткий «удар» — scale 1 → 1.3 → 1. */
.threads-btn__badge.badge-bump {
  animation: threadsBadgeBump 260ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes threadsBadgePop {
  0%   { transform: scale(0);   opacity: 0; }
  60%  { transform: scale(1.2); opacity: 1; }
  100% { transform: scale(1);   opacity: 1; }
}
@keyframes threadsBadgeBump {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.3); }
  100% { transform: scale(1); }
}
#topbar .logo {
  display: flex; align-items: center; justify-content: flex-start; gap: 0;
  font-weight: 700; letter-spacing: 1px;
  color: var(--fg);
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
}
/* Title — это <input readonly>: тапаем = открывается меню; long-press =
   readonly снимается и input становится редактируемым. По аналогии с
   .routine-title-input — без отдельного бара/попапа, без дублирования. */
#topbar .logo .logo-text {
  position: relative;
  display: flex; flex-direction: row; align-items: center; justify-content: flex-start;
  gap: 0;
  min-width: 0; flex: 1 1 auto;
  line-height: 1.1;
  min-height: 32px;
}
#topbar .logo .title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}
#topbar .logo .topbar-title-btn {
  appearance: none; -webkit-appearance: none;
  background: transparent;
  border: 1px solid transparent;
  padding: 2px 6px;
  margin: 0;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  width: 100%;
  max-width: 100%;
  height: 26px;
  outline: none;
  box-sizing: border-box;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
  caret-color: transparent;
}
#topbar .logo .topbar-title-btn::selection { background: transparent; }
#topbar .logo .topbar-title-btn.holding {
  background: rgba(217, 119, 87, 0.10);
  border-color: rgba(217, 119, 87, 0.35);
}
body.editing-title #topbar .logo .topbar-title-btn {
  background: rgba(217, 119, 87, 0.08);
  border-color: var(--accent);
  cursor: text;
  caret-color: var(--accent);
}
#topbar .logo .subtitle { display: none; }
#topbar .logo .cursor {
  color: var(--fg-dim);
  flex: 0 0 auto;
}

/* ===== Topbar dropdown menu (вызывается тапом по title) ===== */
#topbar { position: sticky; }
#topbar-menu {
  position: absolute;
  /* v1092: меню вынесено из <header> в #app (фикс блюра — см. index.html).
     Раньше top:100% от высоты шапки-родителя; теперь равняем верх меню на
     измеренный низ топбара (--topbar-menu-top ставит setupTopbarHeightVar). */
  top: calc(var(--topbar-menu-top, var(--topbar-h, 56px)) - 1px);
  left: 0;
  right: 0;
  width: auto;
  z-index: 100;
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border: 0;
  border-bottom: 1px solid var(--border);
  box-shadow: 0 6px 0 0 rgba(0,0,0,0.45);
  animation: topbarMenuOpen 220ms steps(6, end);
  will-change: clip-path, opacity;
}
#topbar-menu.hidden { display: none; }
#topbar-menu.closing {
  animation: topbarMenuClose 180ms steps(5, end) forwards;
  pointer-events: none;
}
@keyframes topbarMenuOpen {
  0%   { clip-path: inset(0 0 100% 0); opacity: 0.35; }
  100% { clip-path: inset(0 0 0 0);    opacity: 1; }
}
@keyframes topbarMenuClose {
  0%   { clip-path: inset(0 0 0 0);    opacity: 1; }
  100% { clip-path: inset(0 0 100% 0); opacity: 0.25; }
}
.topbar-menu-row {
  appearance: none; -webkit-appearance: none;
  background: var(--bg-card);
  color: var(--fg);
  display: flex;
  flex-direction: column;
  padding: 8px 12px;
  border: none;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  width: 100%;
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}
.topbar-menu-row:last-child { border-bottom: none; }
.topbar-menu-row:hover { background: rgba(217, 119, 87, 0.08); }
.topbar-menu-row:active { background: rgba(217, 119, 87, 0.18); }
.topbar-menu-row[disabled] {
  cursor: default;
  opacity: 0.55;
}
.topbar-menu-row-title {
  color: var(--accent);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.4px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.topbar-menu-row-desc {
  color: var(--fg-dim);
  font-size: 11px;
  margin-top: 3px;
  line-height: 1.35;
  font-family: inherit;
  word-break: break-all;
}
/* Для пункта «Название чата» desc — это сам заголовок, у него есть пробелы:
   рвём по словам, а не по буквам, и чуть крупнее — это главный пункт меню. */
.topbar-menu-row--title .topbar-menu-row-desc {
  font-size: 13px;
  color: var(--fg);
  word-break: normal;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.topbar-menu-row.is-busy .topbar-menu-row-title::after {
  content: "…";
  color: var(--fg-dim);
  margin-left: 6px;
  animation: blink 0.7s steps(2, start) infinite;
}

@keyframes blink { 50% { opacity: 0; } }
#topbar .status {
  margin-left: auto;
  flex: 0 0 auto;
  /* row-reverse — точка справа (визуально первая у правого края), текст слева от неё.
     Симметрично гамбургеру: тот сидит на 8px от левого края (padding-left топбара),
     лампочка — на 8px от правого края (padding-right топбара). Свой padding кнопки
     = 0 справа, чтобы точка прижалась прямо к границе контейнера. */
  flex-direction: row-reverse;
  display: flex; align-items: center; gap: 6px;
  font-size: 11px; color: var(--fg-dim);
  text-transform: uppercase;
  background: none; border: none; padding: 0 0 0 6px; cursor: pointer;
  letter-spacing: 0.5px;
  /* Без выделения текста при тапе по «ДУМАЮ»/«ЖДУ» — иначе iOS начинает text selection
     и click до листенера не доходит. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}
/* Дети не должны ловить события сами — все тапы идут на button. */
#topbar .status #conn-dot,
#topbar .status #conn-text { pointer-events: none; }
/* Online — спокойное состояние: тянем title-ом ширину, лампочка одна без надписи.
   Любое не-online состояние (thinking / waiting / connecting / offline) — текст
   возвращается рядом с точкой. */
#topbar .status[data-state="online"] #conn-text { display: none; }
#topbar .status[data-state="thinking"] #conn-text,
#topbar .status[data-state="waiting"] #conn-text { color: var(--accent); }
#topbar .status[data-state="offline"] #conn-text { color: var(--red); }
#topbar .status .dot {
  width: 8px; height: 8px; border-radius: 0;
  background: var(--red);
  box-shadow: 0 0 6px currentColor;
  flex: 0 0 auto;
}
#topbar .status .dot.on  { background: var(--green); }
#topbar .status .dot.off { background: var(--red); }
#topbar .status .dot.connecting { background: var(--accent); animation: blink 0.6s steps(2, start) infinite; }
#topbar .status .dot.thinking { background: var(--green); animation: blink 0.6s steps(2, start) infinite; }
/* waiting = сервер сказал busy, стрима ещё нет: полый квадрат, медленный пульс,
   без бегущей фразы — честный сигнал "ждём подтверждения". */
#topbar .status .dot.waiting {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--fg-dim);
  animation: waitingPulse 1.4s ease-in-out infinite;
}
@keyframes waitingPulse {
  0%, 100% { opacity: 0.4; }
  50%      { opacity: 0.95; }
}

/* ===== Buttons ===== */
.icon-btn {
  appearance: none; -webkit-appearance: none;
  background: var(--bg-card);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 0;
  padding: 6px 10px;
  font-family: inherit; font-size: 14px;
  cursor: pointer;
  transition: transform 0.05s ease, background 0.1s ease;
}
.icon-btn:active { transform: translateY(1px); }
.icon-btn.holding {
  background: var(--accent);
  color: #1a1306;
  border-color: var(--accent);
  animation: icon-btn-holding-pulse 0.45s ease-in-out infinite alternate;
  -webkit-touch-callout: none;
  user-select: none;
  -webkit-user-select: none;
}
@keyframes icon-btn-holding-pulse {
  from { box-shadow: 0 0 0 0 rgba(255, 184, 0, 0.0); }
  to   { box-shadow: 0 0 0 4px rgba(255, 184, 0, 0.35); }
}
.icon-btn.primary {
  background: var(--accent);
  color: #1a1306;
  border-color: var(--accent);
  font-weight: 800;
}
.icon-btn.primary:disabled {
  opacity: 0.5; cursor: not-allowed;
}

/* ===== Chat ===== */
#chat {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 12px 12px 18px 12px;
  scroll-padding-bottom: 18px;
  display: flex; flex-direction: column;
  gap: 10px;
  -webkit-user-select: text; user-select: text;
  /* v913: pull-up подъём через `position: relative` + `top`, а не
     через `margin-top`. Причина — у #chat есть `contain: layout style`
     и `flex: 1 1 auto`; в этом сочетании margin-top на iOS WKWebView
     визуально не сдвигает чат (расчёт flex поглощает margin), и
     спиннер лезет прямо на текст сообщений. `position: relative` +
     `top` — чистый paint-offset без перерасчёта layout соседей, и
     БЕЗ создания composite layer (как transform делал) — фантомов
     шапок не вызывает. */
  position: relative;
  /* v948 #8: транзишн замедлен с 0.26s до 0.55s + более мягкий ease.
     При soft-snapPull чат скатывался с -94px до 0 за 260мс резко —
     Кирилл воспринимал это как «прыжок высоты от текста до чипов».
     0.55s + decelerating ease делает возврат заметно мягче. */
  transition: top 0.55s cubic-bezier(0.22, 0.61, 0.36, 1);
  top: calc(var(--pull-progress, 0) * -110px);
  /* contain отрезает reflow chat-а от остального дерева: правка height
     у textarea в composer'е больше не пересчитывает layout 5к сообщений.
     v904: убран `paint` — на iOS WKWebView он держал stale-paint остатки
     прошлых сообщений. Layout/style контейнмент сохраняем. */
  contain: layout style;
  /* overflow-anchor (scroll-anchoring) на iOS/WebKit не поддерживается вовсе
     (caniuse) — позицию при мутациях высоты держим сами, в withChatScrollComp.
     На десктопных движках (Chrome/Firefox в браузерной мини-аппе) дефолтный
     auto дрался бы с нашей ручной компенсацией → дребезг; отключаем явно для
     единообразия поведения. */
  overflow-anchor: none;
  /* touch-action: pan-y — заявляем браузеру, что вертикальный скролл
     наш и единственный жест по этому контейнеру; это убирает попытки
     iOS предсказывать горизонтальные/диагональные жесты и подёргивать
     прокрутку, пока он определяет направление. */
  touch-action: pan-y;
  -webkit-overflow-scrolling: touch;
  /* iOS rubber-band: на краях скролла контент тянется вместе с пальцем.
     При pull-up reload это дублирует жест — двигается и текст, и плашка.
     contain отключает bounce у границ, но скролл внутри остаётся. */
  overscroll-behavior-y: contain;
}
body.kb-active #chat {
  transform: translate3d(0, calc(-1 * var(--kb, 0px)), 0);
}
body.kb-active #chat:has(.start-page) {
  transform: translateZ(0);
}
/* Telegram-style прижим: ::before — невидимый flex-item с flex:1, который
   поглощает всю свободную вертикаль и толкает реальные msg'и + #chat-thinking
   к низу #chat. В коротком треде: пустота сверху, сообщения у композёра
   (gap до композёра фиксированный, маскот не «плавает» на разной высоте).
   В длинном треде: ::before схлопывается до 0 (контент переполняет), скролл
   работает как обычно. На .start-page правило снято — она сама flex:1
   и центрируется justify-content: center; добавлять ещё один flex:1 нельзя,
   иначе start-page уедет вниз. */
#chat:not(:has(.start-page))::before {
  content: "";
  flex: 1 1 auto;
  min-height: 0;
}
/* v1065: во время активного «хода» (отправил → читаю ответ, GPT-стиль)
   гасим прижим — иначе он толкает моё сообщение к низу и не даёт уехать
   к верху. Низ при этом резервирует #chat-bottom-spacer. */
#chat.turn-active::before { flex: 0 0 0; min-height: 0; }
/* GPT turn-спейсер: пустое место под контентом, чтобы моё сообщение
   дотянулось к верху экрана. order:10000 — всегда последний flex-item,
   ниже #chat-thinking (order:9999). Высоту ставит JS (updateTurnSpacer). */
#chat-bottom-spacer {
  order: 10000;
  flex: 0 0 auto;
  width: 100%;
  pointer-events: none;
}
/* v1004: прячем нативный скроллбар (#chat-scroll-indicator его заменяет).
   Без этого на iOS видно ДВА бара — overlay-нативный во время жеста
   и наш кастомный JS-индикатор. */
#chat { scrollbar-width: none; }
#chat::-webkit-scrollbar { display: none; }

/* v1003: кастомный scroll-indicator справа от #chat. Нативный
   ::-webkit-scrollbar на iOS WKWebView — overlay-стиля: появляется
   только в момент gesture-скролла и сразу прячется, плюс нижняя его
   часть уезжает под композёр (см. padding-bottom у #chat). Этот
   индикатор живёт в body (position: fixed), позиционируется JS-ом
   относительно chatEl.getBoundingClientRect(), и не пересекает зону
   композёра. */
#chat-scroll-indicator {
  position: fixed;
  width: 3px;
  min-height: 24px;
  border-radius: 2px;
  background: var(--scroll-thumb);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
  z-index: 5;
  will-change: top, height;
}
#chat-scroll-indicator.visible { opacity: 0.85; }
:root.theme-glass #chat-scroll-indicator { background: rgba(255, 255, 255, 0.45); }

.msg {
  max-width: 92%;
  border: 1px solid var(--border);
  background: var(--bg-card);
  padding: 8px 10px;
  white-space: pre-wrap;
  word-wrap: break-word;
  word-break: break-word;
  border-radius: 0;
  box-shadow: 2px 2px 0 rgba(0,0,0,0.6);
  position: relative;
  touch-action: pan-y;
  -webkit-touch-callout: none;
  /* iOS: запрещаем нативное выделение текста в пузыре — иначе появляется
     эппловская лупа и контекстное меню Copy/Look Up/Translate поверх
     нашего openMessageMenu. Long-press висит на JS-таймере, не страдает.
     Копирование делает наш «Копировать» в меню. */
  -webkit-user-select: none;
  user-select: none;
  /* Гасим нативную iOS-вспышку при касании: «эффект нажатия» на сообщении должен
     быть ТОЛЬКО при длинном пуше (наш JS-визуал .lp-holding), не при коротком тапе. */
  -webkit-tap-highlight-color: transparent;
  /* content-visibility: auto + contain-intrinsic-size давали дребезг
     на iPhone: браузер держит для оффскрин-сообщений плейсхолдер 80px,
     при скролле сообщение «материализуется», реальная высота ≠ 80px,
     и scrollTop прыгает. Убираем — без этого тоже жить можно. */
  /* v907: НЕ создаём GPU-layer per .msg, и НЕ заводим свой stacking
     context. Любая такая промоция на iOS WKWebView усугубляет фантомы
     шапок ушедших сообщений (имя/время как .role-label/.msg-time
     остаются paint'ом над верхним краем .msg). */
}
.msg * { touch-action: pan-y; }
/* Длинный пуш по сообщению (attachMsgLongPress). Короткий тап эффекта НЕ даёт —
   визуал стартует только после START_DELAY. Дальше прогрессивно, ровно как
   зажатие кнопок-вариантов .qr-btn: rAF гонит --hp 0→1, сообщение обрастает
   accent-подсветкой (outline) и слегка вдавливается (scale), на 100% — снап
   lpSnap. Тема-независимо: трогаем только outline/transform, базовую
   box-shadow/border не перебиваем. */
.msg.lp-holding {
  outline: calc(var(--hp, 0) * 2.5px) solid var(--accent);
  outline-offset: 0;
  transform: scale(calc(1 - var(--hp, 0) * 0.02));
  transform-origin: center;
  z-index: 2;
}
.msg.lp-releasing {
  outline: 0 solid var(--accent);
  transform: none;
  transition: outline-width 240ms cubic-bezier(0.2, 0, 0, 1),
              transform 240ms cubic-bezier(0.2, 0, 0, 1);
}
.msg.lp-snap {
  animation: lpSnap 0.34s cubic-bezier(0.34, 1.56, 0.64, 1) both;
  z-index: 2;
}
@keyframes lpSnap {
  0% { transform: scale(0.98); }
  45% { transform: scale(1.015); }
  100% { transform: scale(1); }
}
/* Glass: пресс-подсветка длинного пуша должна повторять скругление пузыря.
   CSS outline рисует ПРЯМОУГОЛЬНИК (острые углы) даже на rounded .msg — в
   стекле это чужеродно. Меняем outline на box-shadow-кольцо (оно следует за
   border-radius) и скругляем элемент на время удержания/возврата. */
:root.theme-glass .msg.lp-holding,
:root.theme-glass .msg.lp-releasing {
  outline: none;
  border-radius: 14px;
}
:root.theme-glass .msg.user.lp-holding,
:root.theme-glass .msg.user.lp-releasing {
  border-radius: 18px;
}
:root.theme-glass .msg.lp-holding {
  box-shadow: 0 0 0 calc(var(--hp, 0) * 2.5px) var(--accent);
}
:root.theme-glass .msg.lp-releasing {
  box-shadow: 0 0 0 0 var(--accent);
  transition: box-shadow 240ms cubic-bezier(0.2, 0, 0, 1),
              transform 240ms cubic-bezier(0.2, 0, 0, 1);
}
.msg.swipe-snap {
  transition: transform 180ms ease-out;
}
.msg.swipe-armed {
  border-color: var(--accent-dim);
}
.msg.swipe-armed::after {
  content: "↩";
  position: absolute;
  left: 100%;
  margin-left: 14px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--accent);
  font-size: 22px;
  font-weight: bold;
  white-space: nowrap;
  pointer-events: none;
  line-height: 1;
}
/* В glass-темах (и в light-, и в dark-варианте, и в minimal-подтеме) — заменяем
   эмодзи на минималистичную линейную стрелку-обратку. */
:root.theme-glass .msg.swipe-armed::after {
  content: "";
  width: 22px;
  height: 22px;
  margin-left: 14px;
  background-color: var(--accent);
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M9 14 4 9l5-5'/><path d='M4 9h10a6 6 0 0 1 0 12h-3'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M9 14 4 9l5-5'/><path d='M4 9h10a6 6 0 0 1 0 12h-3'/></svg>");
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain; mask-size: contain;
}
.msg.user {
  align-self: flex-end;
  background: var(--user-bg);
  color: var(--user-fg);
  border-color: #4a2810;
}
.msg.bot {
  align-self: flex-start;
  border-left: 3px solid var(--accent);
  /* v1161: фикс-ширина как у tool-плашки (.msg.tool — 92%), чтобы время встало
     стабильно справа («выровнять по зелёному»), а не скакало с длиной ответа. */
  width: 92%;
  max-width: 92%;
  box-sizing: border-box;
}
.msg.bot.typing {
  border: 1px solid var(--accent);
  border-left: 3px solid var(--accent);
  box-shadow: 2px 2px 0 rgba(255, 138, 61, 0.25);
  animation: botTypingPulse 1.6s ease-in-out infinite;
}
@keyframes botTypingPulse {
  0%, 100% { box-shadow: 2px 2px 0 rgba(255, 138, 61, 0.18); }
  50%      { box-shadow: 2px 2px 0 rgba(255, 138, 61, 0.45); }
}
/* Блок размышлений — чистый приглушённый вид как «Thought process» в офиц.
   приложении Клода: без курсива, без жирной карточки/тени/обводки, лёгкая
   утопленная подложка (--bg-soft) и приглушённый текст. Заголовок-шеврон
   (свернуть/развернуть) и плавный body для glass — отдельными правилами ниже. */
.msg.thinking {
  align-self: flex-start;
  border: none;
  border-radius: 0;
  background: var(--bg-soft);
  box-shadow: none;
  color: var(--fg-dim);
  padding: 8px 10px;
  line-height: 1.5;
}
.msg.thinking .role.clickable {
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
}
.msg.thinking .role-chev {
  flex: 0 0 auto;
  color: var(--fg-dim);
  font-size: 11px;
  margin-left: 4px;
  transition: transform 0.15s steps(2);
  display: inline-block;
}
.msg.thinking.collapsed .role-chev {
  transform: rotate(-90deg);
}
.msg.thinking.collapsed .body {
  display: none;
}
.msg.tool {
  align-self: flex-start;
  width: 92%;
  max-width: 92%;
  box-sizing: border-box;
  border-left: 3px solid var(--green);
  color: var(--green);
  font-size: 12px;
  background: var(--code-bg);
  /* v1151: левый паддинг 8→10 — текст плашки (EDIT/BASH…) встаёт на тот же
     левый край, что и надпись бота MC/МАККОД. У .msg.bot: border-left 3 +
     padding-left 10 = 13px; тут было border-left 3 + 8 = 11px (на 2px левее).
     Кирилл: «источником правды — надпись MC, выравниваем по ней». */
  padding: 4px 8px 4px 10px;
}
.msg.tool .role {
  margin-bottom: 2px;
  flex-wrap: nowrap;
  min-width: 0;
}
.msg.tool .role > .role-label { min-width: 0; flex-shrink: 1; }
.msg.tool .role.clickable {
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
}
.msg.tool .role-chev {
  flex: 0 0 auto;
  color: var(--green);
  font-size: 11px;
  margin-left: 4px;
  transition: transform 0.15s steps(2);
  display: inline-block;
}
.tool-group {
  display: contents;
}
.tool-group:not(.expanded) > .msg.tool .role-chev {
  transform: rotate(-90deg);
}
.tool-group:not(.expanded) > .msg.tool:not(:last-child) {
  display: none;
}
.tool-group.single > .msg.tool .role-chev,
.tool-group.single > .msg.tool .session-badge {
  display: none;
}
.tool-count-badge {
  order: 2;
  margin-left: auto;
  margin-right: -3px;
  color: var(--green);
  font-size: 10px;
  font-weight: bold;
  letter-spacing: 0.04em;
  flex-shrink: 0;
  white-space: nowrap;
}
.tool-group:not(.expanded):not(.single) > .msg.tool .role .tool-count-badge + .role-chev {
  margin-left: 0;
}
@keyframes toolBadgePulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}
.msg.tool.tool-current .body {
  animation: toolBadgePulse 1.1s ease-in-out infinite;
}
/* v1031: активный tool — заметная пульсирующая акцентная грань слева + свечение,
   чтобы «работа идёт / вычисления» читалось с одного взгляда. Раньше единственным
   сигналом был opacity-пульс текста .body — на длинных тулзах он терялся. Псевдо-
   элемент: не трогает layout и не конфликтует с entry-анимацией .msg-new.
   left:-3px накрывает статичный green border-left, при пульсе светится акцентом. */
.msg.tool.tool-current::before {
  content: "";
  position: absolute;
  left: -3px;
  top: -1px;
  bottom: -1px;
  width: 3px;
  background: var(--accent);
  box-shadow: 0 0 8px 1px rgba(217, 119, 87, 0.55);
  border-radius: 1px;
  pointer-events: none;
  transform-origin: center;
  animation: toolEdgePulse 1.1s ease-in-out infinite;
}
@keyframes toolEdgePulse {
  0%, 100% { opacity: 0.4; transform: scaleY(0.55); }
  50%      { opacity: 1;   transform: scaleY(1); }
}
/* v1103: список шагов в попапе тул-группы (#modal). Строки — как источники в
   свёрнутом блоке офиц. Claude: человечная надпись крупно + деталь мелко. */
.tool-steps-list { display: flex; flex-direction: column; }
.tool-step-row {
  padding: 10px 2px;
  border-bottom: 1px solid var(--border);
}
.tool-step-row:last-child { border-bottom: none; }
.tool-step-label {
  color: var(--fg);
  font-size: 14px;
  font-weight: 600;
}
/* v1156: бейдж «× N» у схлопнутых повторов одного шага. */
.tool-step-count {
  margin-left: 6px;
  color: var(--fg-dim);
  font-size: 12px;
  font-weight: 600;
}
.tool-step-detail {
  margin-top: 3px;
  color: var(--fg-dim);
  font-size: 11px;
  white-space: pre-wrap;
  word-break: break-word;
  line-height: 1.35;
}
/* v1165: миниатюра превью Read-картинки в строке попапа «что делаю»
   (Кирилл: «не показывай картинку в ленте, но превью должно быть в попапе»). */
.tool-step-row.has-thumb { display: flex; align-items: center; gap: 10px; }
.tool-step-text { flex: 1 1 auto; min-width: 0; }
.tool-step-thumb-wrap {
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  cursor: zoom-in;
}
.tool-step-thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transition: opacity 0.2s ease;
}
.tool-step-thumb.loaded { opacity: 1; }
/* v1150: строки попапа кликабельны — тап копирует действие в буфер. .copied —
   подтверждающая вспышка «скопировано» справа сверху. */
.tool-step-row.clickable {
  cursor: pointer;
  position: relative;
  -webkit-tap-highlight-color: transparent;
  border-radius: 6px;
  transition: background 0.15s ease;
}
.tool-step-row.clickable:active { background: color-mix(in srgb, var(--fg) 8%, transparent); }
/* v1151: подтверждение копирования. Раньше был ГОЛЫЙ акцентный текст — на сером
   и на стеклянном фоне он «уходил в серость» (Кирилл: «уродливо, надо заливать
   фон — белым или акцентом»). Теперь это залитый бейдж: акцентная заливка +
   белый текст с тенью (читается на любой теме) + галка ✓, и сама строка коротко
   подсвечивается акцентной заливкой — чёткий визуальный «скопировано». */
.tool-step-row.copied {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}
.tool-step-row.copied::after {
  content: "✓ скопировано";
  position: absolute;
  top: 6px;
  right: 4px;
  padding: 3px 9px;
  border-radius: 6px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: none;
  white-space: nowrap;
  background: var(--accent);
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.30);
  animation: toolCopyPop 1.2s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
  pointer-events: none;
}
@keyframes toolCopyPop {
  0%   { opacity: 0; transform: translateY(-3px) scale(0.82); }
  16%  { opacity: 1; transform: translateY(0) scale(1); }
  76%  { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(-2px) scale(0.97); }
}
@keyframes toolBodyWipe {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}
.msg.tool.tool-wipe .body {
  animation: toolBodyWipe 0.96s ease-out both;
}
.tool-group.expanded .tool-count-badge,
.tool-group.single .tool-count-badge {
  display: none;
}
.msg.tool .body {
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
  cursor: pointer;
  white-space: pre-wrap;
  word-break: break-word;
}
.msg.tool.expanded .body {
  display: block;
  -webkit-line-clamp: unset;
  overflow: visible;
}
/* v1150: техническую зелёную строку тулза (деталь/путь команды) в самом чате
   убираем — Кирилл: «убери техническую строку зеленую». Текст остаётся в DOM
   (попап «что делаю» читает .body.textContent) и доступен тапом по плашке. */
.msg.tool > .body,
.msg.tool.expanded > .body { display: none; }
.msg.tool .tool-image-row { margin-top: 6px; }
.msg.system {
  align-self: center;
  text-align: center;
  font-size: 11px;
  color: var(--fg-dim);
  border: none;
  background: transparent;
  text-transform: uppercase;
  letter-spacing: 1px;
  box-shadow: none;
}
.chat-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 8px;
  font-size: 10px;
  letter-spacing: 2px;
  color: var(--fg-dim);
  text-transform: uppercase;
}
.chat-divider-line {
  flex: 1 1 auto;
  height: 0;
  border-top: 1px dashed var(--border);
}
.chat-divider-label {
  flex-shrink: 0;
  font-family: var(--font-ui);
}
.chat-divider[data-reason="compact"] .chat-divider-label {
  color: var(--accent, #ffb454);
}

/* Pending-state: «сжимаю контекст…» с шиммером и мигающими точками */
.chat-divider.is-pending .chat-divider-label {
  color: var(--accent, #ffb454);
  animation: uc-pulse-fade 1.6s ease-in-out infinite;
}
.chat-divider.is-pending .chat-divider-line {
  border-top: 0;
  height: 1px;
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    var(--accent, #ffb454) 50%,
    transparent 100%
  );
  background-size: 200% 100%;
  background-position: 100% 0;
  animation: uc-shimmer 1.6s linear infinite;
  opacity: 0.55;
}
.chat-divider.is-pending .uc-d {
  display: inline-block;
  animation: uc-blink 1.2s ease-in-out infinite;
}
.chat-divider.is-pending .uc-d:nth-child(2) { animation-delay: 0.15s; }
.chat-divider.is-pending .uc-d:nth-child(3) { animation-delay: 0.30s; }
@keyframes uc-pulse-fade {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}
@keyframes uc-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}
@keyframes uc-blink {
  0%, 100% { opacity: 0.25; }
  50%      { opacity: 1; }
}
.msg.error {
  align-self: stretch;
  color: var(--red);
  border-color: var(--red);
  border-left: 3px solid var(--red);
}

.msg .role {
  display: flex;
  align-items: baseline;
  justify-content: flex-start;
  gap: 6px;
  font-size: 10px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin-bottom: 4px;
}
.msg .role-label {
  flex: 0 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  order: 1;
}
.msg .role-chev {
  order: 2;
}
.msg .msg-time {
  flex: 0 0 auto;
  font-size: 9px;
  color: var(--fg-dim);
  opacity: 0.7;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  text-transform: none;
  order: 3;
  margin-left: auto;
}
.msg.tool .role .role-chev,
.msg.thinking .role .role-chev {
  margin-left: auto;
}
.msg.tool .role .msg-time,
.msg.thinking .role .msg-time {
  margin-left: 0;
}
/* msg-time в tool-карточке — тот же зелёный, что и +N и сама карточка.
   Это технический штамп («вчера / 23:18»), визуально подвязан к +N — должен
   читаться одной группой, а не серым из общего .msg .msg-time. */
.msg.tool .role .msg-time {
  color: var(--green);
  opacity: 1;
}
/* Когда шеврон скрыт (одиночный тул-группа) — время уезжает вправо само. */
.tool-group.single > .msg.tool .role .msg-time {
  margin-left: auto;
}
/* v1153: надпись плашки команды (.role-label: «Запускаю команду в терминале» и т.п.)
   — зелёным, в тон зелёному времени и грани слева. Раньше лейбл наследовал серый
   из общего `.msg .role`, а зелёными были только время и +N. Кирилл: «у команд и
   время, и саму команду — зелёным, как сейчас время написано». */
.msg.tool .role .role-label {
  color: var(--green);
}
/* v1161: время bot-сообщений — акцентом (как имя «Маккод») и прижато вправо
   margin-left:auto, как у зелёной tool-плашки. Раньше клеили влево, чтобы не
   скакало с длиной ответа; теперь .msg.bot фикс-ширины 92% (как .msg.tool), край
   стабилен — время держится у правого края ровно как у команд. Кирилл: «у маккода
   время выровни по зелёному». */
.msg.bot .role .msg-time {
  color: var(--accent);
  opacity: 1;
  margin-left: auto;
}

/* В расхлопнутом тул-группе только не-первые становятся уже — голова и одиночки 92%. */
.tool-group.expanded > .msg.tool:not(:first-child) {
  width: 78%;
  max-width: 78%;
}

/* Раскрытие списка тул-баблов — плавное появление вложенных. */
@keyframes toolReveal {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.tool-group.expanded > .msg.tool:not(:first-child) {
  animation: toolReveal 0.22s ease-out both;
}
.msg .session-badge {
  flex: 0 0 auto;
  margin-left: auto;
  margin-right: 6px;
  width: 18px;
  height: 18px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--green, var(--accent));
  background: var(--bg-card);
  border: 1px solid var(--green, var(--accent));
  border-radius: 0;
  opacity: 0.75;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  appearance: none;
  -webkit-appearance: none;
  transition: transform 0.05s ease, background 0.1s ease, opacity 0.1s ease;
  box-sizing: border-box;
}
.msg .session-badge svg {
  width: 12px;
  height: 12px;
  display: block;
}
.msg .session-badge:hover {
  opacity: 1;
  background: rgba(40, 200, 80, 0.18);
}
.msg .session-badge:active {
  opacity: 1;
  background: rgba(40, 200, 80, 0.28);
  transform: translateY(1px);
}
.msg.tool > .session-path {
  margin: 0 0 6px 0;
  padding: 4px 6px;
  font-size: 10px;
  font-family: var(--font-ui);
  color: var(--green, var(--accent));
  background: var(--bg-card);
  border: 1px solid var(--green, var(--accent));
  word-break: break-all;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  cursor: pointer;
  letter-spacing: 0;
  text-transform: none;
  opacity: 0.85;
}
.msg.tool > .session-path:active {
  opacity: 1;
  background: rgba(40, 200, 80, 0.18);
}
.msg.user .role { color: var(--accent-2); }
/* v1153: надпись «Маккод» и её время — акцентом (раньше лейбл был тускло-серый
   accent-dim-2). Кирилл: «время у сообщений Маккода — таким же цветом как написано
   Маккод, то есть акцент». Имя+время читаются одним акцентным блоком; время
   красится отдельным правилом ниже (общий .msg .msg-time иначе перебьёт серым). */
.msg.bot  .role { color: var(--accent); }

/* ===== Thinking phrase (анимированная "крутилка фразочек") ===== */
.msg.thinking-phrase {
  align-self: flex-start;
  border: 1px solid var(--accent);
  border-left: 3px dashed var(--accent);
  color: var(--accent-2);
  font-style: italic;
  background: var(--bg-soft);
  box-shadow: 2px 2px 0 rgba(255,138,61,0.25);
  transform-origin: 0 100%;
  animation: phraseSpawn 0.34s ease-out;
}
@keyframes phraseSpawn {
  0%   { opacity: 0; transform: translate(-6px, 8px) scale(0.4); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: translate(0, 0) scale(1); }
}
.msg.thinking-phrase .role { color: var(--accent); }
.msg.thinking-phrase .phrase {
  display: inline-block;
  position: relative;
  white-space: nowrap;
}
.msg.thinking-phrase .phrase-text::after {
  content: "▮";
  margin-left: 2px;
  color: var(--accent);
  animation: blink 0.8s steps(2, start) infinite;
}
.msg.thinking-phrase .phrase.swap {
  animation: phraseSwap 0.96s steps(4, end);
}
@keyframes phraseSwap {
  0%   { opacity: 0; transform: translateX(-3px); filter: blur(0.5px); }
  60%  { opacity: 0.6; }
  100% { opacity: 1; transform: translateX(0); filter: none; }
}

.msg pre {
  background: var(--code-bg);
  border: 1px solid var(--border);
  padding: 6px 8px;
  margin: 4px 0;
  overflow-x: auto;
  font-size: 12px;
  white-space: pre;
}
.msg code {
  background: var(--code-bg);
  padding: 1px 4px;
  border: 1px solid var(--border);
}

.msg .body pre.codeblock {
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-left: 2px solid var(--green);
  padding: 8px 10px;
  margin: 6px 0;
  overflow-x: auto;
  font-size: 12px;
  white-space: pre;
  color: var(--green);
}
.msg .body pre.codeblock code {
  background: transparent;
  border: 0;
  padding: 0;
  color: inherit;
  font: inherit;
}
.msg .body code.inline {
  background: var(--code-bg);
  border: 1px solid transparent;
  padding: 0 4px;
  color: var(--green);
  border-radius: 3px;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.msg .body pre.codeblock { cursor: pointer; }
.msg .body img.inline-img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 6px 0;
  border: 1px solid var(--border);
  border-radius: 0;
  cursor: zoom-in;
  background: linear-gradient(110deg, var(--code-bg) 0%, var(--bg-card) 40%, var(--code-bg) 80%);
  background-size: 200% 100%;
  animation: imgSkeleton 1.4s ease-in-out infinite;
  opacity: 0.35;
  transition: opacity 0.35s ease;
  min-height: 120px;
}
.msg .body img.inline-img.img-loaded {
  background: var(--code-bg);
  animation: none;
  opacity: 1;
}
.msg .body img.inline-img.img-error {
  background: var(--code-bg);
  animation: none;
  opacity: 0.5;
}
@keyframes imgSkeleton {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.msg .body .img-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  margin: 6px 0;
}
.msg .body .img-grid img.inline-img {
  margin: 0;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 0;
  min-height: 0;
}
.msg .body video.inline-vid {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 6px 0;
  border-radius: 0;
  background: #000;
}
.msg .body .media-carousel {
  position: relative;
  margin: 6px 0;
}
.msg .body .media-carousel-track {
  display: flex;
  gap: 0;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  border-radius: 0;
  scrollbar-width: none;
}
.msg .body .media-carousel-track::-webkit-scrollbar { display: none; }
.msg .body .media-carousel-track .cslide {
  flex: 0 0 100%;
  width: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--code-bg);
  border-radius: 0;
  overflow: hidden;
}
.msg .body .media-carousel-track .cslide img.inline-img,
.msg .body .media-carousel-track .cslide video.inline-vid {
  margin: 0;
  width: 100%;
  height: auto;
  max-height: 70vh;
  object-fit: contain;
  border-radius: 0;
  min-height: 0;
  animation: none;
  opacity: 1;
  background: transparent;
}
.msg .body .media-carousel-track .cslide .pixel-player {
  margin: 0;
  width: 100%;
  max-height: 70vh;
}
.msg .body .media-carousel-dots {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin-top: 6px;
}
.msg .body .media-carousel-dots .cdot {
  width: 8px;
  height: 8px;
  border-radius: 0;
  border: none;
  background: var(--border);
  padding: 0;
  cursor: pointer;
  opacity: 0.6;
  image-rendering: pixelated;
  transition: opacity 0.15s linear, background 0.15s linear, transform 0.15s linear;
}
.msg .body .media-carousel-dots .cdot.active {
  background: var(--accent);
  opacity: 1;
  transform: scale(1.4);
}
.msg .body .media-carousel .ccount {
  position: absolute;
  top: 6px;
  right: 8px;
  font-size: 11px;
  padding: 2px 7px;
  border-radius: 0;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-variant-numeric: tabular-nums;
  font-family: var(--font-ui);
  pointer-events: none;
  user-select: none;
}

.msg .body .pixel-player {
  position: relative;
  display: block;
  width: 100%;
  margin: 6px 0;
  background: #000;
  border: 1px solid var(--border);
  font-family: var(--font-ui);
  -webkit-tap-highlight-color: transparent;
  image-rendering: pixelated;
}
.msg .body .pixel-player video.inline-vid {
  display: block;
  width: 100%;
  height: auto;
  max-height: 70vh;
  margin: 0;
  border: none;
  border-radius: 0;
  background: #000;
  object-fit: contain;
}
.msg .body .pixel-player .pp-shade {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.35);
  cursor: pointer;
  transition: opacity 0.15s linear;
}
.msg .body .pixel-player.playing .pp-shade { opacity: 0; pointer-events: none; }
.msg .body .pixel-player .pp-play {
  width: 22px;
  height: 22px;
  display: block;
  color: #fff;
  filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.55));
  image-rendering: pixelated;
}
.msg .body .pixel-player .pp-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.75) 100%);
  color: #fff;
  font-size: 11px;
  line-height: 1;
  opacity: 0;
  transition: opacity 0.15s linear;
  pointer-events: none;
}
.msg .body .pixel-player:hover .pp-bar,
.msg .body .pixel-player.show-bar .pp-bar {
  opacity: 1;
  pointer-events: auto;
}
.msg .body .pixel-player .pp-btn {
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: #fff;
  color: #000;
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
  image-rendering: pixelated;
}
.msg .body .pixel-player .pp-btn:hover { background: #e6e6e6; }
.msg .body .pixel-player .pp-btn:active { background: #d0d0d0; }
.msg .body .pixel-player .pp-btn svg { width: 12px; height: 12px; display: block; }
.msg .body .pixel-player .pp-ico { display: inline-flex; align-items: center; justify-content: center; line-height: 0; }
.msg .body .pixel-player .pp-ico.glass-icon { display: none; }
.msg .body .pixel-player .pp-ico svg { display: block; }
.msg .body .pixel-player .pp-time {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  letter-spacing: 0.5px;
  flex-shrink: 0;
}
.msg .body .pixel-player .pp-track {
  position: relative;
  flex: 1;
  height: 8px;
  background: rgba(255,255,255,0.18);
  cursor: pointer;
  image-rendering: pixelated;
}
.msg .body .pixel-player .pp-track .pp-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0%;
  background: #fff;
}
.msg .body code.inline.copied,
.msg .body pre.codeblock.copied {
  background: rgba(40, 200, 80, 0.22);
  border-color: var(--green);
}
.msg .body a.link {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  word-break: break-all;
}
.msg .body a.link:hover { filter: brightness(1.15); }

.msg .body strong { font-weight: 700; color: inherit; }
.msg .body em { font-style: italic; color: inherit; }

.msg .body .md-list {
  margin: 6px 0;
  padding-left: 22px;
  list-style: disc outside;
}
.msg .body .md-list .md-list {
  margin: 2px 0;
  padding-left: 18px;
  list-style: circle outside;
}
.msg .body .md-list li {
  margin: 3px 0;
  line-height: 1.5;
  padding-left: 2px;
}
.msg .body .md-list li::marker {
  color: var(--muted, #888);
}

.msg .body .md-table-wrap {
  margin: 8px 0;
  overflow-x: auto;
  border: 1px solid var(--border);
  background: var(--bg-card);
  -webkit-overflow-scrolling: touch;
}
.msg .body .md-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
  line-height: 1.4;
  white-space: normal;
  color: inherit;
}
.msg .body .md-table th,
.msg .body .md-table td {
  padding: 6px 8px;
  border: 1px solid var(--border);
  vertical-align: top;
  word-break: break-word;
  overflow-wrap: anywhere;
}
.msg .body .md-table th {
  background: rgba(255, 255, 255, 0.04);
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}
.msg .body .md-table tbody tr:nth-child(odd) td {
  background: rgba(255, 255, 255, 0.015);
}

.msg .body code.inline.path-link {
  color: var(--accent);
  border-color: transparent;
  text-decoration: none;
  cursor: pointer;
}
body.no-code-copy .msg .body code.inline.path-link {
  color: var(--accent);
  cursor: pointer;
}
.msg.user .body a.link { color: var(--accent); }
.msg.user .body code.inline,
.msg.user .body pre.codeblock { color: var(--green); }

body.no-code-copy .msg .body code.inline,
body.no-code-copy .msg .body pre.codeblock,
body.no-code-copy .msg .body pre.codeblock code,
body.no-code-copy .msg.user .body code.inline,
body.no-code-copy .msg.user .body pre.codeblock {
  color: inherit;
  cursor: text;
}
body.no-code-copy .msg .body pre.codeblock { border-left-color: var(--border); }
body.no-code-copy .msg .body code.inline.copied,
body.no-code-copy .msg .body pre.codeblock.copied {
  background: var(--code-bg);
  border-color: var(--border);
}

.msg .body .quick-replies {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  margin: 8px 0 2px;
}
.msg .body .quick-replies .qr-btn {
  font: inherit;
  font-size: 13px;
  line-height: 1.2;
  padding: 6px 12px;
  border-radius: 0;
  border: 1px solid var(--accent);
  background: transparent;
  color: var(--accent);
  cursor: pointer;
  text-align: left;
  white-space: normal;
  word-break: break-word;
  max-width: 100%;
  align-self: flex-start;
  -webkit-tap-highlight-color: transparent;
  /* iOS: кнопка внутри #chat, у которого user-select:text (для копирования
     текста сообщений). Без явной глушилки удержание (hold-to-confirm) iOS
     трактует как выделение текста и показывает callout-меню поверх, сбивая
     hold. Глушим выделение и callout именно на кнопке. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  transition: background 0.15s ease, color 0.15s ease, transform 0.08s ease;
}
@media (hover: hover) and (pointer: fine) {
  .msg .body .quick-replies .qr-btn:hover,
  .msg .body .quick-replies .qr-btn:active {
    background: var(--accent);
    color: var(--bg);
  }
  .msg .body .quick-replies .qr-btn:active { transform: scale(0.97); }
}
.msg .body .quick-replies .qr-btn.qr-pressed {
  background: var(--accent);
  color: var(--bg);
  transform: scale(0.97);
}
.msg .body .quick-replies.qr-used .qr-btn {
  opacity: 0.45;
  pointer-events: none;
  cursor: default;
}

/* v1007: hold-to-confirm. Кирилл часто мискликает qr-btn пальцем — теперь
   нужно ЗАЖАТЬ кнопку ~0.9s (rAF-прогресс ставит --hp 0→1), параллельно
   ускоряются хаптик-тики (см. startQrHold в app.js). На 100% — снап-вспышка
   (класс qr-snap), потом действие. Если отпустить раньше — fade-out 150ms. */
/* Собственный --hp: 0 рвёт наследование от родителя .msg. Иначе msg-long-press
   (.msg.lp-holding ставит inline --hp на весь .msg) протекал в дочерние qr-кнопки
   через CSS-наследование, и их ::after-заливка «нажималась» синхронно с зажатием
   тела сообщения. Настоящий qr-hold ставит inline --hp на саму кнопку → перебивает
   этот 0 (inline > stylesheet), так что hold-заливка работает как прежде. */
.msg .body .quick-replies .qr-btn { position: relative; overflow: hidden; --hp: 0; }
.msg .body .quick-replies .qr-btn::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--accent);
  pointer-events: none;
  transform-origin: left center;
  transform: scaleX(var(--hp, 0));
  opacity: calc(0.18 + var(--hp, 0) * 0.55);
  transition: transform 0.14s linear, opacity 0.14s linear;
  z-index: 0;
}
.msg .body .quick-replies .qr-btn.qr-holding {
  transform: scale(1.015);
}
.msg .body .quick-replies .qr-btn.qr-holding::after {
  transition: transform 60ms linear, opacity 60ms linear;
}
.msg .body .quick-replies .qr-btn > * { position: relative; z-index: 1; }
.msg .body .quick-replies .qr-btn.qr-snap {
  animation: qrSnap 0.34s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes qrSnap {
  0%   { transform: scale(1.02); }
  35%  { transform: scale(0.88); }
  100% { transform: scale(1); }
}

.typing::after { content: none; }

/* ===== Composer ===== */
#composer {
  border-top: 1px solid var(--border);
  background: var(--bg-card);
  padding: 6px 8px 18px 8px;
  display: flex; flex-direction: column;
  gap: 6px;
  position: relative;
  /* z-index 5: композёр+чипы стоят НАД pull-spinner (z-index 1). Спиннер
     выезжает «из-под» композёра — нижняя половина траектории визуально
     скрыта чипами/полем ввода, видим только то, что поднялось выше них. */
  z-index: 5;
  transition: transform 0.26s cubic-bezier(0.32, 0.72, 0, 1);
}
/* Pull-to-reload жест: при тяге снизу-вверх (когда чат уже в самом низу)
   #chat едет вверх на --pull-progress * -90px, и параллельно из safe-area
   под home indicator выезжает круглый спиннер с подписью. Оба двигаются
   синхронно за пальцем — пользователь визуально «вытягивает» спиннер
   из-под нижней кромки одновременно с подъёмом чата.
   На время жеста корень получает .pull-active — он отключает transition
   у #chat, чтобы transform шёл за пальцем 60fps без визуального лагa.
   На отпускание класс снимается → возврат идёт через transition.
   Два порога:
   soft (>=PULL_SOFT) — мягкий ресет (закрываем WS → реконнект, мини-апп
     пере-натянет state без полного reload страницы);
   hard (>=PULL_HARD) — полная перезагрузка страницы (location.reload).
   Подпись меняется по достигнутому порогу. */
:root.pull-active #chat {
  transition: none;
}
.pull-spinner {
  position: fixed;
  left: 50%;
  bottom: calc(var(--safe-bot, 0px) + 6px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  pointer-events: none;
  opacity: 0;
  transform: translate3d(-50%, 40px, 0);
  color: var(--muted, #888);
  /* z-index 1: НИЖЕ композёра (z-index 5 classic / 10 glass) — спиннер
     визуально вылазит из-под чипов/поля ввода, а не парит поверх них. */
  z-index: 1;
  transition: opacity 0.22s ease, transform 0.22s cubic-bezier(0.32, 0.72, 0, 1);
}
.pull-spinner__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  transition: color 0.18s ease;
}
.pull-spinner__icon .glass-icon { display: none; }
:root.theme-glass .pull-spinner__icon .classic-icon { display: none; }
:root.theme-glass .pull-spinner__icon .glass-icon { display: inline-block; }
.pull-spinner__label {
  font-family: var(--mono, ui-monospace, "SF Mono", Menlo, monospace);
  font-size: 10px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted, #888);
  white-space: nowrap;
  transition: color 0.18s ease, opacity 0.14s ease, transform 0.14s ease;
  will-change: opacity, transform;
}
.pull-spinner__label.pull-spinner__label--out {
  opacity: 0;
  transform: translateY(-4px);
}
.pull-spinner__label.pull-spinner__label--in {
  animation: pull-spinner-label-in 0.18s cubic-bezier(.22,.61,.36,1) both;
}
@keyframes pull-spinner-label-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.pull-spinner.is-active {
  /* На тяге — без transition: значения пишутся 60fps через --pull-progress;
     transition дают каскад незавершённых анимаций = дребезг.
     Y-смещение: 40px (стартовая «спрятанность» под кромкой)
                 - pp * 240px (выезд вверх по тяге).
     При pp=1.0 (PULL_HARD) спиннер на -200px от base — заметно выше
     композёра, на уровне нижнего сообщения. */
  /* Появляемся только после прохождения композёра: до pp=0.55 спиннер
     ещё под чипами/полем ввода (z-index 1 < z-index #composer 5) и его
     не видно. С pp=0.55 fade-in: (pp - 0.55) * 3.3 → ноль до 0.55,
     1.0 на pp≈0.85, overshoot до ~1.5 при pp=1.0 (CSS clamp'ит к 1). */
  /* v949-fix: формула travel'а должна подстраиваться под высоту композёра
     (чипы открыты/закрыты + сколько строк в textarea) и под safe-bot.
     Иначе при маленьком композёре спиннер уходит выше нужного, при большом —
     не дотягивает до верха композёра. Целимся: при pp=1.0 нижний край
     спиннера ровно на (composer-h + 16)px от низа экрана = 16px выше
     верха композёра.
     Спиннер фиксирован bottom: safe-bot + 6. Если translateY = Y, реальная
     позиция от низа = (safe-bot + 6) - Y. Подставляем композёр+16:
       Y = safe-bot - composer-h - 10
     Стартовая точка (pp=0) = 40 (спрятан под кромкой). Линейная интерполяция:
       Y = 40 + pp * (safe-bot - composer-h - 50). */
  opacity: calc((var(--pull-progress, 0) - 0.55) * 3.3);
  transform: translate3d(-50%, calc(
    40px
    + var(--pull-progress, 0) * var(--safe-bot, 0px)
    - var(--pull-progress, 0) * var(--composer-h, 180px)
    - var(--pull-progress, 0) * 50px
  ), 0);
  transition: none;
}
.pull-spinner.is-active .pull-spinner__icon {
  transform: rotate(calc(var(--pull-progress, 0) * 270deg));
}
.pull-spinner.is-active.pull-spinner--soft {
  color: var(--accent, #d97757);
}
.pull-spinner.is-active.pull-spinner--soft .pull-spinner__label {
  color: var(--fg);
}
.pull-spinner.is-active.pull-spinner--hard {
  color: var(--accent, #d97757);
}
.pull-spinner.is-active.pull-spinner--hard .pull-spinner__icon {
  /* Hard-порог: иконка чуть крупнее даёт визуальный «упор». Сам контейнер
     спиннера уже едет вверх через .pull-spinner.is-active transform, тут
     только локальная иконка. */
  transform: rotate(calc(var(--pull-progress, 0) * 270deg)) scale(1.1);
}
.pull-spinner.is-active.pull-spinner--hard .pull-spinner__label {
  color: var(--fg);
}
.pull-spinner.is-spinning {
  /* v944: контейнера больше нет — спиннер крутится без подложки. Кирилл прочитал
     даже opaque #0f0f0f как «серый контейнер» поверх aura. Убираем фон, тень,
     padding и border-radius — остаются только иконка+подпись со своими цветами.
     Для контраста на светлой aura текст/иконка тянут var(--fg) и var(--accent).
     v948 #8: z-index был 100 — спиннер парил поверх чипов композёра. Сбрасываем
     до 2 (ниже z-index композёра: classic 5 / glass 10), композёр перекрывает
     спиннер на пересечении.
     v948-fix-2: -88px поднимал спиннер всего на 94px от низа экрана — это
     внутри композёра, видимая часть нависала над чипами. Привязали Y к
     --composer-h: спиннер сидит на (composer-h + 16)px от низа, т.е. ~16px
     ВЫШЕ верха композёра.
     v949-fix: формула учитывает safe-bot тоже. Анкер bottom=safe-bot+6,
     значит Y = safe-bot - composer-h - 10 даёт фактическую позицию
     composer-h + 16 от низа экрана независимо от safe-area. */
  opacity: 1;
  transform: translate3d(-50%, calc(
    -10px
    + var(--safe-bot, 0px)
    - var(--composer-h, 180px)
  ), 0);
  color: var(--accent, #d97757);
  z-index: 2;
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0;
  box-shadow: none;
  transition:
    opacity 0.22s ease,
    transform 0.22s cubic-bezier(0.32, 0.72, 0, 1);
}
.pull-spinner.is-spinning .pull-spinner__label {
  color: var(--fg);
}
.pull-spinner.is-spinning .pull-spinner__icon {
  animation: pull-spinner-rotate 0.7s linear infinite;
}
@keyframes pull-spinner-rotate {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Glass-стиль: убираем uppercase/моноширинку, мягкие полупрозрачные цвета. */
:root.theme-glass .pull-spinner {
  color: rgba(255, 255, 255, 0.6);
}
:root.theme-glass.theme-light .pull-spinner {
  color: rgba(0, 0, 0, 0.5);
}
:root.theme-glass .pull-spinner__label {
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;
  font-size: 12px;
  letter-spacing: 0;
  text-transform: none;
  color: rgba(255, 255, 255, 0.6);
}
:root.theme-glass.theme-light .pull-spinner__label {
  color: rgba(0, 0, 0, 0.55);
}
/* Glass: при достижении любого порога — белый/чёрный текст, иконка остаётся
   нейтрального цвета (без оранжевого акцента). */
:root.theme-glass .pull-spinner.is-active.pull-spinner--soft,
:root.theme-glass .pull-spinner.is-active.pull-spinner--hard {
  color: rgba(255, 255, 255, 0.95);
}
:root.theme-glass.theme-light .pull-spinner.is-active.pull-spinner--soft,
:root.theme-glass.theme-light .pull-spinner.is-active.pull-spinner--hard {
  color: rgba(0, 0, 0, 0.85);
}
:root.theme-glass .pull-spinner.is-active.pull-spinner--soft .pull-spinner__label,
:root.theme-glass .pull-spinner.is-active.pull-spinner--hard .pull-spinner__label,
:root.theme-glass .pull-spinner.is-spinning .pull-spinner__label {
  color: rgba(255, 255, 255, 0.95);
}
:root.theme-glass.theme-light .pull-spinner.is-active.pull-spinner--soft .pull-spinner__label,
:root.theme-glass.theme-light .pull-spinner.is-active.pull-spinner--hard .pull-spinner__label,
:root.theme-glass.theme-light .pull-spinner.is-spinning .pull-spinner__label {
  color: rgba(0, 0, 0, 0.9);
}
:root.theme-glass .pull-spinner.is-spinning {
  color: rgba(255, 255, 255, 0.95);
}
:root.theme-glass.theme-light .pull-spinner.is-spinning {
  color: rgba(0, 0, 0, 0.85);
}

body.kb-active #composer {
  transform: translate3d(0, calc(-1 * var(--kb, 0px)), 0);
  will-change: transform;
}
/* В Ретро (НЕ glass, НЕ capacitor-native) composer и #chat сдвигаются
   только на --kb (JS обновляет реалтайм из tg.viewportChanged и
   visualViewport.resize). env(keyboard-inset-height) НЕ используем —
   в Telegram WebView на iOS он либо 0 (нет поддержки), либо отдаёт
   некорректно большое число и composer улетает за пределы экрана.
   min(--kb, 50vh) защищает от случайно «сорванного» --kb. */
:root:not(.theme-glass) body.kb-active:not(.maccode-native) #composer,
:root:not(.theme-glass) body.kb-active:not(.maccode-native) #chat {
  transform: translate3d(0, calc(-1 * min(var(--kb, 0px), 50vh)), 0) !important;
}
@media (prefers-reduced-motion: reduce) {
  #composer { transition: none; }
  #chat { transition: none; }
}
#scroll-down-btn,
#scroll-up-btn {
  position: absolute;
  right: 12px;
  width: 36px; height: 36px;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  box-shadow: 2px 2px 0 0 rgba(0,0,0,0.55);
  z-index: 5;
  animation: arrowAppear 0.18s steps(3, end) both;
  /* iOS WKWebView: стрелка прибита к кромке #composer, который при наборе текста
     анимированно едет вверх (transition height). Без manipulation WebKit ждёт
     возможный double-tap/pan и не дотаскивает click до уезжающей мишени — стрелка
     «не нажимается когда вкл текст». manipulation = click стреляет на touchend. */
  touch-action: manipulation;
}
#scroll-down-btn { top: -46px; }
#scroll-up-btn { top: -90px; }
#scroll-down-btn.hidden,
#scroll-up-btn.hidden { display: none; }
#scroll-down-btn.leaving,
#scroll-up-btn.leaving {
  animation: arrowDisappear 0.18s steps(3, end) forwards;
  /* pointer-events НЕ глушим: тап по стрелке во время fade-out обязан
     срабатывать (Кирилл, баг 2: «иногда приходится по два раза нажимать» —
     первый тап попадал в уже уходящую кнопку с pointer-events:none). */
}
@keyframes arrowDisappear {
  0%   { opacity: 1; transform: translateY(0) scale(1); }
  40%  { opacity: 1; transform: translateY(-2px) scale(1.08); }
  100% { opacity: 0; transform: translateY(6px) scale(0.6); }
}
#scroll-down-btn .scroll-arrow {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%;
}
.scroll-badge {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center; justify-content: center;
  font-weight: 700; font-size: 13px; line-height: 1; font-family: var(--font-ui);
  color: var(--fg);
  letter-spacing: 0;
  pointer-events: none;
  box-sizing: border-box;
}
#scroll-down-btn.has-unread .scroll-arrow { display: none; }
#scroll-down-btn.has-unread .scroll-badge { display: flex; }
#scroll-down-btn.has-unread .scroll-badge.bump {
  animation: badgeBump 0.22s steps(4, end);
}
@keyframes badgeBump {
  0%   { transform: scale(0.55); opacity: 0.2; }
  55%  { transform: scale(1.18); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
@keyframes arrowAppear {
  0%   { opacity: 0; transform: translateY(6px) scale(0.6); }
  60%  { opacity: 1; transform: translateY(-2px) scale(1.08); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

/* ===== Pin bar (закреплённые сообщения, как в Telegram) =====
   Absolute-overlay поверх #chat. Не толкает контент вниз при появлении/
   исчезновении; #chat компенсируется padding-top через :has() ниже. */
.pin-bar {
  position: absolute;
  top: var(--topbar-h, 56px);
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px 6px 0;
  background: var(--bg-card);
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  min-height: 40px;
  z-index: 9;
}
.pin-bar.hidden { display: none; }
/* padding-top #chat под pin-bar выставляется ИНЛАЙНОМ из applyChatPinPadding() в app.js.
   :has() в WebKit обновляется лениво — между toggle классом hidden и измерением
   scrollHeight селектор ещё держит старое значение, compensate ловит delta≈0,
   контент визуально едет на ~1 строку. Инлайн-стиль применяется синхронно. */
.pin-bar {
  transform-origin: center center;
  transition: transform 180ms cubic-bezier(0.2, 0, 0, 1), background 120ms ease;
}
.pin-bar:active {
  background: var(--bg-soft);
  transform: scale(0.965);
}
.pin-bar__accent {
  flex: 0 0 auto;
  width: 3px;
  align-self: stretch;
  margin: 4px 6px 4px 8px;
  background: var(--accent);
}
.pin-bar__icon {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 18px; height: 18px;
  color: var(--accent);
}
.pin-bar__col {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  overflow: hidden;
}
.pin-bar__title {
  font: 700 11px/1.15 inherit;
  color: var(--accent);
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pin-bar__preview {
  font: 400 13px/1.2 inherit;
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0.92;
}
.pin-bar__stack,
.pin-bar__close {
  flex: 0 0 auto;
  width: 28px; height: 28px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--fg-dim);
  cursor: pointer;
}
.pin-bar__stack:active,
.pin-bar__close:active { color: var(--fg); }
.pin-bar.single .pin-bar__stack { display: none; }
.pin-bar__stack { margin-right: 4px; }

#meta-row {
  display: flex; gap: 6px; flex-wrap: wrap;
}
#fav-react-row {
  /* Контейнер всегда в DOM, чтобы можно было анимировать вход/выход через
     max-height + opacity + transform. display:none/flex не транзитится —
     раньше при тапе на ★/реакции ряд появлялся «хуяк», без плавности.
     С v887 — отдельный «dragging»-режим: пока палец на инпуте свайпает
     горизонтально, max-height/opacity/transform едут по CSS-переменной
     --fav-react-progress (0..1), без transition. На отпускании JS решает:
     если progress>0.5 — добавляем .open (transition доехать до 1), иначе
     убираем (transition обратно до 0). */
  display: flex;
  gap: 6px;
  flex-wrap: nowrap;
  align-items: center;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  /* padding-bottom + negative margin-bottom = тень чипов (~16px вниз в glass)
     не клипается горизонтальным скроллом, при этом layout вокруг строки не сдвигается. */
  padding: 0 2px 18px 2px;
  margin-bottom: -18px;
  max-height: 0;
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  visibility: hidden;
  transition: max-height 0.28s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 0.22s ease,
              transform 0.28s cubic-bezier(0.22, 1, 0.36, 1),
              visibility 0s linear 0.28s;
}
#fav-react-row::-webkit-scrollbar { display: none; }
#fav-react-row > .fav-react-chip { flex: 0 0 auto; }
#fav-react-row.open {
  max-height: 78px;
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
  visibility: visible;
  transition: max-height 0.28s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 0.22s ease,
              transform 0.28s cubic-bezier(0.22, 1, 0.36, 1),
              visibility 0s linear 0s;
}
#fav-react-row.fav-react-dragging {
  /* Прогрессивная тяга: переменная --fav-react-progress 0..1 ставится в JS
     на каждом touchmove. Никакого transition — должно идти строго за пальцем. */
  max-height: calc(78px * var(--fav-react-progress, 0));
  opacity: var(--fav-react-progress, 0);
  transform: translateY(calc(8px * (1 - var(--fav-react-progress, 0))));
  visibility: visible;
  pointer-events: none;
  transition: none;
}
#fav-react-row.open .fav-react-chip {
  animation: fav-chip-in 0.32s cubic-bezier(0.22, 1, 0.36, 1) both;
}
#fav-react-row.open .fav-react-chip:nth-child(1) { animation-delay: 0ms; }
#fav-react-row.open .fav-react-chip:nth-child(2) { animation-delay: 24ms; }
#fav-react-row.open .fav-react-chip:nth-child(3) { animation-delay: 44ms; }
#fav-react-row.open .fav-react-chip:nth-child(4) { animation-delay: 60ms; }
#fav-react-row.open .fav-react-chip:nth-child(5) { animation-delay: 74ms; }
#fav-react-row.open .fav-react-chip:nth-child(6) { animation-delay: 84ms; }
#fav-react-row.open .fav-react-chip:nth-child(7) { animation-delay: 92ms; }
#fav-react-row.open .fav-react-chip:nth-child(n+8) { animation-delay: 100ms; }
/* Открытие свайпом: чипы уже проявились вместе с контейнером по ходу пальца,
   поэтому повторный stagger-«выскок» (fav-chip-in) на добавление .open не нужен —
   это и есть «второй раз анимируются». Глушим только для drag-открытия; тап/
   right-click (десктоп) этот класс не ставит и сохраняет stagger. */
#fav-react-row.open.fav-react-no-chip-anim .fav-react-chip { animation: none; }
@keyframes fav-chip-in {
  from { opacity: 0; transform: translateY(8px) scale(0.85); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.fav-react-chip {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0;
  border: 1px solid var(--border); background: var(--bg-card);
  color: var(--fg); cursor: pointer;
  font: inherit; font-size: 11px; line-height: 1;
  border-radius: 0;
}
.fav-react-chip.active { background: var(--fg); color: var(--bg); border-color: var(--fg); }
.fav-react-chip svg { width: 18px; height: 18px; display: block; }
.msg.fav-react-hidden { display: none !important; }
body.in-fav-thread #voice-mode-btn { display: none !important; }
body.in-fav-thread #meta-row { display: none !important; }

/* slash command dropdown + chip popovers (shared look) */
#slash-suggest,
#chip-popover {
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border: 1px solid var(--border);
  box-shadow: 2px 2px 0 0 rgba(0,0,0,0.55);
  max-height: 240px;
  overflow-y: auto;
  /* v1103: «выпадающее меню сдвигает контент наверх» — поповеры сидели в потоке
     внутри #composer и раздували его offsetHeight → --composer-h → padding-bottom
     чата. Выводим их в absolute-оверлей над композером (он position:relative),
     теперь высота композера не растёт, чат не дёргается — плашка ложится поверх. */
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: 100%;
  z-index: 20;
  margin-bottom: 4px;
  transform-origin: bottom center;
  animation: slashOpen8 220ms steps(6, end);
  will-change: clip-path, opacity;
}
#slash-suggest.hidden,
#chip-popover.hidden { display: none; }
#slash-suggest.closing,
#chip-popover.closing {
  animation: slashClose8 180ms steps(5, end) forwards;
  pointer-events: none;
}
@keyframes slashOpen8 {
  0%   { clip-path: inset(100% 0 0 0); opacity: 0.35; }
  100% { clip-path: inset(0 0 0 0);   opacity: 1; }
}
@keyframes slashClose8 {
  0%   { clip-path: inset(0 0 0 0);   opacity: 1; }
  100% { clip-path: inset(100% 0 0 0); opacity: 0.25; }
}
.slash-suggest-row {
  display: flex;
  flex-direction: column;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  font-family: inherit;
}
.slash-suggest-row:last-child { border-bottom: none; }
.slash-suggest-row:hover { background: rgba(217, 119, 87, 0.08); }
.slash-suggest-row.active { background: rgba(217, 119, 87, 0.18); }
/* Строка «Сбросить (авто)» в поповере chip-Ген — деструктивная, приглушаем (отделение даёт .gen-footer). */
.slash-suggest-row.gen-reset { opacity: 0.72; }
.slash-suggest-row.gen-reset .slash-suggest-name { color: #c0392b; }
/* Сводка кредитов Magnific (цена/баланс) — некликабельные инфо-строки. */
.slash-suggest-row.gen-info { cursor: default; }
.slash-suggest-row.gen-info:hover { background: transparent; }
.slash-suggest-row.gen-info .slash-suggest-name { color: var(--fg); font-weight: 600; letter-spacing: 0.2px; }
.slash-suggest-row.gen-warn .slash-suggest-name { color: #c0392b; }
/* v1108: Спишется/Баланс/Сбросить закреплены у нижней кромки окна поповера Ген.
   Футер ВНЕ области скролла (flex-shrink:0) — список опций .gen-scroll над ним
   прокручивается, футер не двигается даже при оттягивании. Фон не задаём —
   наследует фон поповера, чтобы строки футера выглядели как остальные пункты;
   от списка отделяет только тонкая верхняя граница (как у обычных строк). */
.gen-footer {
  flex: 0 0 auto;
  background: transparent;
  border-top: 1px solid var(--border);
}
/* v1109: футер Ген — каждый пункт (Спишется/Баланс/Сбросить) в ОДНУ линию:
   имя слева, числовая разбивка справа приглушённо. Было — имя над описанием
   (две текстовые строки на пункт). */
.gen-footer .slash-suggest-row {
  flex-direction: row;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.gen-footer .slash-suggest-name { flex: 0 0 auto; white-space: nowrap; }
.gen-footer .slash-suggest-desc {
  margin-top: 0;
  display: block;
  flex: 0 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: right;
}
:root.theme-glass #chip-popover .gen-footer { border-top-color: rgba(255, 255, 255, 0.10); }
:root.theme-glass.theme-light #chip-popover .gen-footer { border-top-color: rgba(0, 0, 0, 0.10); }
/* v1110: буквенный бейдж модели в строке «Модель» (референс Magnific) — первая буква
   имени на цветном квадрате; «Авто» = нейтральная звёздочка на --accent-dim. */
.gen-mico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-right: 7px;
  border-radius: 5px;
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0;
  vertical-align: middle;
  flex: 0 0 auto;
}
/* v1112: бейдж модели + подпись — одна отцентрованная по вертикали строка
   (заменяет магический vertical-align; :has — fallback на vertical-align:middle). */
.slash-suggest-name:has(.gen-mico) { display: flex; align-items: center; }
/* v1110: крупный ценник «✦ N кр» + компактная «Сбросить» в одну строку под балансом. */
.gen-footer .gen-actions {
  display: flex;
  align-items: stretch;
  gap: 8px;
  padding: 6px 10px;
}
.gen-price {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  padding: 6px 10px;
  border-radius: 10px;
  background: var(--accent);
  color: #fff;
  transition: background-color 200ms ease, color 200ms ease;
}
.gen-price-spark { font-size: 11px; opacity: 0.9; align-self: center; }
.gen-price-main { font-size: 16px; font-weight: 700; line-height: 1; letter-spacing: 0.3px; }
.gen-price-unit { font-size: 12px; font-weight: 600; opacity: 0.85; }
.gen-price-sub {
  margin-left: auto;
  align-self: center;
  font-size: 11px;
  font-weight: 600;
  opacity: 0.9;
  white-space: nowrap;
}
/* Плейсхолдер (считаю…/выбери модель/длину) — без заливки, приглушённо. */
.gen-price.gen-price-muted {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--fg-dim);
}
.gen-price.gen-price-muted .gen-price-main { font-size: 13px; font-weight: 600; }
.gen-price.gen-price-muted .gen-price-spark { opacity: 0.55; }
/* Не хватает кредитов — красный ценник. */
.gen-price.gen-price-warn { background: #c0392b; color: #fff; }
/* v1112: на стекле ценник «Спишется/Баланс» и его красный «не хватит» — фростед
   (полупрозрачная заливка + блюр), а не плотная плашка; просьба «там где баланс
   или денег не хватает — тоже прозрачность и размытие». :not(.gen-price-muted)
   бережёт плейсхолдер «считаю…/выбери модель». */
:root.theme-glass .gen-price:not(.gen-price-muted) {
  background: color-mix(in srgb, var(--accent) 55%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  backdrop-filter: blur(14px) saturate(160%);
}
:root.theme-glass .gen-price.gen-price-warn {
  background: color-mix(in srgb, #c0392b 55%, transparent);
}
/* «Сбросить» рядом с ценником — компактная боксовая кнопка, не во всю ширину. */
.gen-actions .slash-suggest-row.gen-reset {
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  opacity: 1;
}
/* v1114: line-height:1 + явный шрифт — чтобы высота контента «Сбросить» совпала
   с «выбери модель» (.gen-price-main lh:1, 13px); align-items:stretch у .gen-actions
   на WKWebView высоты не уравнивает, поэтому равняем box-геометрию буквально. */
.gen-actions .slash-suggest-row.gen-reset .slash-suggest-name {
  color: var(--fg-dim);
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
}
.gen-actions .slash-suggest-row.gen-reset .slash-suggest-desc { display: none; }
/* В режиме Ген сам поповер не скроллится — прокручивается только список опций
   (.gen-scroll), а футер остаётся прижатым к низу. Специфичность поднята выше
   :root.theme-glass #chip-popover, иначе в glass вернётся overflow-y:auto. */
#chip-popover[data-anchor="gen"],
:root.theme-glass #chip-popover[data-anchor="gen"] { overflow: hidden; }
#chip-popover[data-anchor="gen"] .gen-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
.slash-suggest-name {
  color: var(--accent);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.4px;
}
.slash-suggest-name i {
  color: var(--fg-dim);
  font-style: italic;
  font-weight: 400;
}
.slash-suggest-desc {
  color: var(--fg-dim);
  font-size: 11px;
  margin-top: 2px;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  white-space: normal;
}
.slash-suggest-more {
  border-top: 1px dashed var(--border);
  background: rgba(217, 119, 87, 0.04);
}
.slash-suggest-more .slash-suggest-name {
  color: var(--fg-dim);
  font-style: italic;
}
.slash-suggest-more:hover { background: rgba(217, 119, 87, 0.12); }

/* slash help modal */
.slash-help-group { margin-bottom: 14px; }
.slash-help-group:last-child { margin-bottom: 0; }
.slash-help-title {
  color: var(--accent);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--border);
  padding-bottom: 3px;
}
.slash-help-item {
  padding: 8px 8px;
  margin: 0 -8px;
  border-radius: 2px;
  cursor: pointer;
  transition: background 0.08s ease;
}
.slash-help-item:hover { background: rgba(217, 119, 87, 0.08); }
.slash-help-item:active { background: rgba(217, 119, 87, 0.18); }
.slash-help-item + .slash-help-item { border-top: 1px solid var(--border); }
.slash-help-name {
  color: var(--fg);
  font-size: 13px;
  font-weight: 600;
}
.slash-help-name i {
  color: var(--fg-dim);
  font-style: italic;
  font-weight: 400;
}
.slash-help-desc {
  color: var(--fg-dim);
  font-size: 11px;
  margin-top: 3px;
  line-height: 1.45;
}
.chip {
  appearance: none; -webkit-appearance: none;
  background: var(--bg-card);
  color: var(--fg-dim);
  border: 1px solid var(--border);
  padding: 4px 8px;
  font-family: inherit; font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  cursor: pointer;
}
.chip.danger { color: var(--red); border-color: var(--red); }
.chip.warn { color: #d99a3a; border-color: #d99a3a; }
#chip-context[disabled] { cursor: default; opacity: 0.85; }
/* Кнопка-чип «★ реакции» — десктопный аналог свайпа ↑ от инпута,
   который на мобиле тоггерит #fav-react-row. На мобиле прячем — там есть жест. */
#chip-react { display: none; }
body.is-desktop #chip-react { display: inline-flex; align-items: center; }
#chip-react.active { color: var(--accent); border-color: var(--accent); }
/* Весь блок чипов невидим, пока в state нет реальных model/effort/view.
   refreshChips() ставит .is-hydrated на #meta-row после того, как все три заполнены
   из server-settings — тогда C:/M/E/V/команды синхронно проявляются вместе.
   Без этого был флип плейсхолдеров (v796) или дыра между C: и / команды (v797). */
#meta-row {
  opacity: 0;
  /* v1098: убран transform: translateY(4px)→0 на гидрации — он двигал контент
     («чипы при появлении двигают чат», Кирилл). v1102: убраны и translateZ(0)+
     will-change, которые я добавил «для надёжности». Они вышли боком: на iOS
     WKWebView transform/will-change на ПРЕДКЕ делают его композитным слоем, и
     backdrop-filter дочерних .chip (8716) начинает семплить пустой буфер слоя
     #meta-row вместо чата за ним — стеклянная подложка чипов умирала, чипы
     читались как плоские (как раз эту регрессию Кирилл и поймал). Держим голый
     opacity-фейд без transform: высота ряда инвариантна (nowrap), контент не
     двигает, а при opacity:1 стекинг-контекст снимается и backdrop оживает. */
  transition: opacity 0.7s cubic-bezier(0.22, 0.61, 0.36, 1);
}
#meta-row.is-hydrated {
  opacity: 1;
}

#input-row {
  display: flex; align-items: flex-end; gap: 6px;
}
.input-wrap {
  position: relative;
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-width: 0;
  box-shadow: 2px 2px 0 0 rgba(0,0,0,0.55);
}
.input-wrap::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid transparent;
  pointer-events: none;
  box-sizing: border-box;
  z-index: 4;
}
.input-wrap:focus-within::after {
  border-color: var(--accent);
}
#input {
  flex: 1 1 auto;
  width: 100%;
  background: var(--bg-card);
  color: var(--fg);
  caret-color: var(--accent);
  border: 1px solid var(--border);
  border-bottom: 0;
  padding: 8px 12px 8px 12px;
  font-family: inherit;
  font-size: 16px;
  line-height: 1.4;
  border-radius: 0;
  resize: none;
  height: 40px;
  min-height: 40px;
  max-height: 200px;
  -webkit-user-select: text; user-select: text;
  touch-action: manipulation;
  box-sizing: border-box;
  overflow-y: hidden;
  appearance: none; -webkit-appearance: none;
  /* Синхронно с mirror: ломаем длинные слова в любой позиции, чтобы курсор
     textarea и текст mirror'а оказывались на одной строке. */
  overflow-wrap: anywhere;
  word-break: normal;
  /* Плавный рост/сжатие при autoresize. Измерительный шаг в JS временно
     выключает transition через inline-style, чтобы height:auto не пытался
     анимироваться (между px и keyword'ом transition не работает). */
  transition: height .22s cubic-bezier(.22,.61,.36,1);
}
#input:focus {
  outline: none;
  border-color: var(--accent);
}
#input-ghost {
  position: absolute;
  top: 0; left: 0;
  right: 0;
  height: 40px;
  display: flex;
  align-items: center;
  padding: 0 12px;
  font-family: inherit;
  font-size: 16px;
  line-height: 1.4;
  color: var(--fg-dim);
  pointer-events: none;
  letter-spacing: 0.5px;
}
#input-ghost .ghost-cur {
  color: var(--accent);
  margin-left: 2px;
  animation: blink 1s steps(2, start) infinite;
}
.input-wrap:focus-within #input-ghost,
.input-wrap.has-content #input-ghost {
  display: none;
}

/* Mirror-зеркало текста textarea — рендерит ровно тот же текст в виде span'ов
   по логическим строкам, чтобы updateInputLineBgs мог ставить data-bg на
   каждую строку отдельно (когда конкретная строка въезжает под user-баббл).
   Mirror лежит абсолютно поверх textarea с теми же шрифт/padding/line-height,
   а текст textarea делается прозрачным (-webkit-text-fill-color) — видим
   только зеркало. Caret и selection-фон работают как раньше через
   соответствующие CSS-свойства textarea. */
#input-mirror {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 40px;
  padding: 8px 12px 8px 12px;
  font-family: inherit;
  font-size: 16px;
  line-height: 1.4;
  white-space: pre-wrap;
  /* `overflow-wrap: anywhere` синхронизирует разбивку длинных слов с textarea:
     iOS WebKit textarea ломает слово в любой позиции, а mirror раньше с
     `word-break: normal` держал слово в одну строку — поэтому курсор и
     отображаемый текст оказывались в разных позициях (видно когда печатаешь
     длинное слово без пробелов). */
  overflow-wrap: anywhere;
  word-break: normal;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
  color: var(--fg);
  overflow: hidden;
  box-sizing: border-box;
  z-index: 2;
}
#input-mirror .line {
  display: inline;
  transition: color 0.08s linear;
}
/* Каждая строка получает свой --bg-mix (0..1) от JS — color интерполируется
   между var(--fg) и #ffffff пропорционально перекрытию строки user-бабблом.
   Работает только в glass: в классике color: inherit, без эффекта. */
:root.theme-glass #input-mirror .line,
:root.theme-glass.theme-light #input-mirror .line {
  --bg-mix: 0;
  color: color-mix(in srgb, var(--fg) calc((1 - var(--bg-mix)) * 100%), #ffffff);
}
/* Прячем фактический текст textarea, оставляя caret и selection-фон. Без
   этого textarea-текст накладывался бы на mirror = двойной серый-белый
   результат. -webkit-text-fill-color работает в WebKit/iOS поверх color. */
.input-wrap > #input {
  -webkit-text-fill-color: transparent;
  color: transparent;
}
.input-wrap > #input::selection {
  -webkit-text-fill-color: var(--fg);
  color: var(--fg);
  background: rgba(100, 150, 250, 0.35);
}
body.is-desktop #input-mirror {
  font-size: 14px;
}

/* THINKING-индикатор: баббл «МАККОД» внутри #chat, виртуально последним
   (order:9999 в flex-column). Состояния: .visible / .exiting / .morphing.
   Вход: окно → надпись → маскот → точки слева направо.
   Выход: точки справа налево → маскот → надпись → окно.
   Morph (пришёл текст): точки справа налево + маскот, JS превращает
   баббл в .msg.bot и запускает typewriter в нём же. */
#chat-thinking {
  display: none;
  flex-direction: column;
  align-self: flex-start;
  gap: 4px;
  padding: 8px 10px;
  margin: 0;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 0;
  box-shadow: 2px 2px 0 rgba(0,0,0,0.6);
  pointer-events: none;
  color: var(--accent);
  line-height: 1;
  flex: 0 0 auto;
  width: max-content;
  max-width: 92%;
  order: 9999;
}
/* Кирилл попросил убрать плашку «маскот + три точки» (#chat-thinking) — гасим её
   во всех состояниях. Морф в bot-баббл при этом цел: finishChatThinkingMorph
   снимает id и вешает класс .msg.bot (видимый), переключение по КЛАССУ, не по
   display. Композер-фраза «думаю…» (mountThinking) даёт юзеру обратную связь.
   Вернуть плашку — поменять none → flex. */
#chat-thinking.visible,
#chat-thinking.exiting,
#chat-thinking.morphing { display: none; }

#chat-thinking .role {
  display: flex;
  align-items: baseline;
  justify-content: flex-start;
  gap: 6px;
  font-size: 10px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--accent-dim-2);
  margin-bottom: 2px;
}
#chat-thinking .role-label {
  flex: 0 1 auto;
  white-space: nowrap;
}
.chat-thinking-row {
  display: flex;
  align-items: center;
  gap: 6px;
}
.chat-thinking-mac {
  width: 24px; height: 20px;
  display: block;
  flex: 0 0 auto;
  color: var(--accent);
  will-change: transform, opacity;
}
.chat-thinking-mac > g { fill: var(--accent); }
.chat-thinking-row { color: var(--accent); }
.chat-thinking-dot {
  width: 4px; height: 4px;
  background: currentColor;
  border-radius: 50%;
  display: inline-block;
  opacity: 0;
}

/* === Вход === */
#chat-thinking.visible {
  animation: chat-thinking-shell-in 220ms ease-out backwards;
}
#chat-thinking.visible .role-label {
  animation: chat-thinking-fade-in 180ms ease-out 200ms backwards;
}
#chat-thinking.visible .chat-thinking-mac {
  animation:
    chat-thinking-fade-in 180ms ease-out 380ms backwards,
    chat-thinking-march 1.4s ease-in-out 600ms infinite;
}
#chat-thinking.visible .chat-thinking-row .chat-thinking-dot:nth-child(2) {
  animation:
    chat-thinking-dot-in 220ms ease-out 560ms backwards,
    chat-thinking-dot-pulse 1.6s cubic-bezier(0.4, 0, 0.2, 1) 1100ms infinite;
}
#chat-thinking.visible .chat-thinking-row .chat-thinking-dot:nth-child(3) {
  animation:
    chat-thinking-dot-in 220ms ease-out 700ms backwards,
    chat-thinking-dot-pulse 1.6s cubic-bezier(0.4, 0, 0.2, 1) 1250ms infinite;
}
#chat-thinking.visible .chat-thinking-row .chat-thinking-dot:nth-child(4) {
  animation:
    chat-thinking-dot-in 220ms ease-out 840ms backwards,
    chat-thinking-dot-pulse 1.6s cubic-bezier(0.4, 0, 0.2, 1) 1400ms infinite;
}

/* === Выход (точки справа налево, потом маскот, надпись, окно) === */
#chat-thinking.exiting {
  animation: chat-thinking-shell-out 220ms ease-in 700ms forwards;
}
#chat-thinking.exiting .chat-thinking-row .chat-thinking-dot:nth-child(4) {
  animation: chat-thinking-fade-out 160ms ease-in 0ms forwards;
}
#chat-thinking.exiting .chat-thinking-row .chat-thinking-dot:nth-child(3) {
  animation: chat-thinking-fade-out 160ms ease-in 100ms forwards;
}
#chat-thinking.exiting .chat-thinking-row .chat-thinking-dot:nth-child(2) {
  animation: chat-thinking-fade-out 160ms ease-in 200ms forwards;
}
#chat-thinking.exiting .chat-thinking-mac {
  animation: chat-thinking-fade-out 180ms ease-in 360ms forwards;
}
#chat-thinking.exiting .role-label {
  animation: chat-thinking-fade-out 180ms ease-in 540ms forwards;
}

/* === Morph (точки справа налево + маскот, JS дальше превращает в .msg.bot) === */
#chat-thinking.morphing .chat-thinking-row .chat-thinking-dot:nth-child(4) {
  animation: chat-thinking-fade-out 140ms ease-in 0ms forwards;
}
#chat-thinking.morphing .chat-thinking-row .chat-thinking-dot:nth-child(3) {
  animation: chat-thinking-fade-out 140ms ease-in 90ms forwards;
}
#chat-thinking.morphing .chat-thinking-row .chat-thinking-dot:nth-child(2) {
  animation: chat-thinking-fade-out 140ms ease-in 180ms forwards;
}
#chat-thinking.morphing .chat-thinking-mac {
  animation: chat-thinking-fade-out 160ms ease-in 320ms forwards;
}

@keyframes chat-thinking-shell-in {
  0%   { opacity: 0; transform: scale(0.85); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes chat-thinking-shell-out {
  0%   { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(0.85); }
}
@keyframes chat-thinking-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes chat-thinking-fade-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}
@keyframes chat-thinking-dot-in {
  0%   { opacity: 0; transform: translateX(-6px); }
  100% { opacity: 0.25; transform: translateX(0); }
}
@keyframes chat-thinking-dot-pulse {
  0%, 100% { opacity: 0.35; transform: scale(0.85); }
  50%      { opacity: 1;    transform: scale(1.05); }
}
@keyframes chat-thinking-march {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-2px); }
}

#send {
  width: 40px; min-width: 40px; height: 40px; box-sizing: border-box;
  box-shadow: 2px 2px 0 0 rgba(0,0,0,0.55);
  padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
#send.icon-btn.primary {
  background: var(--bg-card);
  border-color: var(--bg-card);
  color: var(--fg-dim);
}
.send-mascot {
  display: block;
  position: absolute;
  top: 50%; left: 50%;
  /* v948 #6: морф мaскот↔STOP с лёгким overshoot (cubic-bezier(0.34, 1.56, 0.64, 1)).
     Раньше был чистый opacity-fade «хуяк и превратились». Теперь маскот при морфе
     ужимается до 0.55 + крутится на 90° → STOP вылезает с противоположной стороны
     (с -90° на 0°), оба двигаются синхронно. Время 320мс — заметно, но не вязко. */
  transform: translate(-50%, -50%) scale(1) rotate(0deg);
  transition:
    opacity 240ms ease,
    transform 320ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.send-mascot > g { fill: var(--fg-dim); }
.send-mascot-eyelids { animation: send-mascot-blink 6.4s infinite; }
@keyframes send-mascot-blink {
  0%, 92%, 100% { opacity: 0; }
  94%, 96%      { opacity: 1; }
}
/* v935: STOP морфит из инвейдера прямо в #send — два SVG поверх друг друга,
   переключение opacity через #send.is-stop. Click в is-stop режиме шлёт stop-команду
   (см. app.js click handler). Отдельный #chip-stop из meta-row больше не используем. */
#send { position: relative; }
.send-stop {
  display: block;
  position: absolute;
  top: 50%; left: 50%;
  /* v948 #6: исходное состояние STOP — ужат до 0.55 и повёрнут на -90°.
     При is-stop разворачивается до scale(1) rotate(0). Симметрично маскоту. */
  transform: translate(-50%, -50%) scale(0.55) rotate(-90deg);
  opacity: 0;
  pointer-events: none;
  transition:
    opacity 240ms ease,
    transform 320ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.send-stop > rect { fill: var(--red); }
#send.is-stop .send-mascot {
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.55) rotate(90deg);
}
#send.is-stop .send-stop {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1) rotate(0deg);
}
#send.is-stop { color: var(--red); border-color: var(--red); }
/* В Glass красный стоп — чужеродный. Дark glass → белый, light glass → тёмный. */
:root.theme-glass .send-stop > rect { fill: rgba(255, 255, 255, 0.95); }
:root.theme-glass #send.is-stop { color: rgba(255, 255, 255, 0.95); border-color: rgba(255, 255, 255, 0.45); }
:root.theme-glass.theme-light .send-stop > rect { fill: rgba(0, 0, 0, 0.85); }
:root.theme-glass.theme-light #send.is-stop { color: rgba(0, 0, 0, 0.85); border-color: rgba(0, 0, 0, 0.30); }
/* Старая STOP-пилюля над композёром — больше не показываем нигде. */
#chip-stop { display: none !important; }

/* ===== Image viewer (lightbox) ===== */
#image-viewer {
  position: fixed; inset: 0;
  background: rgba(10, 9, 8, var(--iv-bg-alpha, 1));
  display: flex; align-items: center; justify-content: center;
  z-index: 9700;
  padding: calc(var(--safe-top, 0px) + 8px) 4px calc(var(--safe-bot, 0px) + 8px);
  cursor: zoom-out;
  overflow: hidden;
  touch-action: none;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 220ms ease, visibility 0s linear 220ms;
}
#image-viewer.hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
#image-viewer.iv-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 220ms ease, visibility 0s linear 0s;
}
/* Трек на 3 слота (prev/curr/next): двигаем его как единое целое при свайпе.
   Соседняя картинка видна сразу — а не выпадает после смены src на одной img. */
.iv-track {
  position: absolute;
  top: calc(var(--safe-top, 0px) + 8px);
  right: 4px;
  bottom: calc(var(--safe-bot, 0px) + 8px);
  left: 4px;
  will-change: transform;
  transform: translate3d(0, 0, 0);
  pointer-events: none;
}
.iv-track.iv-snap {
  transition: transform 240ms cubic-bezier(0.2, 0.7, 0.2, 1);
}
.iv-slot {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.iv-slot-prev { transform: translate3d(-100%, 0, 0); }
.iv-slot-curr { transform: translate3d(0, 0, 0); }
.iv-slot-next { transform: translate3d(100%, 0, 0); }
.iv-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
  image-rendering: auto;
  -webkit-image-rendering: auto;
  user-select: none;
  -webkit-user-select: none;
  -webkit-user-drag: none;
  touch-action: none;
  pointer-events: auto;
}
#image-viewer-img {
  transform-origin: 0 0;
  will-change: transform, opacity;
  opacity: 0;
  transform: translate3d(0, 0, 0) scale(0.94);
  transition: transform 240ms cubic-bezier(0.2, 0.7, 0.2, 1), opacity 200ms ease;
}
#image-viewer.iv-open #image-viewer-img {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
}
#image-viewer.iv-zooming #image-viewer-img {
  transition: none;
}
#image-viewer-close {
  position: absolute;
  top: calc(var(--safe-top, 0px) + 10px);
  right: 10px;
  z-index: 10;
  cursor: pointer;
  min-width: 44px;
  min-height: 44px;
  padding: 8px 12px;
  font-size: 18px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
#image-viewer .iv-count {
  position: absolute;
  top: calc(var(--safe-top, 0px) + 18px);
  left: 14px;
  z-index: 10;
  font-family: var(--font-ui);
  font-size: 12px;
  letter-spacing: 0.04em;
  color: rgba(255, 255, 255, 0.88);
  background: rgba(0, 0, 0, 0.45);
  padding: 4px 8px;
  pointer-events: none;
}
#image-viewer .iv-count[hidden] { display: none; }
#image-viewer .iv-actions {
  position: absolute;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 20px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  display: flex;
  gap: 10px;
}
#image-viewer .iv-action {
  appearance: none; -webkit-appearance: none;
  width: 36px;
  height: 36px;
  border: 1px solid var(--border);
  border-radius: 0;
  background: var(--bg-card);
  color: var(--fg);
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.05s ease, background 0.1s ease;
}
#image-viewer .iv-action:active {
  transform: translateY(1px);
}
#image-viewer .iv-action svg {
  width: 16px;
  height: 16px;
  display: block;
}
#chat img {
  cursor: zoom-in;
}

/* ===== Modal ===== */
#modal {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.7);
  display: flex; align-items: center; justify-content: center;
  z-index: 9500;
  padding: calc(var(--safe-top) + 56px) 16px calc(var(--safe-bot, 0px) + 16px);
}
#modal.hidden { display: none; }
.modal-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  width: 100%;
  max-width: 320px;
  max-height: calc(100dvh - var(--safe-top) - var(--safe-bot, 0px) - 72px);
  padding: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
/* v1151: попап «что делаю» — стекло 1-в-1 как поповер нижних чипов (#chip-popover,
   тот самый «низкое/среднее/.../макс»): мощный блюр 60px + лёгкая прозрачность 40%,
   сквозь которую просвечивает чат. v1150 заливал карточку мутным серым
   (color-mix bg-card 70% + слабый blur20) и из-за id-специфичности перебивал даже
   штатное glass-стекло модалок (rgba(22,22,22,.70) blur30) — Кирилл: «серость
   уродливая, сделай как у попапа Макс/обычный». Берём ровно параметры #chip-popover
   (glass dark/light — отдельно). Вне glass-темы стекла нет (нечему просвечивать) —
   карточка остаётся обычной непрозрачной, без серой мути. Скоуп .modal-glass:
   confirm-диалоги и меню не трогаем. */
#modal.modal-glass { background: rgba(0, 0, 0, 0.32); }
:root.theme-glass #modal.modal-glass .modal-card {
  background: rgba(13, 13, 13, 0.40);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 16px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
  -webkit-backdrop-filter: saturate(180%) blur(60px);
  backdrop-filter: saturate(180%) blur(60px);
}
:root.theme-glass.theme-light #modal.modal-glass .modal-card {
  background: rgba(245, 243, 238, 0.38);
  border: 1px solid rgba(0, 0, 0, 0.12);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.10);
}
.modal-header {
  display: flex; align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--fg);
  font-size: 12px;
  flex: 0 0 auto;
  gap: 8px;
}
.modal-header > span {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.modal-header > button {
  flex-shrink: 0;
}
.modal-body {
  padding: 14px 16px 16px;
  display: flex; flex-direction: column; gap: 8px;
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
}
/* Прямые дети модалки не должны сжиматься: в колоночном flex длинный список
   (десятки голосов) включает flex-shrink и давит кнопки по высоте ниже текста,
   а glass overflow:hidden (для ripple) обрезает вылезшие глифы. */
.modal-body > * { flex-shrink: 0; }
.modal-body button {
  display: block; width: 100%;
  text-align: left;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  padding: 8px 10px;
  font-family: inherit; font-size: 13px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.modal-body button:active { background: var(--bg-card); }
.modal-body button .menu-ico { vertical-align: -2px; margin-right: 4px; }
.modal-body label.modal-pick {
  display: block; width: 100%;
  text-align: left;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  padding: 8px 10px;
  font-family: inherit; font-size: 13px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.modal-body label.modal-pick:active { background: var(--bg-card); }
.modal-body label.modal-pick .menu-ico { vertical-align: -2px; margin-right: 4px; }
.modal-body button.active {
  border-color: var(--accent);
  color: var(--accent);
}
.msg-react-bar {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  margin-bottom: 0;
  overflow-x: auto;
  overflow-y: hidden;
  flex-wrap: nowrap;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding: 2px 0;
}
.msg-react-bar::-webkit-scrollbar { display: none; }
.modal-body .msg-react-bar .msg-react-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 36px;
  width: 36px;
  height: 36px;
  padding: 0;
  line-height: 1;
  text-align: center;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--fg);
}
.modal-body .msg-react-bar .msg-react-btn svg {
  width: 20px;
  height: 20px;
  display: block;
}
.modal-body .msg-react-bar .msg-react-btn.active {
  background: var(--bg-card);
  border-color: var(--accent);
  color: var(--accent);
}
.msg-reaction {
  position: absolute;
  right: 6px;
  bottom: -8px;
  background: var(--bg-soft);
  border: 1px solid var(--border);
  padding: 2px 4px;
  line-height: 0;
  pointer-events: none;
  z-index: 2;
  color: var(--accent);
}
.msg-reaction svg {
  width: 14px;
  height: 14px;
  display: block;
}
.modal-body button.btn-rich {
  display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
  padding: 8px 10px;
}
.modal-body button.btn-rich .btn-rich-title {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.2;
}
.modal-body button.btn-rich .btn-rich-desc {
  font-size: 11px;
  color: var(--fg-dim);
  line-height: 1.45;
  font-weight: 400;
}
.modal-body button.btn-rich.active .btn-rich-title { color: var(--accent); }
.modal-body button.btn-rich.active .btn-rich-desc  { color: var(--fg-dim); }

.theme-modal-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.theme-modal-section + .theme-modal-section { margin-top: 10px; }
.theme-modal-section-title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--fg-dim);
  padding: 0 2px 2px;
  line-height: 1.2;
  opacity: 0.7;
}

.vercel-quota-modal {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  padding: 4px 2px 2px;
}
.vercel-quota-big {
  text-align: center;
  font-size: 56px;
  font-weight: 800;
  color: var(--fg);
  letter-spacing: -1px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  padding-top: 4px;
}
.vercel-quota-of {
  font-size: 22px;
  font-weight: 600;
  color: var(--fg-dim);
  letter-spacing: 0;
}
.vercel-quota-sub {
  text-align: center;
  font-size: 11px;
  color: var(--fg-dim);
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.vercel-quota-bar {
  position: relative;
  height: 6px;
  background: var(--border);
  border: 0;
  overflow: hidden;
  margin: 4px 0;
}
.vercel-quota-bar-fill {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--accent);
  transition: width 200ms ease-out;
}
.vercel-quota-bar-fill.warn { background: #ff9933; }
.vercel-quota-bar-fill.crit { background: #ff5a7e; }
.vercel-quota-line {
  font-size: 13px;
  color: var(--fg-dim);
  line-height: 1.5;
  padding: 0 4px;
}
.vercel-quota-line b {
  color: var(--fg);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.vercel-quota-hint {
  font-size: 11px;
  color: var(--fg-dim);
  line-height: 1.45;
  text-align: center;
  padding: 4px 4px 0;
  opacity: 0.8;
}
.vercel-quota-err {
  font-size: 13px;
  color: #ff5a7e;
  text-align: center;
  padding: 16px 4px;
  word-break: break-word;
}

.versions-modal {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 4px 0 8px;
}
.versions-modal-sub {
  font-size: 12px;
  opacity: 0.7;
  padding: 0 4px 6px;
}
.versions-more-btn {
  display: block; width: 100%; padding: 9px 12px;
  border: 1px dashed var(--border); border-radius: 12px;
  background: transparent; color: var(--fg-dim);
  font-family: inherit; font-size: 12px; letter-spacing: 0.5px;
  cursor: pointer; transition: background 160ms ease, color 160ms ease;
}
.versions-more-btn:hover { background: rgba(255, 255, 255, 0.04); color: var(--fg); }
.versions-more-btn:active { transform: scale(0.985); }
.versions-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
  color: inherit;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 160ms ease, transform 120ms ease;
}
.versions-row:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.05);
}
.versions-row:active:not(:disabled) {
  transform: scale(0.985);
}
.versions-row:disabled {
  opacity: 0.55;
  cursor: default;
}
.versions-row.active {
  border-color: var(--accent, #4caf50);
  background: rgba(76, 175, 80, 0.08);
}
/* v1024: hold-to-confirm отката версии — те же ощущения, что у qr-btn.
   Зажать строку ~0.9с (--hp 0→1 заливает ::after accent'ом), на 100% — снап
   (qrSnap, общий с qr-btn) и откат. Отпустить раньше — fade-out. */
.versions-row { position: relative; overflow: hidden; }
.versions-row::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--accent, #4caf50);
  pointer-events: none;
  transform-origin: left center;
  transform: scaleX(var(--hp, 0));
  opacity: calc(0.18 + var(--hp, 0) * 0.55);
  transition: transform 0.14s linear, opacity 0.14s linear;
  z-index: 0;
}
.versions-row.versions-row-holding:not(:disabled) {
  transform: scale(1.015);
}
.versions-row.versions-row-holding::after {
  transition: transform 60ms linear, opacity 60ms linear;
}
.versions-row > * { position: relative; z-index: 1; }
.versions-row.versions-row-snap {
  animation: qrSnap 0.34s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.versions-row-left {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
.versions-row-tag {
  font-weight: 600;
  font-size: 14px;
}
.versions-row-sub {
  font-size: 11px;
  opacity: 0.7;
  overflow: hidden;
  /* Описание «что поменял» — до 2 строк, дальше многоточие. Раньше резалось
     в одну строку (white-space: nowrap) и сабж был бесполезным огрызком. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  line-height: 1.3;
}
.versions-row-right {
  font-size: 12px;
  opacity: 0.8;
  white-space: nowrap;
}
.versions-reload-btn {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px;
  margin-bottom: 8px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
  color: inherit;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 160ms ease, transform 120ms ease, border-color 160ms ease;
}
.versions-reload-btn:hover { background: rgba(255, 255, 255, 0.07); }
.versions-reload-btn:active { transform: scale(0.985); }
.versions-reload-btn.has-pending {
  border-color: var(--color-warn, #ff9800);
  background: rgba(255, 152, 0, 0.10);
}
.versions-reload-title {
  font-weight: 600;
  font-size: 14px;
}
.versions-reload-btn.has-pending .versions-reload-title {
  color: var(--color-warn, #ff9800);
}
.versions-reload-sub {
  font-size: 11px;
  opacity: 0.7;
}
.versions-deploy-head {
  font-size: 14px;
  font-weight: 600;
  padding: 4px 4px 6px;
}
.versions-deploy-log {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: rgba(0, 0, 0, 0.2);
  min-height: 80px;
  white-space: pre-wrap;
  word-break: break-word;
}
.versions-deploy-note {
  font-size: 11px;
  opacity: 0.7;
  padding: 8px 4px 0;
}

.theme-schedule-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 12px;
  margin-top: 6px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
}
.theme-schedule-form[hidden] { display: none; }
.theme-schedule-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 12px;
  color: var(--fg-dim);
}
.theme-schedule-label { letter-spacing: 0.3px; }
.theme-schedule-input {
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 4px 10px;
  font-family: inherit;
  font-size: 13px;
  min-width: 100px;
  text-align: center;
}
.theme-schedule-input::-webkit-calendar-picker-indicator { filter: invert(0.5); }
.theme-light .theme-schedule-form { background: rgba(0, 0, 0, 0.03); }
.theme-light .theme-schedule-input::-webkit-calendar-picker-indicator { filter: none; }
/* В Ретро убираем скругления и rgba-подложку — должно повторять стиль
   соседних кнопок темы (острые углы, var(--bg)/var(--border)). */
:root:not(.theme-glass) .theme-schedule-form {
  background: var(--bg);
  border-radius: 0;
  padding: 8px 10px;
  gap: 6px;
  margin-top: 4px;
}
:root:not(.theme-glass) .theme-schedule-input {
  border-radius: 0;
  background: transparent;
}

/* ===== Toast ===== */
#toast {
  position: fixed;
  left: 50%; transform: translateX(-50%) translateY(-6px);
  top: calc(60px + var(--safe-top));
  background: var(--bg-card);
  border: 1px solid var(--border);
  color: var(--fg);
  padding: 6px 10px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 1px;
  z-index: 200;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s cubic-bezier(0.4,0,0.2,1), transform 0.2s cubic-bezier(0.4,0,0.2,1);
}
#toast.toast-visible { opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto; }
#toast.hidden { display: none; }

#thread-done-pop {
  position: fixed;
  left: 50%;
  top: calc(56px + var(--safe-top, 0px));
  background: var(--bg-card);
  border: 1px solid var(--accent);
  border-left: 3px solid var(--accent);
  color: var(--fg);
  padding: 10px 14px;
  font-size: 13px;
  z-index: 250;
  cursor: pointer;
  max-width: 80vw;
  width: 320px;
  box-shadow: 3px 3px 0 rgba(0,0,0,0.7);
  display: flex;
  flex-direction: column;
  gap: 4px;
  transform: translateX(-50%);
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}
#thread-done-pop.hidden { display: none; }
#thread-done-pop.is-error { border-color: #d04040; border-left-color: #d04040; }
#thread-done-pop.is-stopped { border-color: var(--fg-dim); border-left-color: var(--fg-dim); }
.thread-done-pop__title { color: var(--accent); font-weight: bold; }
#thread-done-pop.is-error .thread-done-pop__title { color: #ff6060; }
#thread-done-pop.is-stopped .thread-done-pop__title { color: var(--fg); }
.thread-done-pop__preview {
  color: var(--fg-dim);
  font-size: 12px;
  line-height: 1.35;
  white-space: normal;
  word-break: break-word;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-top: 2px;
}
.thread-done-pop__sub { color: var(--fg-dim); font-size: 11px; text-transform: uppercase; letter-spacing: 1px; margin-top: 2px; }
#thread-done-pop.is-entering { animation: thread-pop-in 220ms ease-out; }
#thread-done-pop.is-leaving { animation: thread-pop-out 220ms ease-in forwards; }
#thread-done-pop.is-dragging {
  animation: none !important;
  transition: none !important;
  transform: translate(calc(-50% + var(--drag-x, 0px)), 0);
  opacity: var(--drag-opacity, 1);
}
@keyframes thread-pop-in {
  from { opacity: 0; transform: translate(-50%, -16px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
@keyframes thread-pop-out {
  from { opacity: 1; transform: translate(-50%, 0); }
  to   { opacity: 0; transform: translate(calc(-50% - 140%), 0); }
}

/* ===== Empty state ===== */
.empty-hint {
  align-self: center;
  color: var(--fg-dim);
  font-size: 12px;
  text-align: center;
  margin-top: 24px;
  padding: 8px 16px;
  border: 1px dashed var(--border);
}
.empty-hint .big {
  display: block;
  font-size: 28px;
  color: var(--fg);
  margin-bottom: 8px;
  letter-spacing: 4px;
}
.empty-hint .empty-unread {
  margin-top: 14px;
  padding: 6px 10px;
  border: 1px dashed var(--border);
  color: var(--fg);
  font-size: 12px;
  letter-spacing: 1px;
}

/* ===== Start page (gemini-style) ===== */
.start-page {
  align-self: stretch;
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
  padding: 32px 16px 24px;
  gap: 0;
}
.start-hero {
  display: flex;
  justify-content: center;
  align-self: center;
  width: 100%;
  margin: 8px 0 18px;
  animation: hero-bob 2.6s ease-in-out infinite;
}
.hero-svg {
  width: 168px;
  height: 140px;
  display: block;
  margin: 0 auto;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  filter: drop-shadow(0 6px 0 rgba(0, 0, 0, 0.18));
}
.hero-body { fill: var(--accent); }
.hero-eye  {
  fill: #141414;
  transform-box: fill-box;
  transform-origin: center;
  animation: hero-blink 5.4s infinite;
}
:root.theme-light .hero-eye { fill: #1c1b19; }
:root.theme-glass .hero-eye { fill: #0e0e16; }
@keyframes hero-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-5px); }
}
@keyframes hero-blink {
  0%, 92%, 100% { transform: scaleY(1); }
  95%, 96%      { transform: scaleY(0.05); }
}
.start-big {
  font-size: 32px;
  color: var(--accent);
  text-align: center;
  line-height: 1;
  margin-bottom: 14px;
}
.start-big__mark {
  display: inline-block;
  width: 0.32em;
  height: 0.72em;
  background: currentColor;
  vertical-align: baseline;
  margin-right: 0.32em;
}
.start-big__text {
  letter-spacing: 6px;
}
.start-greet {
  font-size: 14px;
  color: var(--fg);
  text-align: center;
  letter-spacing: 3px;
  margin-bottom: 6px;
}
.start-hint {
  font-size: 11px;
  color: var(--fg-dim);
  text-align: center;
  letter-spacing: 1px;
  margin-bottom: 28px;
}

/* Когда пользователь начинает печатать на стартовой странице (есть текст в
   #input) — гасим маскот и оба заголовка («▮ МАККОД», «С ЧЕГО НАЧНЁМ?»),
   чтобы не отвлекали. Если стереть текст — возвращаются. Ретро-тема
   гасит «пиксельно» (steps), стеклянная — плавно с лёгким blur'ом. */
.start-hero,
.start-big,
.start-greet {
  transition: opacity 240ms steps(5, end);
  will-change: opacity;
}
body:has(.input-wrap.has-content) .start-hero,
body:has(.input-wrap.has-content) .start-big,
body:has(.input-wrap.has-content) .start-greet {
  opacity: 0;
  pointer-events: none;
}
:root.theme-glass .start-hero,
:root.theme-glass .start-big,
:root.theme-glass .start-greet {
  transition: opacity 320ms cubic-bezier(0.4, 0, 0.2, 1),
              filter 320ms cubic-bezier(0.4, 0, 0.2, 1),
              transform 320ms cubic-bezier(0.4, 0, 0.2, 1);
}
:root.theme-glass body:has(.input-wrap.has-content) .start-hero,
:root.theme-glass body:has(.input-wrap.has-content) .start-big,
:root.theme-glass body:has(.input-wrap.has-content) .start-greet {
  filter: blur(10px);
  transform: translateY(-6px);
}
.start-health {
  margin: 18px 12px 0;
  border: 1px dashed var(--border);
  background: var(--bg-card);
  font-family: inherit;
}
.start-health-head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: transparent;
  border: 0;
  color: var(--fg);
  font-family: inherit;
  font-size: 12px;
  letter-spacing: 1.5px;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.start-health-head.has-fail {
  color: #ff8080;
}
.start-health-arrow {
  margin-left: auto;
  color: var(--fg-dim);
  font-size: 10px;
}
.start-health-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  flex: 0 0 8px;
  border-radius: 0;
}
.start-health-dot.status-ok { background: #2bb24c; }
.start-health-dot.status-fail { background: #d94343; }
.start-health-dot.status-warn { background: #f0b429; }
.start-health-dot.status-stopped { background: #888; }
.start-health-list {
  border-top: 1px dashed var(--border);
}
.start-health-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 12px;
  font-size: 11px;
  color: var(--fg);
  flex-wrap: wrap;
}
.start-health-name {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.start-health-time {
  color: var(--fg-dim);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
}
.start-health-detail {
  flex: 1 0 100%;
  margin-left: 18px;
  color: var(--fg-dim);
  font-size: 10px;
}
.start-health-row-expandable {
  cursor: pointer;
  user-select: none;
}
.start-health-row-expandable:active {
  background: rgba(255,255,255,0.04);
}
.start-health-badge {
  font-size: 10px;
  color: var(--fg-dim);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.5px;
}
.start-health-chev {
  font-size: 14px;
  color: var(--fg-dim);
  line-height: 1;
  margin-left: 2px;
  transition: transform 0.15s ease;
  display: inline-block;
}
.start-health-chev.open {
  transform: rotate(90deg);
}
.start-health-sub {
  border-top: 1px dashed var(--border);
  border-bottom: 1px dashed var(--border);
  padding: 4px 0;
  background: rgba(255,255,255,0.02);
}
.start-health-sub.hidden { display: none; }
.start-health-subrow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 12px 4px 24px;
  font-size: 10px;
  color: var(--fg);
}
.start-health-subname {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--fg-dim);
}
.start-health-subunseen {
  font-size: 10px;
  color: var(--fg);
  font-variant-numeric: tabular-nums;
}
.start-health-suberror {
  font-size: 9px;
  color: #ff8080;
  max-width: 50%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.start-health-sep {
  padding: 6px 12px;
  font-size: 10px;
  color: var(--fg-dim);
  letter-spacing: 1px;
  text-align: center;
  border-top: 1px dashed var(--border);
}

/* health-page overlay (страница «Сервисы и скиллы») */
.health-page .settings-scroll { padding: 8px 0 24px; }
.health-empty {
  padding: 24px 12px;
  text-align: center;
  font-size: 12px;
  color: var(--fg-dim);
  letter-spacing: 1px;
}

/* news-page overlay (страница «Новости») */
.news-page .settings-scroll { padding: 12px 12px 24px; }
.news-empty {
  padding: 24px 12px;
  text-align: center;
  font-size: 12px;
  color: var(--fg-dim);
  letter-spacing: 1px;
}
.news-empty.hidden { display: none; }
#news-list { display: flex; flex-direction: column; gap: 10px; }
.news-card {
  position: relative;
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
  background: transparent;
}
.news-card-unseen { border-color: var(--accent); }
.news-card-unseen::before {
  content: "";
  position: absolute;
  top: 13px; right: 13px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--accent);
}
.news-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}
.news-tag {
  font-size: 10px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 6px;
  padding: 1px 6px;
  white-space: nowrap;
}
.news-card-high .news-tag { background: var(--accent); color: var(--bg); }
.news-date { font-size: 11px; color: var(--fg-dim); }
.news-card-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--fg);
  margin-bottom: 4px;
  line-height: 1.3;
}
.news-card-body { font-size: 13px; color: var(--fg-dim); line-height: 1.45; }
.news-card-link {
  display: inline-block;
  margin-top: 8px;
  font-size: 13px;
  color: var(--accent);
  text-decoration: none;
}
.news-card-link:active { opacity: 0.6; }
.news-card-foot { display: flex; align-items: center; gap: 16px; margin-top: 8px; }
.news-card-foot .news-card-link { margin-top: 0; }
.news-card-ask {
  font-size: 13px; color: var(--accent); background: transparent;
  border: none; padding: 0; font-family: inherit; cursor: pointer;
}
.news-card-ask:active { opacity: 0.6; }

/* ── Лента уведомлений — лёгкие строки без обводок, как список чатов ──── */
#notif-list { display: flex; flex-direction: column; gap: 0; }
.notif-swipe-wrap {
  position: relative;
  overflow: hidden;
  border-radius: 0;
}
/* Маяк ленивой ленты: невидимая полоска у низа списка. Когда уезжает в зону
   видимости (rootMargin 500px) — IntersectionObserver досыпает следующую пачку. */
.notif-sentinel {
  height: 1px;
  width: 100%;
  pointer-events: none;
}
.notif-mark-act {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 56px;
  background: var(--accent, #d97757);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; user-select: none; -webkit-user-select: none;
}
.notif-mark-act:active { opacity: 0.8; }
.notif-mark-act svg { display: block; }
/* Строка уведомления: без рамок и карточек — круглый аватар-иконка слева,
   заголовок + время, превью второй строкой. Один в один как .thread-item. */
.notif-row {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 11px;
  border: none;
  border-radius: 0;
  padding: 11px 14px;
  background: var(--bg);
  cursor: pointer;
  will-change: transform;
}
.notif-row:active { background: color-mix(in srgb, var(--fg) 6%, var(--bg)); }
.notif-row:not(.notif-row-unseen) { opacity: 0.6; }
.notif-avatar {
  flex: none;
  width: 28px; height: 28px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
  margin-top: 1px;
}
.notif-avatar svg { width: 16px; height: 16px; display: block; }
.notif-row-bad .notif-avatar {
  background: color-mix(in srgb, var(--red, #d96e6e) 16%, transparent);
  color: var(--red, #d96e6e);
}
.notif-main { flex: 1 1 auto; min-width: 0; padding-top: 1px; }
.notif-title-row { display: flex; align-items: baseline; gap: 8px; }
.notif-title {
  flex: 1 1 auto; min-width: 0;
  font-size: 14px; font-weight: 600; color: var(--fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.notif-row-unseen .notif-title { font-weight: 700; }
.notif-time { flex: none; font-size: 11px; color: var(--fg-dim); }
.notif-sub {
  margin-top: 2px;
  font-size: 12.5px; color: var(--fg-dim); line-height: 1.4;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Фильтр сверху: Все / Непрочитанные / Прочитанные — сегмент-бар. */
.notif-filter {
  display: flex;
  gap: 6px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
}
.notif-filter-btn {
  flex: 1 1 auto;
  padding: 6px 4px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  color: var(--fg-dim);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}
.notif-filter-btn.active {
  color: var(--accent);
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.notif-filter-btn:active { opacity: 0.7; }
.drawer-notif-toggle { position: relative; }
.drawer-notif-dot {
  position: absolute;
  top: -4px; right: -4px;
  min-width: 16px; height: 16px;
  box-sizing: border-box;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  pointer-events: none;
}
.drawer-notif-dot.hidden { display: none; }

.row-value-accent { color: var(--accent) !important; font-weight: 600; }
.health-summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  font-size: 12px;
  letter-spacing: 1.5px;
  border-bottom: 1px dashed var(--border);
}
.health-summary.has-fail { color: #ff8080; }
.health-summary.has-warn { color: #f0b429; }
.health-summary-text { flex: 1 1 auto; }

/* badge на иконке drawer-foot */
.drawer-foot-icon { position: relative; }
.drawer-foot-badge {
  position: absolute;
  top: 12px;
  right: 3px;
  min-width: 11px;
  height: 11px;
  padding: 0 3px;
  background: #d94343;
  color: #fff;
  border-radius: 999px;
  font-size: 8px;
  line-height: 11px;
  text-align: center;
  font-weight: 700;
  letter-spacing: 0;
  pointer-events: none;
}
.drawer-foot-badge.hidden { display: none; }
.start-section {
  font-size: 11px;
  color: var(--fg-dim);
  letter-spacing: 2px;
  padding: 6px 4px;
  border-bottom: 1px dashed var(--border);
  margin-bottom: 6px;
}
.start-section-clickable {
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.12s ease;
}
.start-section-clickable:active {
  color: var(--fg);
}
/* «Прочесть всё» — маленькая галочка справа в заголовке секции.
   Кликабельна отдельно от заголовка (stopPropagation в JS), чтобы тап по
   галочке не открывал страницу рутин, а только гасил счётчик. */
.start-section-mark-all {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: 8px;
  width: 16px;
  height: 16px;
  font-size: 12px;
  font-weight: 700;
  color: var(--accent);
  border: 1px solid var(--border);
  border-radius: 50%;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  -webkit-user-select: none;
  transition: background 0.12s ease;
}
.start-section-mark-all:active {
  background: var(--accent);
  color: var(--bg);
}
/* Упавшие сервисы — заголовок-плашка в drawer-home с акцентным цветом
   (как раздел «непрочитанные», только сигнальный — пора смотреть). */
.start-section.start-section-fails {
  color: var(--accent);
  border-bottom-color: var(--accent);
}
/* Непрочитанная отработавшая автоматизация — чуть ярче, с левой полоской
   акцентного цвета: тот же приём, что у непрочитанных чатов. */
.start-busy-row.is-unread-run {
  position: relative;
}
.start-busy-row.is-unread-run::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6px;
  bottom: 6px;
  width: 3px;
  background: var(--accent);
  border-radius: 2px;
}
.start-busy-row.is-unread-run .start-busy-title {
  color: var(--fg);
}
/* Свайп для строк автоматизаций — обёртка та же, что у чатов
   (.start-row-swipe), плюс псевдо-класс для специфичных правок. */
.start-busy-swipe-wrap {
  position: relative;
  overflow: hidden;
}

/* ===== Carta: токены Клода (session 5h + weekly 7d) ===== */
.claude-usage-host {
  box-sizing: border-box;
  width: 100%;
}
.claude-usage-card {
  border: 1px solid var(--border);
  background: var(--bg-card);
  padding: 10px 12px 12px;
  margin: 0 0 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-family: var(--font-ui);
  box-sizing: border-box;
  width: 100%;
}
.claude-usage-card .uc-title {
  font-size: 10px;
  letter-spacing: 2px;
  color: var(--fg-dim);
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px dashed var(--border);
  padding: 0 0 6px;
  background: transparent;
  border-top: none;
  border-left: none;
  border-right: none;
  width: 100%;
  font-family: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.claude-usage-card .uc-title:active { opacity: 0.6; }
.claude-usage-card .uc-collapse-ind {
  font-size: 12px;
  color: var(--fg-dim);
  line-height: 1;
}
.claude-usage-card .uc-title-hint {
  color: var(--fg-dim);
  letter-spacing: 0.5px;
  font-size: 9px;
  text-transform: lowercase;
}
.claude-usage-card .uc-sec {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.claude-usage-card .uc-sec-head {
  font-size: 11px;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}
.claude-usage-card .uc-sec-title {
  color: var(--fg);
  letter-spacing: 1px;
}
.claude-usage-card .uc-sec-reset {
  color: var(--fg-dim);
  font-size: 10px;
  letter-spacing: 1px;
}
.claude-usage-card .uc-sec-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 12px;
}
.claude-usage-card .uc-pct {
  font-size: 18px;
  letter-spacing: 1px;
  color: var(--fg);
}
.claude-usage-card .uc-used {
  color: var(--fg-dim);
  letter-spacing: 0.5px;
}
.claude-usage-card .uc-bar {
  position: relative;
  height: 6px;
  background: var(--bg);
  border: 1px solid var(--border);
  overflow: hidden;
}
.claude-usage-card .uc-bar-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0%;
  background: var(--accent, #ffb454);
  transition: width 0.4s ease;
}
.claude-usage-card .uc-sec.is-warn .uc-bar-fill { background: var(--orange, #ffb454); }
.claude-usage-card .uc-sec.is-crit .uc-bar-fill { background: var(--red, #ff6a6a); }
.claude-usage-card .uc-sec.is-warn .uc-pct { color: var(--orange, #ffb454); }
.claude-usage-card .uc-sec.is-crit .uc-pct { color: var(--red, #ff6a6a); }
.claude-usage-card .uc-sec.is-empty .uc-pct { color: var(--fg-dim); }

.claude-usage-card .uc-rows {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 14px;
  border-top: 1px dashed var(--border);
  padding-top: 8px;
  font-size: 12px;
}
.claude-usage-card .uc-row-label {
  color: var(--fg-dim);
  letter-spacing: 1px;
}
.claude-usage-card .uc-row-value {
  text-align: right;
  color: var(--fg);
}
.claude-usage-card .uc-row-value-dim {
  color: var(--fg-dim);
}
.claude-usage-card .uc-foot {
  border-top: 1px dashed var(--border);
  padding-top: 6px;
  font-size: 11px;
  color: var(--fg-dim);
  display: flex;
  justify-content: space-between;
  letter-spacing: 1px;
}

/* OpenRouter ($) — расход не-Anthropic моделей (Kimi) через ccr */
.claude-usage-card .uc-openrouter {
  border-top: 1px dashed var(--border);
  padding-top: 8px;
  display: grid;
  gap: 6px;
}
.claude-usage-card .uc-or-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 11px;
  letter-spacing: 1px;
  color: var(--fg);
}
.claude-usage-card .uc-or-remain {
  font-size: 12px;
  font-weight: 600;
  color: var(--accent, #ffb454);
  letter-spacing: 0.5px;
}
.claude-usage-card .uc-or-remain.is-warn { color: var(--orange, #ffb454); }
.claude-usage-card .uc-or-remain.is-crit { color: var(--red, #ff6a6a); }
.claude-usage-card .uc-or-rows { border-top: none; padding-top: 0; }

/* compact (свернуто): одна строка с двумя мини-барами */
.claude-usage-card .uc-compact {
  display: none;
  width: 100%;
  background: transparent;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  font-family: inherit;
  color: inherit;
  align-items: center;
  gap: 12px;
  -webkit-tap-highlight-color: transparent;
}
.claude-usage-card.is-collapsed {
  padding: 8px 10px;
  gap: 0;
}
.claude-usage-card.is-collapsed .uc-full { display: none; }
.claude-usage-card.is-collapsed .uc-compact { display: flex; gap: 10px; }
.claude-usage-card .uc-compact:active { opacity: 0.7; }
.claude-usage-card .uc-compact-cell {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  min-width: 0;
}
.claude-usage-card .uc-compact-label {
  font-size: 10px;
  letter-spacing: 0.3px;
  color: var(--fg-dim);
  flex-shrink: 0;
  min-width: 22px;
  white-space: nowrap;
}
/* Кирилл: «оба окна примерно как на айфоне батарейка» — корпус-прямоугольник
   со скруглением, носик справа (::after), заливка по % и число ВНУТРИ корпуса. */
.claude-usage-card .uc-compact-bar {
  position: relative;
  flex: 0 0 auto;
  width: 38px;
  height: 18px;
  background: var(--bg);
  border: 1px solid var(--accent, #ffb454);
  border-radius: 4px;
  min-width: 38px;
}
.claude-usage-card .uc-compact-bar::after {
  content: "";
  position: absolute;
  right: -3px; top: 50%;
  transform: translateY(-50%);
  width: 2px; height: 42%;
  border-radius: 1px;
  background: var(--accent, #ffb454);
}
.claude-usage-card .uc-compact-fill {
  position: absolute;
  inset: 1px auto 1px 1px;
  width: 0%;
  background: var(--accent, #ffb454);
  opacity: 0.30;
  border-radius: 3px;
  transition: width 0.4s ease;
}
.claude-usage-card .uc-compact-cell.is-warn .uc-compact-fill { background: var(--orange, #ffb454); }
.claude-usage-card .uc-compact-cell.is-crit .uc-compact-fill { background: var(--red, #ff6a6a); }
.claude-usage-card .uc-compact-cell.is-warn .uc-compact-bar,
.claude-usage-card .uc-compact-cell.is-warn .uc-compact-bar::after { border-color: var(--orange, #ffb454); background-color: var(--orange, #ffb454); }
.claude-usage-card .uc-compact-cell.is-warn .uc-compact-bar { background: var(--bg); }
.claude-usage-card .uc-compact-cell.is-crit .uc-compact-bar,
.claude-usage-card .uc-compact-cell.is-crit .uc-compact-bar::after { border-color: var(--red, #ff6a6a); background-color: var(--red, #ff6a6a); }
.claude-usage-card .uc-compact-cell.is-crit .uc-compact-bar { background: var(--bg); }
.claude-usage-card .uc-compact-cell.is-empty .uc-compact-bar,
.claude-usage-card .uc-compact-cell.is-empty .uc-compact-bar::after { border-color: var(--border); background-color: var(--border); }
.claude-usage-card .uc-compact-cell.is-empty .uc-compact-bar { background: var(--bg); }
.claude-usage-card .uc-compact-pct {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.2px;
  color: var(--accent, #ffb454);
  font-variant-numeric: tabular-nums;
}
.claude-usage-card .uc-compact-cell.is-warn .uc-compact-pct { color: var(--orange, #ffb454); }
.claude-usage-card .uc-compact-cell.is-crit .uc-compact-pct { color: var(--red, #ff6a6a); }
.claude-usage-card .uc-compact-cell.is-empty .uc-compact-pct { color: var(--fg-dim); }
.claude-usage-card .uc-compact-or {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  white-space: nowrap;
}
.claude-usage-card .uc-compact-or-label {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.3px;
  color: var(--fg-dim);
}
.claude-usage-card .uc-compact-or-val {
  font-size: 11px;
  font-weight: 700;
  color: var(--accent, #ffb454);
  font-variant-numeric: tabular-nums;
}
.claude-usage-card .uc-compact-or-val.is-warn { color: var(--orange, #ffb454); }
.claude-usage-card .uc-compact-or-val.is-crit { color: var(--red, #ff6a6a); }
.claude-usage-card .uc-compact-chev {
  display: none;
}
@keyframes uc-skeleton-shine {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.claude-usage-card.is-loading .uc-compact-label,
.claude-usage-card.is-loading .uc-compact-pct {
  color: transparent;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--bg) 0%, var(--border) 50%, var(--bg) 100%);
  background-size: 200% 100%;
  animation: uc-skeleton-shine 1.4s linear infinite;
}
.claude-usage-card.is-loading .uc-compact-bar {
  background: linear-gradient(90deg, var(--bg) 0%, var(--border) 50%, var(--bg) 100%);
  background-size: 200% 100%;
  animation: uc-skeleton-shine 1.4s linear infinite;
  border-color: transparent;
}
.claude-usage-card.is-loading .uc-compact-fill {
  display: none;
}
.claude-usage-card .uc-full {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.claude-usage-card .uc-collapse-btn {
  background: transparent;
  border: none;
  color: var(--fg-dim);
  font-size: 16px;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.claude-usage-card .uc-collapse-btn:active { opacity: 0.6; }

.start-unread-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.start-unread-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  background: transparent;
  color: var(--fg);
  border: 1px dashed transparent;
  padding: 8px 8px;
  font-family: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  letter-spacing: 0.5px;
}
.start-unread-row:active {
  border-color: var(--accent);
  color: var(--accent);
}
.start-unread-row.current {
  border-color: var(--accent);
  background: rgba(217, 119, 87, 0.06);
  color: var(--accent);
}
.start-unread-title {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.start-unread-time {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--fg-dim);
  letter-spacing: 1px;
}
.start-no-unread {
  font-size: 11px;
  color: var(--fg-dim);
  text-align: center;
  letter-spacing: 1px;
  padding: 12px 0;
  border: 1px dashed var(--border);
}
.start-busy-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 16px;
}
.start-busy-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  background: none;
  color: var(--fg);
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  letter-spacing: 0.5px;
}
.start-busy-row:active {
  border-color: var(--accent);
  color: var(--accent);
}
.start-busy-row.current {
  border-color: var(--accent);
  background: rgba(217, 119, 87, 0.06);
  color: var(--accent);
}
.start-busy-title {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.start-busy-time {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--fg-dim);
  letter-spacing: 1px;
}

/* ===== Drawer (треды) ===== */
#drawer {
  position: fixed;
  top: 0; bottom: 0; left: 0;
  width: var(--drawer-width);
  max-width: 86vw;
  background: var(--bg-card);
  border-right: 1px solid var(--border);
  z-index: 150;
  display: flex; flex-direction: column;
  padding-top: var(--safe-top);
  padding-bottom: 0;
  padding-left: var(--safe-left);
  transform: translateX(0);
  transition: transform 0.32s cubic-bezier(.22,.61,.36,1);
  box-shadow: 4px 0 16px rgba(0,0,0,0.4);
}
#drawer.hidden {
  transform: translateX(-110%);
  pointer-events: none;
}
/* Шторка в Ретро — серый/тёмно-серый, явно контрастирует с фоном чата
   (--bg). Не белый/чёрный, чтобы граница drawer/chat читалась без рамки.
   Glass-тема свои overrides ниже, её не трогаем. */
:root:not(.theme-glass) {
  --drawer-bg: #3a3935;
}
:root:not(.theme-glass).theme-light {
  --drawer-bg: #dcdad4;
}
:root:not(.theme-glass) #drawer,
:root:not(.theme-glass) .drawer-sticky-shared,
:root:not(.theme-glass) .drawer-footer {
  background: var(--drawer-bg);
}
/* Из всего drawer прозрачной оставляем только липкую шапку (счётчики/табы).
   Поиск (.thread-search), панель токенов (.claude-usage-card) и чаты
   (.thread-item) — карточки на var(--bg-card), как до v688. */
:root:not(.theme-glass) #drawer .thread-sticky-top {
  background: transparent;
}
#drawer-resize {
  display: none;
  position: absolute;
  top: 0; bottom: 0; right: -3px;
  width: 8px;
  cursor: col-resize;
  z-index: 200;
  touch-action: none;
}
body.is-desktop #drawer-resize {
  display: block;
}
#drawer-resize::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  left: 3px;
  width: 1px;
  background: transparent;
  transition: background 0.15s ease;
}
#drawer-resize:hover::after,
#drawer.resizing #drawer-resize::after {
  background: var(--accent-dim);
}
#drawer.resizing,
#drawer.resizing ~ * {
  user-select: none;
}
#drawer.resizing #drawer-resize {
  cursor: col-resize;
}
.drawer-header {
  display: flex; align-items: center;
  justify-content: space-between;
  gap: 0;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}
.drawer-title {
  color: var(--fg);
  font-weight: 700;
  letter-spacing: 1px;
  font-size: 12px;
  text-transform: uppercase;
  flex: none;
  min-width: 76px;
  white-space: nowrap;
}
.drawer-title.is-fading { opacity: 0; }
.drawer-header-icon {
  appearance: none; -webkit-appearance: none;
  background: transparent;
  color: var(--fg);
  border: 0;
  border-radius: 0;
  padding: 0;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: transform 0.05s ease, opacity 0.1s ease;
  opacity: 0.7;
}
.drawer-header-icon svg { width: 16px; height: 16px; display: block; }
.drawer-header-icon:hover { opacity: 1; }
.drawer-header-icon:active { transform: translateY(1px); }
.drawer-sticky-shared {
  flex: none;
  background: var(--bg-card);
  padding: 4px 8px 0 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.drawer-sticky-shared > .claude-usage-host { padding: 0; }
.drawer-sticky-shared .claude-usage-card { margin: 0; }
.drawer-sticky-shared > .thread-search { margin: 0; }
.drawer-sticky-shared > .thread-search-status { margin: 0; }
.thread-sticky-top {
  background: var(--bg-soft);
}
.thread-search {
  position: relative;
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 4px 8px 4px 8px;
  border: 1px solid var(--border);
  background: var(--bg-card);
  padding: 0 6px 0 8px;
  min-height: 34px;
}
.thread-search-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 14px; height: 14px;
  color: var(--fg-dim, var(--fg));
  opacity: 0.6;
  flex: none;
}
.thread-search-icon svg { width: 14px; height: 14px; display: block; }
.thread-search-input {
  flex: 1 1 auto;
  appearance: none; -webkit-appearance: none;
  background: transparent;
  border: 0;
  outline: 0;
  color: var(--fg);
  font-family: inherit;
  font-size: 12px;
  letter-spacing: 0.3px;
  padding: 6px 4px;
  min-width: 0;
}
.thread-search-input::placeholder {
  color: var(--fg);
  opacity: 0.45;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-size: 11px;
}
.thread-search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.thread-search-clear {
  appearance: none; -webkit-appearance: none;
  background: transparent;
  border: 0;
  color: var(--fg);
  opacity: 0.55;
  cursor: pointer;
  font-size: 12px;
  padding: 2px 6px;
  flex: none;
}
.thread-search-clear:active { opacity: 1; }
.thread-search-ai-btn {
  appearance: none; -webkit-appearance: none;
  background: transparent;
  border: 0;
  border-left: 1px solid var(--border);
  color: var(--accent);
  cursor: pointer;
  padding: 4px 6px 4px 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  min-height: 28px;
}
.thread-search-ai-btn svg { width: 14px; height: 14px; display: block; }
.thread-search-ai-btn:active { transform: translateY(1px); }
.thread-search-ai-btn.is-loading {
  animation: thread-ai-pulse 1s ease-in-out infinite;
}
.thread-search-ai-btn.is-active {
  background: var(--accent);
  color: #000;
}
@keyframes thread-ai-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}
.thread-search-status {
  margin: 0 8px 4px 8px;
  padding: 6px 8px;
  font-size: 11px;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--fg);
  opacity: 0.7;
  border: 1px dashed var(--border);
  background: transparent;
}
.thread-search-status.is-error { color: #ff6b6b; opacity: 1; border-color: #ff6b6b; }
.thread-search-status-bolt {
  display: inline-block;
  width: 0.95em;
  height: 0.95em;
  vertical-align: -2px;
  margin: 0 1px;
}
.thread-item.with-snippet {
  flex-wrap: wrap;
  padding-bottom: 8px;
  align-items: flex-start;
}
.thread-item.with-snippet .thread-status,
.thread-item.with-snippet .thread-title,
.thread-item.with-snippet .thread-meta,
.thread-item.with-snippet .thread-pin {
  align-self: center;
}
.thread-snippet {
  flex-basis: 100%;
  display: block;
  margin: 4px 0 0 18px;
  font-size: 11px;
  line-height: 1.35;
  letter-spacing: 0.2px;
  color: var(--fg);
  opacity: 0.6;
  text-transform: none;
  font-weight: 400;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.thread-snippet mark,
.thread-title mark {
  background: var(--accent);
  color: #000;
  padding: 0 2px;
}
.thread-ai-reason {
  flex-basis: 100%;
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 3px 0 0 18px;
  font-size: 10px;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  color: var(--accent);
  opacity: 0.85;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.thread-ai-reason::before {
  content: "◆";
  font-size: 7px;
  flex: none;
  opacity: 0.6;
}
.thread-ai-score {
  margin-left: auto;
  opacity: 0.6;
  font-weight: 700;
}
#thread-list {
  list-style: none;
  margin: 0;
  padding: 2px 8px 12px 8px;
}
.drawer-views {
  flex: 1 1 auto;
  position: relative;
  overflow: hidden;
  min-height: 0;
}
.drawer-view {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  transform: translate3d(-100%, 0, 0);
  transition: transform 0.28s cubic-bezier(0.32, 0.72, 0, 1);
  will-change: transform;
}
.drawer-view[data-pos="active"] { transform: translate3d(0, 0, 0); }
.drawer-view[data-pos="exiting"] { transform: translate3d(100%, 0, 0); }
.drawer-views.dragging .drawer-view { transition: none; }
.drawer-home {
  flex: 1 1 auto;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  padding: 2px 8px 14px;
  gap: 2px;
}
.drawer-home::-webkit-scrollbar { width: 4px; }
.drawer-home::-webkit-scrollbar-thumb { background: var(--scroll-thumb); }
.drawer-home-fav-list { margin: 0; }
.scope-chips {
  display: none;
  gap: 6px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  touch-action: pan-x;
  overscroll-behavior-x: contain;
  overscroll-behavior-y: none;
  -webkit-overflow-scrolling: touch;
  padding: 2px 4px 10px;
  margin-bottom: 2px;
}
.scope-chips.is-visible { display: flex; }
.scope-chips::-webkit-scrollbar { display: none; }
.scope-chip {
  flex: 0 0 auto;
  background: transparent;
  color: var(--fg-dim);
  border: 1px dashed var(--border);
  padding: 7px 14px;
  font: inherit;
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  cursor: pointer;
  white-space: nowrap;
}
.scope-chip.is-active {
  color: var(--accent);
  border-color: var(--accent);
  border-style: solid;
}
.scope-chip:active { transform: translateY(1px); }

.search-history {
  display: none;
  flex-direction: column;
  padding: 4px 4px 6px;
  gap: 2px;
}
.search-history.is-visible { display: flex; }
.search-history-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 4px 8px 2px;
  font-size: 10px;
  letter-spacing: 1px;
  color: var(--fg-dim);
  text-transform: uppercase;
}
.search-history-clear {
  background: transparent;
  border: none;
  color: var(--fg-dim);
  cursor: pointer;
  font: inherit;
  font-size: 10px;
  letter-spacing: 1px;
  padding: 2px 4px;
  text-transform: uppercase;
}
.search-history-clear:active { color: var(--accent); }
.search-hist-wrap {
  position: relative;
  overflow: hidden;
}
.search-hist-del {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 56px;
  display: flex; align-items: center; justify-content: center;
  background: #c0392b;
  color: #fff;
  cursor: pointer;
}
.search-hist-del:active { opacity: 0.8; }
.search-history-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 8px;
  background: var(--bg);
  border: 1px dashed transparent;
  cursor: pointer;
  font: inherit;
  color: var(--fg);
  text-align: left;
  width: 100%;
  position: relative;
}
.search-history-row:active { border-color: var(--border); background: rgba(127,127,127,0.06); }
.search-history-ico {
  flex: 0 0 auto;
  width: 12px; height: 12px;
  color: var(--fg-dim);
  display: inline-flex; align-items: center; justify-content: center;
}
.search-history-text {
  flex: 1 1 auto;
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.search-history-rm {
  flex: 0 0 auto;
  background: transparent;
  border: none;
  color: var(--fg-dim);
  cursor: pointer;
  font: inherit;
  font-size: 14px;
  line-height: 1;
  padding: 0 4px;
}
.search-history-rm:active { color: var(--accent); }

.scope-result {
  list-style: none;
  padding: 8px 10px;
  border-bottom: 1px dashed var(--border);
  cursor: pointer;
}
.scope-result:active { background: rgba(127, 184, 127, 0.08); }
.scope-result-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.scope-result-name {
  font-size: 13px;
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1 1 auto;
}
.scope-result-tag {
  flex: 0 0 auto;
  font-size: 9px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--fg-dim);
  border: 1px dashed var(--border);
  padding: 1px 5px;
}
.scope-result-desc {
  margin-top: 4px;
  font-size: 11px;
  color: var(--fg-dim);
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.start-fav-row { color: var(--fg); }
.start-fav-star {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  color: #ff5a7e;
}
.drawer-scroll {
  flex: 1 1 auto;
  overflow-y: scroll;
  scrollbar-gutter: stable;
  display: flex; flex-direction: column;
  min-height: 0;
}
.drawer-scroll::-webkit-scrollbar { width: 4px; }
.drawer-scroll::-webkit-scrollbar-thumb { background: var(--scroll-thumb); }
.thread-swipe-wrap {
  position: relative;
  overflow: hidden;
  margin-bottom: 4px;
}
.thread-pin-act {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 56px;
  background: #3a8c5f;
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; user-select: none; -webkit-user-select: none;
}
.thread-pin-act.pinned { background: #6b6b6b; }
.thread-pin-act:active { opacity: 0.8; }
.thread-mark-act {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 56px;
  background: var(--accent, #d97757);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; user-select: none; -webkit-user-select: none;
}
.thread-mark-act:active { opacity: 0.8; }
.thread-settings-act {
  position: absolute;
  left: 112px; top: 0; bottom: 0;
  width: 56px;
  background: #d9a441;
  color: #000;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; user-select: none; -webkit-user-select: none;
}
.thread-settings-act:active { opacity: 0.8; }
.thread-settings-act.thread-settings-act--left { left: 0; }
.thread-restore-act {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 56px;
  background: #3a8c5f;
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; user-select: none; -webkit-user-select: none;
}
.thread-restore-act:active { opacity: 0.8; }
.thread-pin-act svg, .thread-mark-act svg, .thread-settings-act svg, .thread-restore-act svg { display: block; }
.start-row-swipe {
  position: relative;
  overflow: hidden;
}
.start-row-swipe .start-unread-row,
.start-row-swipe .start-busy-row {
  position: relative;
  z-index: 1;
  width: 100%;
  box-sizing: border-box;
  background: var(--bg);
}
.start-row-swipe .start-unread-row.current,
.start-row-swipe .start-busy-row.current {
  background:
    linear-gradient(rgba(217, 119, 87, 0.06), rgba(217, 119, 87, 0.06)),
    var(--bg);
}
.thread-mark-act.start-row-mark { left: 0; }
.thread-settings-act.start-row-settings { left: 56px; }
.thread-section-hdr {
  padding: 10px 10px 4px 10px;
  font-size: 10px;
  color: var(--fg-dim);
  letter-spacing: 1px;
  list-style: none;
}
.thread-section-hdr--toggle {
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  display: flex;
  align-items: center;
  gap: 4px;
}
.thread-section-hdr--toggle:active { opacity: 0.7; }
.archive-arrow { font-size: 9px; }
.archive-count { opacity: 0.5; font-weight: 400; }
.thread-item {
  display: flex; align-items: center;
  padding: 8px 10px;
  border: 1px solid transparent;
  background: var(--bg-card);
  color: var(--fg);
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  position: relative;
  z-index: 1;
  transition: box-shadow 0.18s ease;
}
.thread-item.archived { color: var(--fg-dim); }
.thread-item.archived .thread-title { opacity: 0.75; }
.thread-item.active {
  border-color: var(--accent);
  color: var(--accent);
}
.thread-status.fav-heart {
  background: transparent !important;
  opacity: 1 !important;
  color: #ff5a7e !important;
  box-shadow: none !important;
  animation: none !important;
}
.thread-item .thread-title {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
}
.thread-item .thread-meta {
  font-size: 10px;
  color: var(--fg-dim);
  margin-left: 8px;
  flex: 0 0 auto;
  /* «12:34» vs «18.05» в пропорциональном шрифте разной ширины — даты в
     закрепах съезжают. Tabular-nums + min-width делают колонку ровной. */
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  min-width: 36px;
  text-align: right;
}
.thread-empty {
  padding: 16px 12px;
  color: var(--fg-dim);
  font-size: 12px;
  text-align: center;
}
.thread-skel {
  display: flex; align-items: center;
  padding: 10px 12px;
  margin-bottom: 2px;
  list-style: none;
  opacity: 0.85;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}
.thread-skel-line, .thread-skel-meta {
  height: 12px;
  border-radius: 3px;
  background: linear-gradient(90deg,
    var(--bg-soft) 0%,
    var(--bg-card) 50%,
    var(--bg-soft) 100%);
  background-size: 200% 100%;
  animation: thread-skel-shimmer 1.2s linear infinite;
}
.thread-skel-line { flex: 1 1 auto; margin-right: 8px; }
.thread-skel-meta { flex: 0 0 36px; height: 8px; opacity: 0.6; }
.thread-skel:nth-child(1) .thread-skel-line { width: 60%; }
.thread-skel:nth-child(2) .thread-skel-line { width: 80%; }
.thread-skel:nth-child(3) .thread-skel-line { width: 50%; }
.thread-skel:nth-child(4) .thread-skel-line { width: 70%; }
.thread-skel:nth-child(5) .thread-skel-line { width: 45%; }
.thread-skel:nth-child(6) .thread-skel-line { width: 65%; }
@keyframes thread-skel-shimmer {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}

.msg.skel {
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
  position: relative;
  overflow: hidden;
  border: none;
  border-left: none;
  box-shadow: none;
  padding: 0;
  background: rgba(255,255,255,0.05);
  opacity: 1;
  animation: msg-skel-pulse 1.6s ease-in-out infinite;
}
html.theme-light .msg.skel {
  background: rgba(0,0,0,0.06);
}
/* Соседние плашки дышат со сдвигом, чтобы шла «волна», а не синхронное мерцание. */
.msg.skel:nth-child(2n)     { animation-delay: -0.40s; }
.msg.skel:nth-child(3n)     { animation-delay: -0.80s; }
.msg.skel:nth-child(5n + 1) { animation-delay: -1.20s; }
@keyframes msg-skel-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.45; }
}
@media (prefers-reduced-motion: reduce) {
  .msg.skel { animation: none; }
}
.msg.skel::after { display: none; }
.msg.skel::before { display: none; }
.msg.skel .role,
.msg.skel .body { display: none; }

/* Вертикальный шиммер сверху вниз поверх всех плашек скелетона. */
body.chat-skel #chat { position: relative; }
body.chat-skel #chat::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    transparent 0%,
    rgba(255,255,255,0.08) 50%,
    transparent 100%);
  background-size: 100% 60%;
  background-repeat: no-repeat;
  animation: chat-skel-shimmer 1.6s linear infinite;
  pointer-events: none;
  z-index: 2;
}
html.theme-light body.chat-skel #chat::after {
  background: linear-gradient(180deg,
    transparent 0%,
    rgba(0,0,0,0.06) 50%,
    transparent 100%);
  background-size: 100% 60%;
  background-repeat: no-repeat;
}
@keyframes chat-skel-shimmer {
  from { background-position: 0 -60%; }
  to   { background-position: 0 160%; }
}

/* Пин-скелетон (докатка истории над pin-плейсхолдером): тонкие нейтральные
   полосы вместо фейк-бабблов. Держат вертикальное место под скролл-математику
   ensurePinSkeleton (она само-компенсирует scrollTop), но на сообщения не
   похожи. */
.pj-skel-bar {
  height: 12px;
  width: 60%;
  margin: 9px auto;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.06);
  animation: msg-skel-pulse 1.6s ease-in-out infinite;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}
.pj-skel-bar:nth-child(2n) { width: 74%; animation-delay: -0.4s; }
.pj-skel-bar:nth-child(3n) { width: 48%; animation-delay: -0.8s; }
html.theme-light .pj-skel-bar { background: rgba(0, 0, 0, 0.06); }
html.theme-glass .pj-skel-bar { background: rgba(40, 176, 182, 0.14); border-radius: 5px; }

/* Топбар-скелетон при переключении/подгрузке чата: вместо живого названия
   и status-кнопки — две приглушённые плашки в тон сообщениям. */
body.chat-skel #topbar-title { visibility: hidden; }
body.chat-skel .logo .logo-text { position: relative; }
body.chat-skel .logo .logo-text::after {
  content: "";
  position: absolute;
  left: 14px; top: 50%;
  width: 140px; max-width: calc(100% - 28px); height: 16px;
  transform: translateY(-50%);
  background: rgba(255,255,255,0.05);
  pointer-events: none;
  animation: msg-skel-pulse 1.6s ease-in-out infinite;
  animation-delay: -0.20s;
}
html.theme-light body.chat-skel .logo .logo-text::after { background: rgba(0,0,0,0.06); }
body.chat-skel #conn-status-btn { position: relative; color: transparent; }
body.chat-skel #conn-status-btn .dot,
body.chat-skel #conn-status-btn #conn-text { visibility: hidden; }
body.chat-skel #conn-status-btn::after {
  content: "";
  position: absolute;
  right: 8px; top: 50%;
  width: 52px; height: 14px;
  transform: translateY(-50%);
  background: rgba(255,255,255,0.05);
  pointer-events: none;
  animation: msg-skel-pulse 1.6s ease-in-out infinite;
  animation-delay: -0.60s;
}
html.theme-light body.chat-skel #conn-status-btn::after { background: rgba(0,0,0,0.06); }

/* === Скелетоны под три темы =============================================
   Базовые правила выше — это классика темная (root без класс-маркеров).
   Светлая (html.theme-light) — мягче, прозрачность ниже, чтобы не «давила».
   Стекло (html.theme-glass) — стеклянный фон + холодный shimmer под акцент. */

/* --- classic-light: смягчаем shimmer (текущий 0.06 для чата норм, для
       drawer-skel линий — переопределяем под мягче, чтобы серый shimmer
       не выглядел грязным на белом). */
html.theme-light .thread-skel-line,
html.theme-light .thread-skel-meta {
  background: linear-gradient(90deg,
    rgba(0,0,0,0.04) 0%,
    rgba(0,0,0,0.10) 50%,
    rgba(0,0,0,0.04) 100%);
  background-size: 200% 100%;
}

/* --- classic-dark: чуть усилим контраст shimmer в drawer-skel, текущие
       --bg-soft/--bg-card в тёмной теме почти совпадают и волна не видна. */
html:not(.theme-glass):not(.theme-light) .thread-skel-line,
html:not(.theme-glass):not(.theme-light) .thread-skel-meta {
  background: linear-gradient(90deg,
    rgba(255,255,255,0.04) 0%,
    rgba(255,255,255,0.12) 50%,
    rgba(255,255,255,0.04) 100%);
  background-size: 200% 100%;
}

/* --- glass: стеклянный шиммер с холодным teal-оттенком (под glass-акцент
       по умолчанию #28B0B6 → 40,176,182). Захардкожено, не подвязано к
       выбранному --accent — скелетон висит коротко, на смену акцента
       реагировать не критично. */
html.theme-glass .thread-skel-line,
html.theme-glass .thread-skel-meta {
  background: linear-gradient(90deg,
    rgba(255,255,255,0.05) 0%,
    rgba(40,176,182,0.18) 50%,
    rgba(255,255,255,0.05) 100%);
  background-size: 200% 100%;
  border-radius: 5px;
}
html.theme-glass .thread-skel {
  opacity: 0.95;
}

html.theme-glass .msg.skel {
  background: rgba(255,255,255,0.05);
  border-radius: 14px;
  backdrop-filter: blur(8px) saturate(140%);
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.06);
}

html.theme-glass body.chat-skel #chat::after {
  background: linear-gradient(180deg,
    transparent 0%,
    rgba(40,176,182,0.12) 50%,
    transparent 100%);
  background-size: 100% 60%;
  background-repeat: no-repeat;
}

html.theme-glass body.chat-skel .logo .logo-text::after,
html.theme-glass body.chat-skel #conn-status-btn::after {
  background: rgba(40,176,182,0.14);
  border-radius: 4px;
}

/* Реальный чат проскроллен к низу — скелетон тоже прижимаем к низу,
   иначе при подгрузке настоящих сообщений происходит визуальный скачок
   с верха экрана к низу. margin-top:auto в flex-column толкает первый
   скел и всю гирлянду к нижней кромке #chat. */
/* Скелетон-плашки распределяются по всей высоте чата сверху, без прижима к низу. */
#drawer-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.55);
  z-index: 140;
  opacity: 1;
  visibility: visible;
  transition: opacity 0.32s cubic-bezier(.22,.61,.36,1), visibility 0s linear 0s;
}
#drawer-overlay.hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.32s cubic-bezier(.22,.61,.36,1), visibility 0s linear 0.32s;
}

/* ===== Drag-and-drop overlay ===== */
#drop-overlay {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(38,38,36,0.88);
  border: 2px dashed var(--accent);
  pointer-events: none;
}
#drop-overlay.hidden { display: none; }
.drop-label {
  color: var(--accent);
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 2px;
  border: 1px solid var(--accent);
  padding: 10px 20px;
}

/* ===== Desktop layout ===== */
body.is-desktop #app {
  padding-left: var(--drawer-width);
}
body.is-desktop #drawer,
body.is-desktop #drawer.hidden {
  transform: none !important;
  transition: none !important;
  pointer-events: auto !important;
  box-shadow: none;
  z-index: 50;
}
body.is-desktop #drawer-overlay,
body.is-desktop #drawer-close,
body.is-desktop #threads-btn,
body.is-desktop #edge-handle,
body.is-desktop .settings-row[data-act="autocorrect"],
body.is-desktop .settings-row[data-act="haptics"],
body.is-desktop .settings-group-title.mobile-only {
  display: none !important;
}
body:not(.is-desktop) .settings-row[data-act="zoom"] {
  display: none !important;
}
body.is-desktop #topbar,
body.is-desktop #composer {
  margin-left: 0;
}
body.is-desktop #chat {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  width: 100%;
  padding-left: max(12px, calc((100% - 760px) / 2));
  padding-right: max(12px, calc((100% - 760px) / 2));
}
body.is-desktop #composer {
  max-width: 760px;
  margin-left: auto;
  margin-right: auto;
  box-sizing: border-box;
}
body.is-desktop #app {
  max-width: 1200px;
  margin-left: auto;
  margin-right: auto;
}
body.is-desktop {
  font-size: 12px;
}
body.is-desktop .msg {
  font-size: 12px;
}
body.is-desktop .msg .body pre.codeblock,
body.is-desktop .msg .body pre.codeblock code {
  font-size: 11px;
}
body.is-desktop #input {
  font-size: 14px;
}
body.is-desktop #input-ghost {
  font-size: 14px;
}
body.is-desktop #input-ghost .ghost-cur {
  display: none;
}
/* Десктоп: списки сущностей — адаптивный грид. auto-fit, чтобы при малом количестве
   карточек они растянулись на доступную ширину, а не оставляли пустую колонку справа.
   Контейнер не центруем — пусть выравнивается по табам/поиску над ним. */
body.is-desktop #routines-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: 12px;
  align-items: start;
}
body.is-desktop #routines-list .routine-card {
  margin: 0;
}
body.is-desktop #tech-list {
  column-width: 280px;
  column-gap: 8px;
}
body.is-desktop #tech-list .tech-card {
  display: block;
  width: 100%;
  margin: 0 0 8px;
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
}
/* Десктоп: название треда в топбаре не обрезаем — ширину пересчитывает JS
   (autosizeTopbarTitle в app.js) по измерению через canvas. */
body.is-desktop #topbar .logo .topbar-title-btn {
  max-width: none;
  text-overflow: clip;
}
/* Десктоп: пузыри сообщений уже мобильных 92% — на широком экране они
   растягивались почти на всю строку и читались плохо. И разрешаем выделять
   текст мышью: на iOS селект отключён ради openMessageMenu и лупы Apple,
   но мыши это не касается. */
body.is-desktop .msg {
  max-width: 70%;
  -webkit-user-select: text;
  user-select: text;
  cursor: text;
}
/* ===== Modal extras (rename input + actions) ===== */
.modal-input {
  width: 100%;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  padding: 8px 10px;
  font-family: inherit;
  font-size: 14px;
  border-radius: 0;
}
.modal-input:focus {
  outline: none;
  border-color: var(--accent);
}
.modal-actions {
  display: flex; gap: 6px;
  margin-top: 6px;
}
.modal-actions button {
  flex: 1 1 auto;
}
.modal-body button.primary {
  background: var(--accent);
  color: #1a1306;
  border-color: var(--accent);
  font-weight: 700;
  text-align: center;
}
.modal-body button.danger {
  color: var(--red);
  border-color: var(--red);
}
.confirm-message {
  color: var(--fg);
  font-size: 13px;
  line-height: 1.45;
  padding: 4px 2px 12px;
}
.confirm-actions {
  display: flex;
  gap: 8px;
  margin-top: 4px;
}
.confirm-actions .confirm-btn {
  flex: 1 1 0;
  display: block;
  width: 100%;
  text-align: center;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  color: var(--fg);
  -webkit-text-fill-color: currentColor;
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 10px 12px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.5px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.confirm-actions .confirm-btn:active { background: var(--bg-card); }
.confirm-actions .confirm-btn.primary {
  color: var(--accent);
  -webkit-text-fill-color: var(--accent);
  border-color: var(--accent);
  background: transparent;
}
.confirm-actions .confirm-btn.primary:active {
  background: rgba(255, 110, 66, 0.12);
}
.confirm-actions .confirm-btn.danger {
  color: #e85a5a;
  -webkit-text-fill-color: #e85a5a;
  border-color: #e85a5a;
  background: transparent;
}
.confirm-actions .confirm-btn.danger:active {
  background: rgba(232, 90, 90, 0.12);
}

/* ===== Попап «Кеш» (дешборд) ===== */
.cache-dash {
  padding: 4px 2px 12px;
  color: var(--fg);
  font-size: 13px;
  line-height: 1.4;
}
.cache-dash-loading {
  color: var(--muted, var(--fg));
  opacity: 0.7;
  padding: 6px 0;
}
.cache-dash-grid {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: 12px;
  row-gap: 6px;
  align-items: baseline;
}
.cache-dash-key {
  color: var(--muted, var(--fg));
  opacity: 0.7;
  text-transform: lowercase;
  font-size: 12px;
  letter-spacing: 0.3px;
}
.cache-dash-val {
  color: var(--fg);
  font-variant-numeric: tabular-nums;
  word-break: break-word;
}
.cache-dash-section {
  margin-top: 14px;
  margin-bottom: 4px;
  color: var(--muted, var(--fg));
  opacity: 0.7;
  text-transform: lowercase;
  font-size: 12px;
  letter-spacing: 0.3px;
}
.cache-dash-urls {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 12px;
  line-height: 1.35;
  color: var(--fg);
}
.cache-dash-urls li {
  padding: 2px 0;
  word-break: break-all;
  opacity: 0.85;
}

/* ===== Попап сущности из журнала рутины ===== */
.entity-popup-label {
  color: var(--fg);
  font-size: 13px;
  line-height: 1.4;
  padding: 4px 2px 6px;
  word-break: break-word;
}
.entity-popup-url {
  color: var(--fg-dim);
  font-size: 11px;
  line-height: 1.4;
  padding: 2px 2px 12px;
  word-break: break-all;
  font-family: var(--font-ui);
}

/* ===== Список в модалке (скиллы / mcp / конфиги / память) ===== */
.list-loading {
  color: var(--fg-dim);
  font-size: 12px;
  text-align: center;
  padding: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
}
.list-empty {
  color: var(--fg-dim);
  font-size: 12px;
  text-align: center;
  padding: 12px;
  border: 1px dashed var(--border);
}
.modal-body button.btn-sub {
  display: flex; flex-direction: column;
  align-items: flex-start; gap: 2px;
}
.modal-body button.btn-sub .sub {
  font-size: 10px;
  color: var(--fg-dim);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.modal-body button.btn-back {
  background: var(--bg);
  color: var(--fg-dim);
  border-color: var(--border);
  text-align: left;
}
.modal-body button.btn-back:active { color: var(--fg); }
.modal-body .file-view-bar {
  display: flex;
  gap: 6px;
  align-items: center;
  justify-content: space-between;
}
.modal-body .file-view-bar > div {
  display: flex;
  gap: 6px;
  align-items: center;
  flex-shrink: 0;
}
.modal-body .file-view-bar button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  flex: 0 0 auto;
  white-space: nowrap;
  text-align: center;
  padding: 6px 12px;
  font-size: 12px;
  line-height: 1.2;
}
.modal-body button.btn-insert {
  background: var(--accent, #d97757);
  color: #fff;
  border: 1px solid var(--accent, #d97757);
  width: 32px;
  height: 32px;
  padding: 0;
}
.modal-body button.btn-insert:active { opacity: 0.8; }
.file-view {
  background: var(--code-bg);
  border: 1px solid var(--border);
  color: var(--fg);
  padding: 8px 10px;
  margin: 0;
  font-family: inherit;
  font-size: 12px;
  line-height: 1.45;
  white-space: pre-wrap;
  word-wrap: break-word;
  word-break: break-word;
  max-height: 60vh;
  overflow-y: auto;
  -webkit-user-select: text; user-select: text;
}
.file-view::-webkit-scrollbar { width: 4px; }
.file-view::-webkit-scrollbar-thumb { background: var(--scroll-thumb); }

/* Лог рутины: «процесс размышления» — сворачиваемый блок под итогом, чтобы
   итог прогона не тонул в полотне процесса (build 1184). */
.log-process {
  margin-top: 18px;
  border-top: 1px solid var(--border);
  padding-top: 6px;
}
.log-process > summary {
  cursor: pointer;
  list-style: none;
  -webkit-user-select: none; user-select: none;
  font-weight: 600;
  opacity: 0.65;
  padding: 10px 0;
  display: flex;
  align-items: center;
  gap: 8px;
}
.log-process > summary::-webkit-details-marker { display: none; }
.log-process > summary::before {
  content: "\25B8"; /* ▸ */
  font-size: 0.85em;
  opacity: 0.8;
  transition: transform 0.15s ease;
}
.log-process[open] > summary { opacity: 1; }
.log-process[open] > summary::before { transform: rotate(90deg); }
.log-process-body { margin-top: 4px; }

.report-view {
  background: var(--code-bg);
  border: 1px solid var(--border);
  color: var(--fg);
  padding: 10px 12px;
  font-size: 13px;
  line-height: 1.5;
  word-wrap: break-word;
  word-break: break-word;
  max-height: 60vh;
  overflow-y: auto;
  -webkit-user-select: text; user-select: text;
}
.report-view::-webkit-scrollbar { width: 4px; }
.report-view::-webkit-scrollbar-thumb { background: var(--scroll-thumb); }
.report-view a.link { color: var(--accent); text-decoration: underline; word-break: break-all; }
.report-view code.inline {
  background: rgba(255,255,255,0.06);
  padding: 1px 4px;
  border-radius: 3px;
  font-family: var(--font-ui);
  font-size: 12px;
}
.report-view strong { font-weight: 700; }
.report-view em { font-style: italic; }

/* делаем модалку шире, когда показываем список или файл */
.modal-card.wide { max-width: 480px; }

/* ===== Adaptive grid внутри модалки (скиллы / mcp / конфиги / память) ===== */
.modal-body .modal-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 6px;
  width: 100%;
}
.modal-body .modal-grid .grid-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 2px;
  width: 100%;
  min-height: 48px;
  padding: 6px 8px;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  font-family: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
  overflow: hidden;
}
@media (hover: hover) and (pointer: fine) {
  .modal-body .modal-grid .grid-card:hover {
    border-color: var(--accent);
    color: var(--accent);
  }
}
.modal-body .modal-grid .grid-card:active {
  border-color: var(--accent);
  color: var(--accent);
}
.modal-body .modal-grid .grid-card .grid-card-label {
  font-size: 12px;
  line-height: 1.25;
  color: inherit;
  word-break: break-word;
  overflow-wrap: anywhere;
  display: block;
  white-space: normal;
}
.modal-body .modal-grid .grid-card .grid-card-sub {
  font-size: 9px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--fg-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  display: block;
}
@media (min-width: 520px) {
  .modal-body .modal-grid {
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 8px;
  }
}

/* ===== edge-handle для свайпа ===== */
#edge-handle {
  position: fixed;
  top: 0; left: 0;
  width: 24px; height: 100%;
  z-index: 5;
  background: transparent;
  pointer-events: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: pan-y;
}
/* Тонкая видимая полоска-подсказка на левом краю */
#edge-handle::after {
  content: "";
  position: absolute;
  top: 50%; left: 0;
  width: 3px; height: 56px;
  transform: translateY(-50%);
  background: var(--accent-dim);
  opacity: 0.55;
  box-shadow: 0 0 8px var(--accent-dim);
  border-radius: 0 2px 2px 0;
  pointer-events: none;
}
body.drawer-open #edge-handle { display: none; }

/* Во время drag отключаем transition — drawer следует за пальцем */
#drawer.dragging { transition: none !important; }
#drawer-overlay.dragging { transition: none !important; }

/* ===== Настройки внутри drawer ===== */
.drawer-section {
  font-size: 10px;
  letter-spacing: 1.5px;
  color: var(--fg-dim);
  padding: 14px 14px 6px;
  border-top: 1px solid var(--border);
  margin-top: 8px;
  text-align: center;
}
.drawer-action {
  display: block;
  width: 100%;
  background: transparent;
  color: var(--fg);
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 12px 16px;
  text-align: left;
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
  transition: background 0.1s;
}
.drawer-action:active {
  background: var(--bg-card);
  color: var(--fg);
}
.drawer-action.danger:active { color: var(--red); }

/* ===== Drawer footer (settings entry button) ===== */
.drawer-footer {
  border-top: 1px solid var(--border);
  background: var(--bg-card);
  flex: 0 0 auto;
}
.drawer-foot-btn {
  display: grid;
  grid-template-columns: 22px 1fr 16px;
  align-items: center;
  gap: 10px;
  width: 100%;
  background: transparent;
  color: var(--fg);
  border: none;
  padding: 14px 16px;
  font-family: inherit; font-size: 13px;
  cursor: pointer;
  text-align: left;
  touch-action: manipulation;
  -webkit-tap-highlight-color: rgba(255,255,255,0.05);
  position: relative;
  z-index: 1;
}
body:not(.is-desktop) .drawer-footer {
  padding-bottom: calc(8px + var(--safe-bot));
}
.drawer-foot-btn:active { background: var(--bg-card); color: var(--fg); }
.drawer-foot-btn .foot-icon { color: var(--fg-dim); font-size: 14px; }
.drawer-foot-btn .foot-label { letter-spacing: 0.5px; }
.drawer-foot-btn .foot-chev { color: var(--fg-dim); font-size: 16px; text-align: right; }
.drawer-foot-icons {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
}
/* На iPhone нижний ряд иконок должен занимать всю ширину бокового меню (Кирилл:
   «на всю ширину меню»). Тянем контейнер на 100% ширины драуэра и раскидываем
   6 иконок space-between с симметричным боковым padding. */
body:not(.is-desktop) .drawer-foot-icons {
  display: flex;
  justify-content: space-between;
  padding: 0 16px;
  width: 100%;
}
body:not(.is-desktop) .drawer-foot-icons .drawer-foot-icon {
  width: 32px;
  padding: 14px 0;
  flex: 0 0 auto;
}
.tech-search-bar {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-soft);
}
.tech-search-bar .thread-search-icon { color: var(--fg-dim); display: inline-flex; }
.tech-search-bar .thread-search-input {
  flex: 1 1 auto;
  background: transparent;
  border: none;
  outline: none;
  color: var(--fg);
  font: inherit;
  font-size: 14px;
  padding: 6px 0;
}
.tech-search-bar .thread-search-clear {
  background: transparent;
  border: none;
  color: var(--fg-dim);
  font-size: 13px;
  padding: 4px 6px;
  cursor: pointer;
}
.tech-search-ai {
  flex: 0 0 auto;
  background: transparent;
  border: 0;
  border-left: 1px solid var(--border);
  color: var(--accent);
  padding: 4px 6px 4px 8px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  line-height: 1;
  border-radius: 0;
  transition: color 140ms ease, background 140ms ease;
}
.tech-search-ai svg { width: 14px; height: 14px; display: block; }
.tech-search-ai:active { transform: translateY(1px); }
.tech-search-ai.is-active {
  background: var(--accent);
  color: #000;
}
.tech-chips-row {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  border-bottom: 1px solid var(--border);
  background: var(--bg-soft);
}
.tech-chips {
  flex: 1 1 auto;
  display: flex;
  gap: 6px;
  padding: 8px 0 8px 12px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.tech-sort-toggle {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--fg-dim);
  font-size: 14px;
  font-weight: 600;
  padding: 0;
  margin-left: 10px;
  margin-right: 10px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  line-height: 1;
}
.tech-sort-toggle.is-active { color: var(--accent); border-color: var(--accent); }
.tech-page .settings-header,
.tech-page .tech-search-bar { flex: 0 0 auto; }
.tech-chips::-webkit-scrollbar { display: none; }
.tech-chip {
  flex: 0 0 auto;
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 0;
  padding: 6px 12px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.3px;
  cursor: pointer;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.tech-chip.is-active {
  color: var(--accent);
  border-color: var(--accent);
}
.tech-mtype-chips {
  flex: 0 0 auto;
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding: 0 12px 8px 0;
  scrollbar-width: none;
}
.tech-mtype-chips::-webkit-scrollbar { display: none; }
.tech-mtype-chips.hidden { display: none; }
.tech-mtype-chip {
  flex: 0 0 auto;
  background: transparent;
  color: var(--fg-dim);
  border: 1px solid var(--border);
  border-radius: 0;
  padding: 4px 10px;
  font-family: inherit;
  font-size: 11px;
  letter-spacing: 0.3px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.tech-mtype-chip.is-active {
  color: var(--accent);
  border-color: var(--accent);
}
.tech-page #tech-scroll {
  padding: 10px 12px 20px;
  overflow-x: hidden;
  overscroll-behavior-x: contain;
}
.tech-card {
  display: block;
  width: 100%;
  box-sizing: border-box;
  text-align: left;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 0;
  padding: 10px 12px;
  margin: 0 0 8px;
  font-family: inherit;
  color: var(--fg);
  cursor: pointer;
}
.tech-card:active { background: var(--bg-soft); }
.tech-card-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}
.tech-card-name {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: 700;
  font-size: 13px;
  word-break: break-word;
}
.tech-pin {
  flex: 0 0 auto;
  margin-left: 4px;
  color: var(--accent-dim);
  display: inline-flex;
  align-items: center;
  line-height: 0;
}
.tech-card.pinned { border-left: 3px solid var(--accent); padding-left: 9px; }
.tech-card.pinned .tech-pin { color: var(--accent); }
.tech-card-head-right {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 5px;
}
.tech-card-kind {
  font-size: 10px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--fg-dim);
}
.tech-card-mtype {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 0;
  padding: 1px 5px;
  opacity: 0.85;
}
.tech-card-meta {
  font-size: 11px;
  color: var(--fg-dim);
  margin-top: 4px;
  word-break: break-all;
  overflow-wrap: anywhere;
}
.tech-card-sub {
  font-size: 12px;
  color: var(--fg);
  margin-top: 4px;
  line-height: 1.35;
  word-break: break-word;
  overflow-wrap: anywhere;
}
.tech-card-sub a.link { color: var(--accent); text-decoration: underline; word-break: break-all; }
.tech-card-sub code.inline {
  background: rgba(255,255,255,0.06);
  padding: 1px 4px;
  border-radius: 3px;
  font-family: var(--font-ui);
  font-size: 11px;
}
.tech-card-sub strong { font-weight: 700; }
.tech-card-sub em { font-style: italic; }
.tech-card-name mark,
.tech-card-sub mark,
.tech-card-meta mark {
  background: var(--accent);
  color: #000;
  padding: 0 2px;
  border-radius: 0;
}

/* ===== Memory editor ===== */
.memory-edit-scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 8px 12px calc(16px + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.memory-edit-label {
  font-size: 11px;
  color: var(--fg-dim);
  letter-spacing: 0.4px;
  text-transform: uppercase;
  margin-top: 6px;
}
.memory-edit-input,
.memory-edit-textarea {
  width: 100%;
  box-sizing: border-box;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 10px 12px;
  font-family: inherit;
  font-size: 14px;
  color: var(--fg);
  outline: none;
}
.memory-edit-input:focus,
.memory-edit-textarea:focus { border-color: var(--accent); }
.memory-edit-textarea {
  flex: 1 1 auto;
  min-height: 162px;
  font-family: var(--font-ui);
  font-size: 13px;
  line-height: 1.45;
  resize: vertical;
}
.memory-edit-md.file-view-md {
  flex: 0 0 auto;
  min-height: 162px;
  height: auto;
}
.memory-edit-md[hidden] { display: none; }
.memory-edit-path {
  font-size: 11px;
  color: var(--fg-dim);
  word-break: break-all;
  overflow-wrap: anywhere;
  margin-top: 6px;
}
.mem-type-dropdown {
  position: relative;
}
.mem-type-trigger {
  appearance: none; -webkit-appearance: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  text-align: left;
  cursor: pointer;
  color: var(--fg);
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.mem-type-trigger svg { flex-shrink: 0; opacity: 0.5; }
.mem-type-list {
  position: absolute;
  top: calc(100% + 2px);
  left: 0; right: 0;
  background: var(--bg-card);
  border: 1px solid var(--accent);
  z-index: 200;
  display: flex;
  flex-direction: column;
}
.mem-type-list.hidden { display: none; }
.mem-type-option {
  appearance: none; -webkit-appearance: none;
  background: transparent;
  color: var(--fg);
  border: none;
  border-bottom: 1px solid var(--border);
  padding: 10px 12px;
  font-family: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.mem-type-option:last-child { border-bottom: none; }
.mem-type-option:active { background: var(--bg); }
.mem-type-option.is-active { color: var(--accent); }
.memory-edit-actions {
  display: flex;
  gap: 6px;
  margin-top: 12px;
  justify-content: flex-start;
}
.memory-edit-action-btn {
  width: 32px;
  height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  flex-shrink: 0;
}
.memory-edit-delete-icon {
  color: var(--red, #c0473b);
  border-color: var(--red, #c0473b);
}
.memory-edit-delete-icon:active { background: rgba(192, 71, 59, 0.08); }
.icon-btn-accent { color: var(--accent); }
.drawer-foot-icon {
  background: transparent;
  color: var(--fg-dim);
  border: none;
  padding: 14px 0;
  font-family: inherit;
  font-size: 16px;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: rgba(255,255,255,0.05);
  display: flex;
  align-items: center;
  justify-content: center;
}
.drawer-foot-icon:active { background: var(--bg-card); color: var(--fg); }
.drawer-foot-icon .foot-icon { color: var(--fg-dim); font-size: 16px; }

/* Drawer-foot иконки: пиксельный legacy-вариант для всех тем, line-style
   glass-вариант — только для :root.theme-glass. Так стилевые правки glass
   не затрагивают light/dark. */
.drawer-foot-icon .foot-icon-glass { display: none; }
:root.theme-glass .drawer-foot-icon .foot-icon-legacy { display: none; }
:root.theme-glass .drawer-foot-icon .foot-icon-glass {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* v1071: выбранный раздел в нижней панели — его иконка горит акцентом.
   Красим .foot-icon (он несёт currentColor для SVG; цвет, заданный на самой
   кнопке, до SVG не доходит — span перехватывает наследование). */
body.connection-open .drawer-foot-icon[data-act="connection"] .foot-icon,
body.health-open     .drawer-foot-icon[data-act="health"] .foot-icon,
body.routines-open   .drawer-foot-icon[data-act="routines"] .foot-icon,
body.tech-open       .drawer-foot-icon[data-act="tech"] .foot-icon,
body.news-open       .drawer-foot-icon[data-act="news"] .foot-icon,
body.settings-open   #settings-open .foot-icon {
  color: var(--accent);
}

/* Wifi/антенна-иконка = индикатор связи. Цвет несёт span.foot-icon (на нём
   currentColor для SVG; цвет, заданный на самой кнопке, до SVG НЕ доходит —
   span перехватывает наследование, поэтому прежние правила на .drawer-foot-icon
   не красили глиф вовсе и антенна висела серой всегда). Состояние = реальный WS
   (data-conn-state из setConn, app.js; connecting/offline разводятся по
   ws.readyState===0 — в такт верхнему «светофору» _connWsStateKind).
   Схема (Кирилл): подключён → СЕРЫЙ (спокойный, как простаивающая иконка),
   подключается → ЖЁЛТЫЙ (#e0c34c — тот же, что у цифры-бейджа сверху),
   отвалился → КРАСНЫЙ. «Синхронизировано с верхом, с небольшим отступлением»:
   жёлтый/красный те же, что наверху, но connected светит нейтральным серым, а
   не акцентом. thinking/waiting = WS открыт = связь есть → тоже серый (антенна
   показывает СВЯЗЬ, а не «думаю»). Смена цвета плавная — без рывка. */
.drawer-foot-icon[data-act="connection"] .foot-icon {
  transition: color 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
.drawer-foot-icon[data-conn-state="online"] .foot-icon,
.drawer-foot-icon[data-conn-state="thinking"] .foot-icon,
.drawer-foot-icon[data-conn-state="waiting"] .foot-icon { color: var(--fg-dim); }
.drawer-foot-icon[data-conn-state="connecting"] .foot-icon { color: #e0c34c; }
.drawer-foot-icon[data-conn-state="offline"] .foot-icon { color: var(--red); }
/* v1071: мигание/пульс/вспышка wifi-иконки убраны — потерю связи показывает
   красный счётчик-кружок #conn-badge (число проваленных реконнектов), а не
   моргание. См. updateConnProblemBadge в app.js. */

/* ===== Settings page (overlay внутри drawer) ===== */
.settings-page {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  background: var(--bg-soft);
  z-index: 5;
  display: flex; flex-direction: column;
  padding-top: var(--safe-top);
  padding-bottom: var(--safe-bot);
  padding-left: var(--safe-left);
}
.settings-page.hidden { display: none; }

/* Подстраницы внутри drawer'а (Настройки / Сервисы / Диагностика) —
   останавливаемся над панелью с 5 иконками, чтобы юзер мог переключаться
   между разделами одним тапом. Анимация слайд-вверх + плавное появление. */
#settings-page,
#health-page,
#connection-page,
#news-page,
#routines-page,
#tech-page {
  bottom: var(--drawer-foot-h, 0px);
  padding-bottom: 0;
  animation: drawer-subpage-in 0.18s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
/* Уведомления — тоже упираемся в панель с иконками снизу (как health/
   settings), чтобы лента не скроллилась ПОД полупрозрачный футер и не
   просвечивала сквозь него. Тогда стеклянный бар выглядит как в «Сервисах».
   Свою анимацию открытия не навязываем — у уведомлений она своя. */
#notifications-page {
  bottom: var(--drawer-foot-h, 0px);
  padding-bottom: 0;
}
@keyframes drawer-subpage-in {
  from { transform: translateY(8px); opacity: 0; }
  to   { transform: translateY(0);   opacity: 1; }
}
/* Рутины и Техническое теперь живут ВНУТРИ драуэра (как Настройки/Сервисы):
   позиционирование задаёт общая группа выше (position:absolute из .settings-page
   + bottom: --drawer-foot-h), фуллскрин-оверлей убран. Так футер остаётся виден
   под ними, а сами страницы умещаются в боковом меню. */
#routines-page .settings-header,
#tech-page .settings-header {
  grid-template-columns: 32px 1fr 32px 32px;
  gap: 6px;
}
#memory-edit-page.settings-page {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--vh, 100dvh);
  padding-bottom: calc(var(--safe-bot) + var(--kb, 0px));
  z-index: 210;
}
#memory-edit-page .settings-header {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
#file-view-page.settings-page {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--vh, 100dvh);
  padding-bottom: calc(var(--safe-bot) + var(--kb, 0px));
  z-index: 220;
}
#memory-edit-page .settings-header,
#file-view-page .settings-header {
  grid-template-columns: 32px 1fr auto auto;
  gap: 4px;
}
.file-view-crumb {
  background: transparent;
  color: var(--fg-dim);
  border: 1px solid var(--border);
  border-radius: 0;
  padding: 4px 10px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  cursor: pointer;
  white-space: nowrap;
  height: 32px;
  align-self: center;
  justify-self: end;
  display: inline-flex;
  align-items: center;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.file-view-crumb:active { color: var(--accent); border-color: var(--accent); }
.file-view-crumb[hidden] { display: none; }
.file-view-pre.is-tappable,
.file-view-md.is-tappable {
  cursor: text;
}
.file-view-pre.is-tappable:active,
.file-view-md.is-tappable:active {
  background: rgba(255, 255, 255, 0.03);
}
#file-view-page .settings-header > :first-child,
#memory-edit-page .settings-header > :first-child { justify-self: start; }
#file-view-page .settings-header .settings-title,
#memory-edit-page .settings-header .settings-title {
  justify-self: start;
  text-align: left;
  padding-left: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  max-width: 100%;
}
.file-view-scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 6px 10px calc(12px + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.file-view-pre {
  flex: 1 1 auto;
  margin: 0;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 12px 14px;
  font-family: var(--font-ui);
  font-size: 13px;
  line-height: 1.5;
  color: var(--fg);
  white-space: pre-wrap;
  word-break: break-word;
  overflow-wrap: anywhere;
}
.file-view-md {
  flex: 1 1 auto;
  margin: 0;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 12px 14px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--fg);
  word-break: break-word;
  overflow-wrap: anywhere;
  -webkit-user-select: text;
  user-select: text;
}
.file-view-md[hidden] { display: none; }
.file-view-md a.link { color: var(--accent); text-decoration: underline; word-break: break-all; }
.file-view-md code.inline {
  background: rgba(255,255,255,0.06);
  padding: 1px 4px;
  border-radius: 3px;
  font-family: var(--font-ui);
  font-size: 12px;
}
.file-view-md strong { font-weight: 700; }
.file-view-md em { font-style: italic; }
.file-view-md h1, .file-view-md h2, .file-view-md h3,
.file-view-md h4, .file-view-md h5, .file-view-md h6 {
  margin: 14px 0 6px;
  font-weight: 700;
  color: var(--fg);
  line-height: 1.25;
}
.file-view-md h1 { font-size: 18px; }
.file-view-md h2 { font-size: 16px; }
.file-view-md h3 { font-size: 14.5px; }
.file-view-md h4, .file-view-md h5, .file-view-md h6 { font-size: 13.5px; opacity: 0.85; }
.file-view-md h1:first-child, .file-view-md h2:first-child, .file-view-md h3:first-child { margin-top: 0; }
.file-view-md ul.md-list {
  margin: 6px 0;
  padding-left: 20px;
  list-style: disc;
}
.file-view-md ul.md-list .md-list { margin: 2px 0; }
.file-view-md ul.md-list li { margin: 2px 0; }
.file-view-md ul.md-list li::marker { color: var(--accent); }
.file-view-md .md-table-wrap {
  overflow-x: auto;
  margin: 8px 0;
  -webkit-overflow-scrolling: touch;
}
.file-view-md .md-table {
  border-collapse: collapse;
  font-size: 12.5px;
  min-width: 100%;
}
.file-view-md .md-table th,
.file-view-md .md-table td {
  border: 1px solid var(--border);
  padding: 6px 8px;
  vertical-align: top;
  word-break: break-word;
  overflow-wrap: anywhere;
}
.file-view-md .md-table th {
  background: rgba(255,255,255,0.05);
  font-weight: 700;
  white-space: nowrap;
}
.file-view-md .md-table tbody tr:nth-child(odd) td {
  background: rgba(255,255,255,0.02);
}
.file-view-md pre.codeblock {
  margin: 8px 0;
  padding: 10px 12px;
  background: rgba(0,0,0,0.25);
  border: 1px solid var(--border);
  border-radius: 3px;
  font-family: var(--font-ui);
  font-size: 12px;
  line-height: 1.45;
  color: var(--fg);
  overflow-x: auto;
  white-space: pre;
  -webkit-overflow-scrolling: touch;
}
.file-view-md pre.codeblock code {
  font: inherit;
  color: inherit;
  background: transparent;
  padding: 0;
}
.file-view-textarea {
  flex: 1 1 auto;
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 12px 14px;
  font-family: var(--font-ui);
  font-size: 13px;
  line-height: 1.5;
  color: var(--fg);
  outline: none;
  resize: none;
  min-height: 200px;
}
.file-view-textarea:focus { border-color: var(--accent); }
.file-view-path {
  font-size: 11px;
  color: var(--fg-dim);
  word-break: break-all;
  overflow-wrap: anywhere;
  padding: 0 2px 4px;
}
.file-view-actions {
  display: flex;
  gap: 6px;
  margin-top: 4px;
  justify-content: flex-start;
}
.file-view-action-btn {
  width: 32px;
  height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  flex-shrink: 0;
}
/* display:flex/inline-flex выше переопределяет UA-правило [hidden]{display:none}.
   Возвращаем поведение атрибута, иначе у лога (path="") видны "пустые" save+delete
   снизу страницы. */
.file-view-actions[hidden],
.file-view-action-btn[hidden],
.memory-edit-actions[hidden],
.memory-edit-action-btn[hidden] {
  display: none !important;
}
.file-view-delete-icon {
  color: var(--red, #c0473b);
  border-color: var(--red, #c0473b);
}
.file-view-delete-icon:active { background: rgba(192, 71, 59, 0.08); }
.file-view-edit-btn {
  color: var(--fg-dim);
}
.file-view-edit-btn:active { color: var(--accent); }
.settings-header {
  display: grid;
  grid-template-columns: 32px 1fr 32px;
  align-items: center;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
}
.settings-page > .settings-header,
.settings-page > .tech-search-bar,
.settings-page > .tech-chips-row {
  touch-action: pan-y;
}
.settings-header > :first-child { justify-self: start; }
.settings-header > :last-child { justify-self: end; }
.settings-header .icon-btn {
  width: 32px;
  height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  line-height: 1;
}
.settings-title {
  text-align: center;
  color: var(--fg);
  font-weight: 700;
  letter-spacing: 1px;
  font-size: 12px;
  text-transform: uppercase;
}
.settings-scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  padding-bottom: 12px;
}
.settings-scroll::-webkit-scrollbar { width: 4px; }
.settings-scroll::-webkit-scrollbar-thumb { background: var(--scroll-thumb); }
.settings-group-title {
  color: var(--fg-dim);
  font-size: 10px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  padding: 14px 16px 6px;
}
.settings-version {
  color: var(--fg-dim);
  font-size: 11px;
  text-align: center;
  padding: 18px 16px 4px;
  opacity: 0.6;
}
.settings-cache-stats {
  color: var(--fg-dim);
  font-size: 10px;
  text-align: center;
  padding: 0 16px 10px;
  opacity: 0.45;
  font-family: var(--font-ui);
}
.settings-row {
  display: grid;
  grid-template-columns: 22px 1fr auto 16px;
  align-items: center;
  gap: 10px;
  width: 100%;
  background: transparent;
  color: var(--fg);
  border: none;
  border-top: 1px solid var(--border);
  padding: 12px 16px;
  font-family: inherit; font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.settings-row:last-child { border-bottom: 1px solid var(--border); }
.settings-row:active { background: var(--bg-card); color: var(--fg); }
.settings-row .row-icon {
  grid-column: 1;
  color: var(--fg-dim);
  font-size: 14px;
  /* Любой одиночный символ в строке настроек — текстовая глифа, не color emoji. */
  font-variant-emoji: text;
}
.settings-row .row-label { grid-column: 2; letter-spacing: 0.3px; }
.settings-row .row-value {
  grid-column: 3;
  color: var(--fg-dim);
  font-size: 12px;
  max-width: 130px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-align: right;
}
.settings-row .row-chev { grid-column: 4; color: var(--fg-dim); font-size: 16px; text-align: right; }
.settings-row.danger { color: var(--red); }
.settings-row.danger .row-icon { color: var(--red); }
.settings-row.danger:active { color: var(--red); background: var(--bg-card); }
.settings-row.static { cursor: default; }
.settings-row.static:active { background: transparent; }
.settings-row.static .row-value { grid-column: 3 / 5; max-width: 180px; }
/* Кнопки раздела «Действия» (переподключить WS / перезапустить туннель, сервер /
   перезагрузить комп) — заливка-прогресс долгого удержания, как карточки версий.
   --hp гонит scaleX 0→1, на 100% летит действие. user-select/touch-callout/
   touch-action глушим намеренно: без них iOS на долгом удержании запускает
   выделение текста или скролл страницы и шлёт pointercancel — hold рвётся на ~0.5с
   и до 100% не доходит (та же причина, что у «ДУМАЮ» выше). */
.settings-row.hold-row {
  position: relative; overflow: hidden;
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: none;
}
.settings-row.hold-row::before {
  content: ""; position: absolute; inset: 0;
  background: var(--accent, #4caf50); pointer-events: none;
  transform-origin: left center; transform: scaleX(var(--hp, 0));
  opacity: calc(0.16 + var(--hp, 0) * 0.5);
  transition: transform 0.14s linear, opacity 0.14s linear; z-index: 0;
}
/* ребут опаснее перезапусков — заливка красная */
.settings-row.hold-row[data-act="reboot-mac"]::before { background: var(--red); }
.settings-row.hold-row.hold-row-holding::before { transition: transform 60ms linear, opacity 60ms linear; }
.settings-row.hold-row.hold-row-holding { transform: scale(1.01); }
.settings-row.hold-row > * { position: relative; z-index: 1; }
.settings-row.hold-row.hold-row-snap { animation: qrSnap 0.34s cubic-bezier(0.34, 1.56, 0.64, 1) both; }
.conn-info-row { cursor: pointer; }
.conn-info-row .row-value { grid-column: 3; max-width: 150px; }
.conn-info-caret {
  grid-column: 4;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  color: var(--fg-dim);
  opacity: 0.55;
  transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease;
}
.conn-info-row[aria-expanded="true"] .conn-info-caret { transform: rotate(180deg); opacity: 0.9; }
/* Описание «что это значит» — раскрывается по тапу, плавно (glass). */
.conn-info-desc {
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  padding: 0 16px 0 48px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--fg-dim);
  transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1),
              opacity 0.22s ease,
              padding 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.conn-info-row[aria-expanded="true"] + .conn-info-desc {
  max-height: 140px;
  opacity: 0.72;
  padding-top: 2px;
  padding-bottom: 12px;
}
/* Экшн-иконки: classic-глиф по умолчанию, glass-SVG в стеклянной теме. */
.conn-act-icon .glass-icon { display: none; }
.conn-act-icon { display: flex; align-items: center; }
:root.theme-glass .conn-act-icon .classic-icon { display: none; }
:root.theme-glass .conn-act-icon .glass-icon { display: inline-flex; }
.conn-act-icon.busy { animation: connIconBusy 0.8s ease-in-out infinite; }
@keyframes connIconBusy {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}

/* ===== Routines page ===== */
.routines-page #routines-scroll { padding: 10px 12px 20px; }
.routines-sort-bar.hidden { display: none; }
.routines-sort-bar {
  display: flex;
  gap: 6px;
  margin: 0 0 12px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.routines-sort-bar::-webkit-scrollbar { display: none; }
.routines-sort-btn {
  flex: 0 0 auto;
  background: transparent;
  color: var(--fg-dim);
  border: 1px solid var(--border);
  border-radius: 0;
  padding: 6px 10px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.5px;
  cursor: pointer;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.routines-sort-btn:active { background: var(--bg-card); }
.routines-sort-btn.active,
.routines-sort-btn.is-active {
  color: var(--accent);
  border-color: var(--accent);
}
.routines-empty {
  color: var(--fg-dim);
  font-size: 12px;
  text-align: center;
  padding: 30px 10px;
  letter-spacing: 0.5px;
}
.routine-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 0;
  padding: 12px;
  margin-bottom: 14px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.routine-head { display: flex; flex-direction: column; gap: 4px; cursor: pointer; }
.routine-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.routine-status-dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
}
.routine-status-dot.on {
  background: #4ade80;
  box-shadow: 0 0 6px rgba(74, 222, 128, 0.55);
}
.routine-status-dot.off {
  background: var(--fg-dim);
  opacity: 0.55;
}
.routine-status-dot.running {
  background: #4ade80;
  opacity: 1;
  box-shadow: 0 0 0 1px rgba(74, 222, 128, 0.85), 0 0 8px rgba(74, 222, 128, 0.7);
  animation: threadBlink 1.05s steps(2, end) infinite;
}
.routine-title {
  color: var(--fg);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.5px;
  word-break: break-word;
  flex: 1 1 auto;
  min-width: 0;
}
.routine-title-input {
  background: transparent;
  border: 0;
  border-bottom: 1px dashed transparent;
  padding: 2px 0;
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.5px;
  color: var(--fg);
  width: 100%;
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
.routine-card.editing-title .routine-title-input {
  pointer-events: auto;
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}
.routine-title-input::placeholder {
  color: var(--fg-dim);
  opacity: 0.55;
  font-weight: 400;
}
.routine-title-input:focus {
  outline: none;
  border-bottom-color: var(--accent);
  border-bottom-style: solid;
}
.routine-subtitle {
  color: var(--fg-dim);
  font-size: 11px;
  font-family: var(--font-ui);
  letter-spacing: 0.3px;
  word-break: break-all;
  opacity: 0.85;
}
.routine-chevron {
  color: var(--fg-dim);
  font-size: 14px;
  line-height: 1;
  transition: transform 0.15s;
  flex: 0 0 auto;
  padding: 12px 14px;
  margin: -12px -8px -12px -4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-width: 44px;
  min-height: 44px;
  cursor: pointer;
  position: relative;
  z-index: 1;
  -webkit-tap-highlight-color: transparent;
}
.routine-chevron:active { color: var(--fg); }
.routine-card:not(.collapsed) .routine-chevron { transform: rotate(90deg); }
.routine-unread-badge {
  flex: 0 0 auto;
  min-width: 16px;
  height: 16px;
  padding: 0 5px;
  background: #d94343;
  color: #fff;
  border-radius: 999px;
  font-size: 10px;
  line-height: 16px;
  text-align: center;
  font-weight: 700;
  letter-spacing: 0;
  pointer-events: none;
  box-sizing: border-box;
  font-family: var(--font-ui);
}
.routine-unread-badge.hidden { display: none; }
.routine-card { gap: 12px; }
.routine-card.collapsed { gap: 0; }
.routine-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  overflow: hidden;
  max-height: 1600px;
  clip-path: inset(0 0 0 0);
  opacity: 1;
  transform-origin: top center;
  transition:
    max-height 0.22s steps(6, end),
    clip-path 0.22s steps(6, end),
    opacity 0.18s steps(3, end);
  will-change: clip-path, max-height, opacity;
}
.routine-card.collapsed .routine-body {
  max-height: 0;
  clip-path: inset(0 0 100% 0);
  opacity: 0.25;
}
.routine-desc {
  color: var(--fg);
  font-size: 12px;
  line-height: 1.45;
  word-break: break-word;
}
.routine-meta {
  color: var(--fg-dim);
  font-size: 10px;
  letter-spacing: 0.4px;
}
.routine-warn {
  color: var(--red);
  font-size: 11px;
  margin-top: 2px;
}

.routine-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--fg);
  cursor: pointer;
  user-select: none;
}
.routine-toggle input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 34px; height: 18px;
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: 9px;
  position: relative;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
  margin: 0;
}
.routine-toggle input[type="checkbox"]::after {
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 12px; height: 12px;
  background: var(--fg-dim);
  border-radius: 50%;
  transition: left 0.15s, background 0.15s;
}
.routine-toggle input[type="checkbox"]:checked {
  background: rgba(255, 110, 66, 0.18);
  border-color: var(--accent);
}
.routine-toggle input[type="checkbox"]:checked::after {
  left: 18px;
  background: var(--accent);
}

.routine-cron, .routine-prompt {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.routine-cron-lbl {
  color: var(--fg-dim);
  font-size: 10px;
  letter-spacing: 1px;
  text-transform: uppercase;
}
.routine-cron-lbl-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.routine-cron-help-btn {
  background: transparent;
  color: var(--fg-dim);
  border: 1px solid var(--border);
  border-radius: 0;
  width: 32px;
  align-self: stretch;
  padding: 0;
  font-family: inherit;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}
.routine-cron-help-btn.on {
  background: var(--bg-soft);
  color: var(--fg);
  border-color: var(--fg-dim);
}
.routine-cron-help {
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: 0;
  padding: 8px 10px;
  font-size: 11px;
  line-height: 1.5;
  color: var(--fg-dim);
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.routine-cron-inner {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.routine-cron-inner[hidden] { display: none; }
.routine-cron-input-row {
  display: flex;
  align-items: stretch;
  gap: 6px;
}
.routine-cron-input-row .routine-cron-input { flex: 1 1 auto; }
.routine-cron-help[hidden] {
  display: none;
}
.routine-collapse-btn {
  background: transparent;
  color: var(--fg-dim);
  border: 0;
  padding: 4px 0;
  font-family: inherit;
  font-size: 10px;
  letter-spacing: 1px;
  text-transform: uppercase;
  text-align: left;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
}
.routine-collapse-btn:active { color: var(--fg); }
.routine-collapse-btn.on { color: var(--fg); }
.routine-collapse-chev {
  display: inline-block;
  font-size: 10px;
  line-height: 1;
  transition: transform 0.12s ease;
}
.routine-collapse-btn.on .routine-collapse-chev {
  transform: rotate(90deg);
}
.routine-presets[hidden],
.routine-textarea[hidden],
.routine-cron-human[hidden] {
  display: none;
}
.routine-cron-help code {
  background: transparent;
  color: var(--fg);
  font-family: inherit;
  font-size: 11px;
}
.routine-cron-help-row code {
  letter-spacing: 0.5px;
}
.routine-cron-input,
.routine-textarea {
  background: var(--bg-soft);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 0;
  padding: 8px 10px;
  font-family: inherit;
  font-size: 12px;
  width: 100%;
  box-sizing: border-box;
}
.routine-cron-input:focus,
.routine-textarea:focus {
  outline: none;
  border-color: var(--accent);
}
.routine-cron-human {
  color: var(--fg-dim);
  font-size: 11px;
  font-style: italic;
}
.routine-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.routine-chip {
  background: transparent;
  color: var(--fg-dim);
  border: 1px solid var(--border);
  border-radius: 0;
  padding: 4px 8px;
  font-family: inherit;
  font-size: 11px;
  cursor: pointer;
}
.routine-chip:active {
  background: var(--bg-soft);
  color: var(--fg);
  border-color: var(--fg-dim);
}
.routine-textarea {
  resize: vertical;
  min-height: 100px;
  line-height: 1.4;
}
.routine-actions {
  display: flex;
  flex-wrap: nowrap;
  justify-content: flex-start;
  gap: 8px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  padding-bottom: 12px;
  margin: 0 0 -6px;
  scroll-padding-inline: 4px;
}
.routine-actions::-webkit-scrollbar { height: 3px; }
.routine-actions::-webkit-scrollbar-track { background: transparent; margin-top: 8px; }
.routine-actions::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: 2px; }
.routine-actions > button {
  flex: 0 0 auto;
  white-space: nowrap;
}
.routine-chat,
.routine-run,
.routine-save,
.routine-delete {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 0;
  padding: 8px 14px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.5px;
  cursor: pointer;
}
.routine-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border-radius: 0;
  flex: 0 0 auto;
  image-rendering: pixelated;
}
.routine-icon-btn svg {
  display: block;
  pointer-events: none;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
.routine-icon-btn.is-busy { opacity: 0.7; }
.routine-spin { animation: routine-spin-rot 0.9s steps(8) infinite; transform-origin: 50% 50%; }
@keyframes routine-spin-rot { to { transform: rotate(360deg); } }
.routine-chat:active,
.routine-run:active,
.routine-save:active,
.routine-delete:active { background: rgba(255, 110, 66, 0.12); }
.routine-run:disabled,
.routine-save:disabled,
.routine-delete[disabled] { opacity: 0.5; cursor: default; }

.routine-new-form {
  border-color: var(--accent);
}
.routine-new-form .routine-title {
  color: var(--accent);
}

/* ===== Routine: журнал запусков ===== */
.routine-runs {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.routine-runs-count {
  font-size: 10px;
  color: var(--fg-dim);
  opacity: 0.7;
  letter-spacing: 0.5px;
  font-variant-numeric: tabular-nums;
}
.routine-runs-list[hidden] { display: none; }
.routine-runs-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.routine-runs-empty {
  font-size: 11px;
  color: var(--fg-dim);
  opacity: 0.6;
  padding: 4px 0;
}
.routine-run-row {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 5px 6px;
  border-radius: 0;
  font-size: 12px;
  cursor: pointer;
  color: var(--fg);
  background: transparent;
  transition: background 0.12s;
}
.routine-run-head {
  display: grid;
  grid-template-columns: 12px 1fr auto auto;
  align-items: center;
  gap: 8px;
}
.routine-run-summary {
  font-size: 11px;
  color: var(--fg-dim);
  line-height: 1.35;
  padding-left: 20px;
  white-space: pre-wrap;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  word-break: break-word;
}
.routine-run-row:hover { background: rgba(255, 255, 255, 0.04); }
.routine-run-row:active { background: rgba(255, 255, 255, 0.08); }
.routine-run-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
}
.routine-run-dot.status-running {
  background: #4ade80;
  box-shadow: 0 0 0 1px rgba(74, 222, 128, 0.85), 0 0 8px rgba(74, 222, 128, 0.7);
  animation: threadBlink 1.05s steps(2, end) infinite;
}
.routine-run-dot.status-ok {
  background: #4ade80;
  box-shadow: 0 0 4px rgba(74, 222, 128, 0.5);
}
.routine-run-dot.status-fail {
  background: #f87171;
  box-shadow: 0 0 4px rgba(248, 113, 113, 0.5);
}
.routine-run-dot.status-warn {
  background: #f0b429;
  box-shadow: 0 0 4px rgba(240, 180, 41, 0.55);
}
.routine-run-dot.status-neutral {
  background: var(--fg-dim);
  opacity: 0.6;
}
.routine-run-time {
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.routine-run-dur {
  color: var(--fg-dim);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.routine-run-tag {
  font-size: 10px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: 0;
  border: 1px solid var(--fg-dim);
  color: var(--fg-dim);
  white-space: nowrap;
}
.routine-run-row.status-running .routine-run-tag {
  border-color: #4ade80;
  color: #4ade80;
}
.routine-run-row.status-ok .routine-run-tag {
  border-color: rgba(74, 222, 128, 0.6);
  color: #4ade80;
}
.routine-run-row.status-fail .routine-run-tag {
  border-color: rgba(248, 113, 113, 0.7);
  color: #f87171;
}
.routine-run-row.status-warn .routine-run-tag {
  border-color: rgba(240, 180, 41, 0.7);
  color: #f0b429;
}

/* Свайп: обёртка как у чатов — position:relative + overflow:hidden. Сама строка
   двигается transform'ом, action-кнопка mark/unmark лежит под строкой слева. */
.routine-run-swipe-wrap {
  position: relative;
  overflow: hidden;
}
.start-row-swipe .routine-run-row {
  position: relative;
  z-index: 1;
  background: var(--bg);
  width: 100%;
  box-sizing: border-box;
}
.routine-run-row.is-unread-run {
  position: relative;
}
.routine-run-row.is-unread-run::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6px;
  bottom: 6px;
  width: 3px;
  background: var(--accent);
  border-radius: 2px;
  pointer-events: none;
}
.routine-run-row.is-unread-run .routine-run-time {
  color: var(--fg);
}

/* ===== Thread sections (pinned / recents) ===== */
.thread-section {
  font-size: 10px;
  letter-spacing: 1.5px;
  color: var(--fg-dim);
  padding: 10px 12px 4px;
  text-transform: uppercase;
}
.thread-pin {
  flex: 0 0 auto;
  margin-left: 8px;
  /* Фикс-ширина: SVG в classic-теме 12px, в glass 14px — без width
     иконка съезжает по своей естественной ширине и закрепы плавают вправо. */
  width: 16px;
  color: var(--accent-dim);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 0;
}
.thread-pin.draggable {
  padding: 6px 4px;
  /* draggable добавляет padding'и — сохраняем общую ширину контейнера,
     иначе draggable-пин шире non-draggable и колонка скачет. */
  width: 24px;
  margin-left: 4px;
  cursor: grab;
  touch-action: none;
}
.thread-pin.draggable:active { cursor: grabbing; }
.thread-item.dragging {
  z-index: 50;
  background: var(--bg-card);
  box-shadow: 0 6px 18px rgba(0,0,0,0.55);
  border-color: var(--accent);
}
.thread-item.dragging .thread-pin { color: var(--accent); }
#thread-list.pin-dragging .thread-swipe-wrap { overflow: visible; }
#thread-list.pin-dragging .thread-pin-act,
#thread-list.pin-dragging .thread-mark-act,
#thread-list.pin-dragging .thread-settings-act { visibility: hidden; }
#thread-list.pin-dragging .thread-item:active { animation: none; }
#thread-list.pin-dragging { user-select: none; -webkit-user-select: none; }
.thread-status {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  border-radius: 0;
  margin-right: 8px;
  background: var(--fg-dim);
  opacity: 0.45;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 600;
  line-height: 1;
  color: var(--bg);
  font-variant-numeric: tabular-nums;
  /* v935: убрали отрицательный letter-spacing — он сдвигал "99" влево, текст
     визуально не был по центру круга. Cap "99+" → "99" (см. app.js) гарантирует
     что больше 2 символов в бейдж не попадёт. */
  letter-spacing: 0;
  text-align: center;
}
.thread-status.unread {
  background: var(--accent);
  opacity: 1;
  color: var(--bg);
}
.thread-status.thinking {
  background: var(--accent);
  opacity: 1;
  box-shadow: 0 0 0 1px var(--accent), 0 0 6px rgba(217, 119, 87, 0.55);
  animation: threadBlink 0.85s ease-in-out infinite;
}
@keyframes threadBlink {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.25; transform: scale(0.85); }
}
.thread-status.waiting {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--fg-dim);
  opacity: 1;
  animation: waitingPulse 1.4s ease-in-out infinite;
}
.thread-status.stopped {
  background: #f0b429;
  opacity: 1;
}
.thread-status.status-ok {
  background: var(--green);
  opacity: 1;
  box-shadow: 0 0 0 1px var(--green), 0 0 6px rgba(127, 184, 127, 0.55);
}
.thread-status.status-fail {
  background: #d94343;
  opacity: 1;
}
.thread-status.status-warn {
  background: #f0b429;
  opacity: 1;
  box-shadow: 0 0 0 1px #f0b429, 0 0 6px rgba(240, 180, 41, 0.55);
}
.thread-status.status-stopped {
  background: #888;
  opacity: 1;
}

/* ===== Attach row + chips ===== */
/* DOM-порядок — от новой к старой (renderAttachRow вставляет prepend'ом).
   Скролл влево не нужен, поэтому justify-content: flex-start. */
#attach-row {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  flex-wrap: nowrap;
  gap: 4px;
  /* padding-bottom + negative margin — чтобы тени превью (~16px вниз в glass)
     не клипались overflow-x: auto, при этом layout вокруг не плыл. */
  padding: 0 2px 18px 2px;
  margin-bottom: -18px;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  max-height: 88px;
  opacity: 1;
  transform: translateY(0);
  transition: max-height 0.28s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 0.22s ease,
              transform 0.28s cubic-bezier(0.22, 1, 0.36, 1),
              padding 0.28s cubic-bezier(0.22, 1, 0.36, 1),
              margin 0.28s cubic-bezier(0.22, 1, 0.36, 1),
              visibility 0s linear 0s;
}
#attach-row::-webkit-scrollbar { display: none; }
#attach-row > .attach-chip { flex: 0 0 auto; }
#attach-row.hidden {
  display: flex;
  max-height: 0;
  opacity: 0;
  transform: translateY(6px);
  padding-top: 0;
  padding-bottom: 0;
  margin: 0;
  pointer-events: none;
  visibility: hidden;
  overflow: hidden;
  transition: max-height 0.24s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 0.18s ease,
              transform 0.24s cubic-bezier(0.22, 1, 0.36, 1),
              padding 0.24s cubic-bezier(0.22, 1, 0.36, 1),
              margin 0.24s cubic-bezier(0.22, 1, 0.36, 1),
              visibility 0s linear 0.24s;
}
.attach-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  color: var(--fg);
  padding: 3px 6px 3px 8px;
  font-size: 11px;
  max-width: 100%;
  animation: attach-chip-in 0.32s cubic-bezier(0.22, 1, 0.36, 1);
  transform-origin: center;
  transform: scale(1);
  opacity: 1;
  transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 0.22s ease;
}
/* Шторка скрыта → чипы внутри сжимаются и гаснут синхронно с max-height
   родителя. У .attach-chip убран `both` у entry-анимации, поэтому базовое
   состояние (scale 1, opacity 1) восстанавливается после её окончания и
   transition к scale(0.4) работает как ожидается. */
#attach-row.hidden > .attach-chip {
  transform: scale(0.4);
  opacity: 0;
}
.attach-chip.attach-chip--removing {
  animation: attach-chip-out 0.36s cubic-bezier(0.4, 0, 0.6, 1) both;
  pointer-events: none;
}
@keyframes attach-chip-in {
  from { opacity: 0; transform: translateY(8px) scale(0.88); }
  to   { opacity: 1; transform: translateY(0)   scale(1); }
}
@keyframes attach-chip-out {
  from { opacity: 1; transform: translateY(0)    scale(1); }
  to   { opacity: 0; transform: translateY(-4px) scale(0.85); }
}
/* Кроссфейд внутри chip'а при переходе uploading→ready: проценты+бар
   уходят в opacity:0, новый .attach-size въезжает opacity:0→1 с overlap.
   Тайминги в CSS должны совпадать с setTimeout в swapUploadingChipToReady. */
.attach-chip .attach-fade-out {
  animation: attach-piece-fade-out 0.32s cubic-bezier(0.4, 0, 0.6, 1) forwards;
  pointer-events: none;
}
.attach-chip .attach-fade-in {
  animation: attach-piece-fade-in 0.45s cubic-bezier(0.22, 1, 0.36, 1) 0.05s both;
}
@keyframes attach-piece-fade-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}
@keyframes attach-piece-fade-in {
  from { opacity: 0; transform: translateY(3px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.attach-chip .attach-thumb {
  width: 48px; height: 48px;
  object-fit: cover;
  border: 1px solid var(--border);
  cursor: pointer;
  flex: 0 0 auto;
  display: block;
}
.attach-chip .attach-thumb:active { opacity: 0.7; }
/* v985: бейдж с номером — совпадает с @N из попапа меншенов.
   v986: цвет цифры жёстко #fff — на accent читается во всех темах
   (var(--bg) в тёмной даёт тёмное на акценте). */
.attach-chip .attach-chip-num {
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  flex: 0 0 auto;
  border-radius: 9px;
}
.attach-chip .attach-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 140px;
}
.attach-chip .attach-size {
  color: var(--fg-dim);
  font-size: 10px;
}
.attach-chip .attach-rm {
  appearance: none; -webkit-appearance: none;
  background: transparent;
  border: none;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 0 2px;
  font-size: 12px;
  line-height: 1;
}
.attach-chip .attach-rm:active { color: var(--red); }
.attach-chip .attach-retry {
  appearance: none; -webkit-appearance: none;
  background: transparent;
  border: none;
  color: var(--accent);
  cursor: pointer;
  padding: 0 2px;
  font-size: 14px;
  line-height: 1;
}
.attach-chip .attach-retry:active { opacity: 0.6; }
.attach-chip.pending {
  color: var(--fg-dim);
  border-style: dashed;
  font-style: italic;
}
.attach-chip.uploading {
  border-style: dashed;
}
.attach-chip .attach-progress-pct {
  color: var(--accent);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  min-width: 32px;
  text-align: right;
}
.attach-chip .attach-progress-bar {
  display: inline-block;
  width: 60px;
  height: 4px;
  background: var(--border);
  position: relative;
  border-radius: 2px;
  overflow: hidden;
  flex: 0 0 auto;
}
.attach-chip .attach-progress-fill {
  display: block;
  height: 100%;
  background: var(--accent);
  width: 0;
  transition: width 120ms linear;
}

/* attachments inside an existing message bubble */
.msg-attach-row {
  display: flex; flex-wrap: wrap; gap: 4px;
  margin-top: 6px;
}
.msg-attach-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--border);
  padding: 3px 8px 3px 4px;
  font-size: 11px;
  max-width: 100%;
}
.msg-attach-chip.clickable { cursor: pointer; }
.msg-attach-chip.clickable:active { opacity: 0.7; }
.msg-attach-chip .msg-attach-thumb {
  width: 56px; height: 56px;
  object-fit: cover;
  border: 1px solid var(--border);
  flex: 0 0 auto;
  display: block;
  /* Превью fade-in: до load — opacity 0, после .loaded — 1. Пока
     грузится, под img показывается shimmer фон chip'а (.thumb-loading).
     На onerror — img удаляется, на его место ставится иконка файла
     с тем же fade-in (см. .msg-attach-ico.loaded ниже). */
  opacity: 0;
  transition: opacity 0.28s ease;
}
.msg-attach-chip .msg-attach-thumb.loaded { opacity: 1; }
/* Shimmer-плейсхолдер пока превью грузится — мягкий пробег градиента
   по фону chip'а. Без него превью «выскакивает» с резким скачком. */
.msg-attach-chip.thumb-loading {
  background: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0.04) 0%,
    rgba(255, 255, 255, 0.10) 50%,
    rgba(255, 255, 255, 0.04) 100%
  );
  background-size: 200% 100%;
  animation: msg-attach-shimmer 1.1s linear infinite;
}
@keyframes msg-attach-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
/* Иконка файла, появившаяся вместо битого превью — тот же плавный fade. */
.msg-attach-chip .msg-attach-ico {
  opacity: 0;
  transition: opacity 0.28s ease;
}
.msg-attach-chip .msg-attach-ico.loaded { opacity: 1; }
/* Если рендерится сразу с иконкой (не imgSrc) — показываем без задержки. */
.msg-attach-chip:not(.thumb-loading):not(.thumb-error) .msg-attach-ico {
  opacity: 1;
}
.msg-attach-chip .msg-attach-ico {
  padding-left: 4px;
  font-size: 12px;
  color: var(--fg-dim);
  display: inline-flex;
  align-items: center;
}
.msg-attach-chip .msg-attach-ico .pixel-clip {
  width: 9px;
  height: 14px;
  display: block;
}
.msg-attach-chip .msg-attach-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 180px;
}
/* v1176: картинка-вложение = чистое превью внутри баббла (как в веб-версии):
   без тёмной карточки/обводки и без подписи имени файла. На onerror JS снимает
   .is-image → возвращается обычный chip с иконкой и именем. */
.msg-attach-chip.is-image {
  background: transparent;
  border: none;
  padding: 0;
  gap: 0;
  max-width: 100%;
}
.msg-attach-chip.is-image .msg-attach-name { display: none; }
.msg-attach-chip.is-image .msg-attach-thumb {
  width: auto;
  height: auto;
  max-width: min(220px, 100%);
  max-height: 240px;
  border: none;
  border-radius: 12px;
}
/* плейсхолдер пока превью грузится — shimmer из .thumb-loading остаётся,
   даём ему минимальный размер, чтобы было где переливаться. */
.msg-attach-chip.is-image.thumb-loading {
  min-width: 88px;
  min-height: 64px;
  border-radius: 12px;
}

/* ===== Attach + mic buttons ===== */
#attach-btn, #mic-btn, #voice-mode-btn, #clear-input-btn {
  min-width: 40px; height: 40px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--fg-dim);
  box-sizing: border-box;
  box-shadow: 2px 2px 0 0 rgba(0,0,0,0.55);
}
.pixel-btn svg { width: 16px; height: 16px; display: block; }
.pixel-btn svg.pixel-clip { width: 10px; height: 16px; }
#mic-btn.recording {
  color: var(--accent);
  border-color: var(--accent);
  animation: mic-pulse 0.9s ease-in-out infinite;
}

/* Нижняя полоска под textarea — часть единого картона композера (один фон,
   одна общая рамка с textarea, без видимого сепаратора). Слева — attach +
   clear (когда есть текст), справа — voice-mode, mic, send. Между ними
   .bar-spacer растягивается. */
.input-bottom-bar {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 2px;
  height: 44px;
  padding: 0 4px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-top: 0;
  box-sizing: border-box;
}
.input-bottom-bar > #attach-btn,
.input-bottom-bar > #paste-input-btn,
.input-bottom-bar > #clear-input-btn,
.input-bottom-bar > #copy-input-btn,
.input-bottom-bar > #voice-mode-btn,
.input-bottom-bar > #mic-btn,
.input-bottom-bar > #send.input-bar-btn-send {
  /* v943: touch-action: manipulation — на iOS WKWebView без этого Safari
     отдаёт touchstart родителю как кандидата на pan/swipe-жест, и если
     палец сдвинулся даже на 3-4px, click не доходит до кнопки. Кирилл:
     «скрепка не работает через раз», «кнопка маскота бывает не работает».
     manipulation убирает double-tap-to-zoom и cancel-on-move, click стрельнёт
     сразу на touchend.  */
  touch-action: manipulation;
}
.input-bottom-bar > #attach-btn,
.input-bottom-bar > #paste-input-btn,
.input-bottom-bar > #clear-input-btn,
.input-bottom-bar > #copy-input-btn,
.input-bottom-bar > #voice-mode-btn,
.input-bottom-bar > #mic-btn {
  width: 40px; min-width: 40px; height: 40px;
  padding: 0;
  background: transparent;
  border: 0;
  box-shadow: none;
  color: var(--fg-dim);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  /* Базовый transition на цвет/трансформ для всех кнопок панели — чтобы
     при любых state-change'ах (recording, paste-flash и т.п.) изменения
     шли плавно, а не щелчком. */
  transition:
    width .22s cubic-bezier(.22,.61,.36,1),
    min-width .22s cubic-bezier(.22,.61,.36,1),
    opacity .18s ease,
    transform .22s cubic-bezier(.22,.61,.36,1),
    color .15s ease;
}
.input-bottom-bar > .input-bar-btn:hover { color: var(--fg); }
/* Скрытое состояние paste/clear/copy — не display:none, а схлоп по width
   + opacity, чтобы появление/исчезновение шло плавно и соседние кнопки
   (включая скрепку) доезжали на своё место без щелчка. */
.input-bottom-bar > #clear-input-btn,
.input-bottom-bar > #copy-input-btn,
.input-bottom-bar > #paste-input-btn {
  width: 0;
  min-width: 0;
  opacity: 0;
  transform: scale(0.6);
  pointer-events: none;
  overflow: hidden;
}
/* Paste показывается справа от скрепки при фокусе на поле ввода (тап по
   инпуту) ИЛИ когда уже есть набранный текст. Без фокуса и без текста —
   скрыта, чтобы не висеть в холостом композёре. */
.input-wrap.has-content .input-bottom-bar > #paste-input-btn,
.input-wrap.has-focus .input-bottom-bar > #paste-input-btn {
  width: 40px; min-width: 40px; opacity: 1; transform: scale(1); pointer-events: auto;
}
/* На десктопе тихая проверка через Permissions API: если буфер пуст —
   JS вешает .no-clipboard и кнопка гаснет. На iOS readText без
   user-gesture зарежектится, флаг останется снят — кнопка видна всегда
   при has-content/has-focus; на тапе по пустому буферу даём knock + flash-fail. */
.input-wrap.has-content .input-bottom-bar > #paste-input-btn.no-clipboard,
.input-wrap.has-focus .input-bottom-bar > #paste-input-btn.no-clipboard {
  width: 0; min-width: 0; opacity: 0; transform: scale(0.6); pointer-events: none;
}
.input-bottom-bar > #paste-input-btn.flash-ok { color: var(--green); }
.input-bottom-bar > #paste-input-btn.flash-fail { color: var(--accent); }
.input-wrap.has-content .input-bottom-bar > #clear-input-btn,
.input-wrap.has-content .input-bottom-bar > #copy-input-btn {
  width: 40px; min-width: 40px; opacity: 1; transform: scale(1); pointer-events: auto;
}
.input-bottom-bar > #copy-input-btn.flash-ok { color: var(--green); }
.bar-spacer { flex: 1 1 auto; }
.input-bottom-bar > #mic-btn.recording {
  color: var(--accent);
  animation: none;
  box-shadow: none;
}
/* Зачёркнутый микрофон, пока идёт голосовой ввод (showVoiceUI вешает .recording
   на #mic-btn ровно на время записи). Диагональный слэш-оверлей поверх иконки —
   сигнал «идёт захват голоса». currentColor → линия всегда в цвет иконки в любой
   теме. Оверлеем ::after, а не <line> внутри SVG, чтобы одним правилом покрыть оба
   варианта иконки (glass-SVG и classic-pixel). overflow:hidden у .input-bar-btn
   клипует по кнопке, но 22px-слэш по центру 40px-кнопки помещается. z-index:2 —
   поверх иконки (дети кнопки на z-index:1). */
#mic-btn.recording::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 22px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
  transform: translate(-50%, -50%) rotate(45deg);
  z-index: 2;
  pointer-events: none;
}
.input-bottom-bar > #voice-mode-btn.active {
  color: var(--green);
  animation: vmo-icon-pulse 1.4s ease-in-out infinite;
}
.input-bottom-bar > #voice-mode-btn.holding,
.input-bottom-bar > #mic-btn.holding {
  background: var(--accent);
  color: #1a1306;
  border-radius: 6px;
}
.input-bottom-bar > #send.input-bar-btn-send {
  width: 30px; min-width: 30px; height: 30px;
  margin-left: 4px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: none;
  background: var(--accent);
  border: 1px solid var(--accent);
  color: var(--bg);
  border-radius: 15px;
}
.input-bottom-bar > #send.input-bar-btn-send .send-mascot { width: 18px; height: 15px; display: block; }
.input-bottom-bar > #send.input-bar-btn-send .send-mascot > g { fill: var(--bg); }
.input-bottom-bar > #send.input-bar-btn-send .send-mascot-eyelids { fill: var(--bg); }
@keyframes mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(217, 119, 87, 0.5); }
  50%      { box-shadow: 0 0 0 6px rgba(217, 119, 87, 0); }
}
@keyframes vmo-icon-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

/* ===== Voice recording strip (inline в .input-bottom-bar) =====
   Занимает место .bar-spacer во время записи: канвас с частотами + таймер,
   зажатые между attach (слева) и voice/mic/send (справа). Сам textarea
   при этом не перекрывается — остаётся видимым. */
#voice-strip {
  position: relative;
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  height: 100%;
  box-sizing: border-box;
  padding: 10px 4px 10px 12px;
}
#voice-strip.hidden { display: none; }
/* body.recording-leaving держит тот же layout, что и body.recording, чтобы
   во время leave-волны (420мс) кнопки/спейсер не сдвигались. Снимается
   таймером в hideRecStrip после того как канвас успел доиграть. */
body.recording .bar-spacer,
body.recording-leaving .bar-spacer { display: none; }
body.recording #input-ghost,
body.recording-leaving #input-ghost { display: none; }
#voice-strip-wave {
  flex: 1 1 auto;
  align-self: stretch;
  min-width: 0;
  display: block;
}
#voice-strip-timer {
  flex: 0 0 auto;
  color: var(--accent);
  font-family: inherit;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.5px;
}
/* «распознаю...» лежит абсолютным оверлеем над wave/timer, opacity-переход
   делает кросс-фейд: wave-канвас доигрывает leave-волну (JS rAF, ~420мс),
   thinking параллельно фейдится из 0 → 1. Layout не дёргается. */
#voice-strip-thinking {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  color: var(--accent);
  font-family: inherit;
  font-size: 12px;
  letter-spacing: 0.5px;
  opacity: 0;
  transform: translateY(6px) scale(0.96);
  transition: opacity 280ms ease-out, transform 280ms ease-out;
  pointer-events: none;
}
body.transcribing #voice-strip-thinking {
  opacity: 1;
  transform: translateY(0) scale(1);
  pointer-events: auto;
}
/* Кросс-фейд: wave/timer уходят в opacity:0, пока thinking приходит.
   display:none НЕ ставим — JS leave-волна должна успеть отрисоваться. */
body.transcribing #voice-strip-wave,
body.transcribing #voice-strip-timer {
  opacity: 0;
  transition: opacity 220ms ease-out;
}
/* v1039: голосовой режим — освобождаем место для voice-strip, схлопывая ВСЕ
   левые кнопки композера (скрепка/вставка/очистка/копирование). Прячем и
   когда слева только скрепка (пустой инпут), и когда есть текст и видны
   paste/clear/copy. Высокая специфичность (1,3,1) через `.input-wrap`
   перебивает show-правила .has-content/.has-focus (1,3,0). Схлоп тем же
   width/opacity/scale, что и базовый transition — кнопки уезжают плавно. */
body.recording .input-wrap .input-bottom-bar > #attach-btn,
body.recording-leaving .input-wrap .input-bottom-bar > #attach-btn,
body.recording .input-wrap .input-bottom-bar > #paste-input-btn,
body.recording-leaving .input-wrap .input-bottom-bar > #paste-input-btn,
body.recording .input-wrap .input-bottom-bar > #clear-input-btn,
body.recording-leaving .input-wrap .input-bottom-bar > #clear-input-btn,
body.recording .input-wrap .input-bottom-bar > #copy-input-btn,
body.recording-leaving .input-wrap .input-bottom-bar > #copy-input-btn {
  width: 0; min-width: 0; opacity: 0; transform: scale(0.7); pointer-events: none;
  overflow: hidden;
}
body.recording .input-bottom-bar > #voice-mode-btn,
body.recording-leaving .input-bottom-bar > #voice-mode-btn { display: none; }
.voice-thinking-dot {
  width: 5px; height: 5px;
  background: var(--accent);
  border-radius: 50%;
  display: inline-block;
  animation: voice-think 1.2s infinite ease-in-out both;
}
.voice-thinking-dot:nth-child(2) { animation-delay: 0.15s; }
.voice-thinking-dot:nth-child(3) { animation-delay: 0.3s; }
.voice-strip-label { margin-left: 4px; }
@keyframes voice-think {
  0%, 80%, 100% { opacity: 0.25; transform: scale(0.7); }
  40% { opacity: 1; transform: scale(1.2); }
}
/* Содержимое .input-wrap (`>`-ghost, voice-таймер/метка, voice-thinking-точки)
   наследует --bg-mix от .input-wrap (custom-properties inherit by default).
   color интерполируется между var(--fg) и #ffffff пропорционально, как
   у chip/attach-chip/fav-react-chip — синхронно с панелью композера. */
:root.theme-glass #input-ghost,
:root.theme-glass #input-ghost .ghost-cur,
:root.theme-glass #voice-strip-thinking,
:root.theme-glass .voice-strip-label,
:root.theme-glass #voice-strip-timer {
  color: color-mix(in srgb, var(--fg) calc((1 - var(--bg-mix, 0)) * 100%), #ffffff);
  transition: color 0.08s linear;
}
:root.theme-glass .voice-thinking-dot {
  background: color-mix(in srgb, var(--fg) calc((1 - var(--bg-mix, 0)) * 100%), #ffffff);
  transition: background 0.08s linear;
}

/* Частоты голосового ввода — цвет привязан к --accent (читается в app.js
   через getComputedStyle), glow через filter: drop-shadow + пульсация.
   color-mix даёт прозрачные версии акцентного цвета для свечения. */
#voice-strip-wave {
  filter: drop-shadow(0 0 4px color-mix(in srgb, var(--accent) 55%, transparent));
  animation: voiceWaveGlow 1.6s ease-in-out infinite;
  transform-origin: 50% 50%;
}
@keyframes voiceWaveGlow {
  0%, 100% { filter: drop-shadow(0 0 3px color-mix(in srgb, var(--accent) 40%, transparent)); }
  50%      { filter: drop-shadow(0 0 8px color-mix(in srgb, var(--accent) 75%, transparent)); }
}
/* Анимация появления частот: лёгкий fade + scaleY от центра, ~320мс.
   Применяется одноразово при старте записи (класс снимается/ставится в startWaveform). */
#voice-strip-wave.appearing {
  animation: voiceWaveGlow 1.6s ease-in-out infinite, voiceWaveAppear 320ms ease-out;
}
@keyframes voiceWaveAppear {
  0%   { opacity: 0; transform: scaleY(0.4); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: scaleY(1); }
}
/* Сворачивание частот: JS (playWaveformLeave) рисует зеркальную волну
   от краёв к центру на последнем snapshot freqData. ДОПОЛНИТЕЛЬНО — CSS
   transform: scaleY → 0 от центра + opacity-fade в последние 40%. Это
   фолбэк: на iOS-WebView JS rAF может «провалиться» под смену классов
   в том же тике, CSS-анимация работает на GPU и точно отыграет. */
#voice-strip.leaving {
  pointer-events: none;
}
#voice-strip.leaving #voice-strip-wave {
  animation: voiceWaveGlow 1.6s ease-in-out infinite,
             voiceWaveLeave 420ms cubic-bezier(0.55, 0, 0.55, 0.3) forwards;
}
#voice-strip.leaving #voice-strip-timer {
  animation: voiceTimerLeave 280ms ease-out forwards;
}
@keyframes voiceWaveLeave {
  0%   { opacity: 1; transform: scaleY(1); }
  55%  { opacity: 1; transform: scaleY(0.6); }
  100% { opacity: 0; transform: scaleY(0); }
}
@keyframes voiceTimerLeave {
  0%   { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(4px); }
}

/* light-mode safety */
@media (prefers-color-scheme: light) {
  /* в Telegram теме всё равно чёрный фон — мы тут ничего не меняем */
}

/* ===== Адаптив для узких окон ===== */

/* Чипы (model/effort/view/инструменты) — горизонтальный скролл вместо переноса. */
@media (max-width: 860px) {
  #meta-row {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    /* Сдвигаем строку чипов от краёв экрана — иначе крайние пилюли упираются
       в кромку viewport'а и визуально обрезаются. Дополняем negative-margin'ом,
       чтобы рамка скролл-контейнера осталась прежней ширины. */
    padding-left: 12px;
    padding-right: 12px;
    scroll-padding-inline: 12px;
    /* + padding-bottom для тени чипов (~16px вниз в glass): overflow-x: auto
       автоматически клипит и по вертикали, тень обрезалась снизу. negative
       margin компенсирует, чтобы layout композёра не уехал. */
    padding-bottom: 18px;
    margin-bottom: -18px;
  }
  #meta-row::-webkit-scrollbar { display: none; }
  #meta-row > .chip { flex: 0 0 auto; }

  /* На мобильных отступаем композер от скруглённых углов iPhone, но только
     когда клавиатура убрана. Триггер — `body.kb-active` (его ставит tg-shim
     по нативному keyboardWillShow). Никакого transition: padding скачет
     мгновенно одновременно с keyboardWillShow, нативный resize клавиатуры
     забирает фокус — на глаз воспринимается как одно движение. */
  body:not(.is-desktop):not(.kb-active) #input-row {
    padding-left: 18px;
    padding-right: 18px;
  }

  /* Коллапсированный композер: пока инпут пуст и без фокуса — однострочный
     режим. .input-bottom-bar превращается в плавающий ряд абсолютно сверху
     textarea: attach слева, voice/mic/send справа. Текст не залезает под
     кнопки за счёт левого/правого padding'а textarea. Как только фокус
     или есть текст — bottom-bar возвращается на свою обычную позицию
     ниже textarea (с разделителем), padding'и сбрасываются. */
  body:not(.is-desktop):has(#input:placeholder-shown:not(:focus)) .input-bottom-bar {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: auto;
    height: 40px;
    padding: 0 6px;
    background: transparent;
    border: 0;
    box-shadow: none;
    z-index: 3;
    pointer-events: none;
  }
  body:not(.is-desktop):has(#input:placeholder-shown:not(:focus)) .input-bottom-bar > * {
    pointer-events: auto;
  }
  body:not(.is-desktop):has(#input:placeholder-shown:not(:focus)) #input {
    padding-left: 44px;
    padding-right: 116px;
    border-bottom: 1px solid var(--border);
  }
  body:not(.is-desktop):has(#input:placeholder-shown:not(:focus)) #input-ghost {
    padding-left: 44px;
  }
}

/* Десктоп: тот же collapse-режим, что и на мобилке. Пока инпут пуст и без фокуса —
   .input-bottom-bar едет наверх абсолютом поверх textarea (скрепка слева, voice/send
   справа), а ghost-стрелка `>` получает padding-left, чтобы рисоваться правее
   скрепки. Иначе на широком экране ghost торчит в верхнем левом углу один,
   а скрепка снизу — выглядит «потерянным» прямоугольником. */
body.is-desktop:has(#input:placeholder-shown:not(:focus)) .input-bottom-bar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: auto;
  height: 40px;
  padding: 0 6px;
  background: transparent;
  border: 0;
  box-shadow: none;
  z-index: 3;
  pointer-events: none;
}
body.is-desktop:has(#input:placeholder-shown:not(:focus)) .input-bottom-bar > * {
  pointer-events: auto;
}
body.is-desktop:has(#input:placeholder-shown:not(:focus)) #input {
  padding-left: 44px;
  padding-right: 116px;
  border-bottom: 1px solid var(--border);
}
body.is-desktop:has(#input:placeholder-shown:not(:focus)) #input-ghost {
  padding-left: 44px;
}

/* Узкое окно (< 820) — отключаем pinned-drawer и переходим в мобильный режим:
   drawer скрыт по умолчанию, открывается оверлеем, кнопка ☰ снова видна. */
@media (max-width: 819px) {
  body.is-desktop #app { padding-left: 0 !important; }
  body.is-desktop #drawer {
    transform: translateX(-100%) !important;
    transition: transform 0.32s cubic-bezier(.22,.61,.36,1) !important;
    box-shadow: 0 0 24px rgba(0,0,0,0.6);
  }
  body.is-desktop #drawer:not(.hidden) { transform: translateX(0) !important; }
  body.is-desktop #drawer-overlay,
  body.is-desktop #drawer-close,
  body.is-desktop #threads-btn,
  body.is-desktop #edge-handle {
    display: revert !important;
  }
  body.is-desktop #drawer-overlay.hidden { display: none !important; }
  body.is-desktop #drawer-resize { display: none !important; }
}

/* На узких экранах — мельчим подзаголовок, но не прячем. */
@media (max-width: 560px) {
  #topbar .logo .subtitle { font-size: 8px; }
}

/* ===== 8-bit pixel animations ===== */

@keyframes msgIn {
  0%   { opacity: 0; transform: translateY(10px); }
  100% { opacity: 1; transform: translateY(0); }
}
/* Для tool-сообщений отдельный fade без translateY — они приходят серийно
   (Read → Grep → Bash), и каскад прыжков translateY выглядит дёргано. */
@keyframes msgInFade {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes modalIn {
  0%   { transform: scale(0.88) translateY(8px); }
  60%  { transform: scale(1.02) translateY(0); }
  100% { transform: scale(1)    translateY(0); }
}
@keyframes pixelFlash {
  0%, 100% { background: transparent; }
  50%      { background: var(--accent-dim); }
}
@keyframes threadTap {
  0%, 100% { border-color: transparent; }
  50%      { border-color: var(--accent); }
}
/* «Приехали»-эффект для стрелочек ↑/↓ и тапа по закрепу.
   Полностью имитирует long-press (.msg.is-pressed → scale 0.965, который
   запускается перед попап-меню). Никакой подсветки — только scale. */
@keyframes bubbleTapScale {
  0%   { transform: scale(1); }
  30%  { transform: scale(0.965); }
  65%  { transform: scale(0.965); }
  100% { transform: scale(1); }
}
.msg.bubble-tap {
  animation: bubbleTapScale 0.62s cubic-bezier(0.2, 0, 0, 1) both;
  transform-origin: center center;
  will-change: transform;
}
/* В glass-теме .msg.bot имеет animation: glassBotFadeIn !important —
   перебиваем, чтобы bubble-tap отыграл и на ботовых баблах. */
:root.theme-glass .msg.bot.bubble-tap,
:root.theme-glass .msg.user.bubble-tap {
  animation: bubbleTapScale 0.62s cubic-bezier(0.2, 0, 0, 1) both !important;
}
.msg-pin-mark {
  display: none;
  margin-left: 4px;
  color: var(--accent);
  opacity: 0.85;
  vertical-align: middle;
  line-height: 0;
}
.msg.pinned-msg .msg-pin-mark {
  display: inline-flex;
  align-items: center;
}
/* Ретро/классика (не glass): унифицируем булавку — тот же пиксельный пин, что
   и в баннере #pin-bar. Прячем диагональный bootstrap-SVG из app.js и
   рисуем тот же вертикальный пиксельный пин через mask. */
:root:not(.theme-glass) .msg-pin-mark svg { display: none; }
:root:not(.theme-glass) #pin-bar .pin-bar__icon svg { display: none; }
:root:not(.theme-glass) .msg-pin-mark::before,
:root:not(.theme-glass) #pin-bar .pin-bar__icon::before {
  content: "";
  display: block;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' shape-rendering='crispEdges'><path d='M6 2h4v1h-4zM5 3h1v3h-1zM10 3h1v3h-1zM4 6h8v1h-8zM7 7h2v6h-2z'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' shape-rendering='crispEdges'><path d='M6 2h4v1h-4zM5 3h1v3h-1zM10 3h1v3h-1zM4 6h8v1h-8zM7 7h2v6h-2z'/></svg>");
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain; mask-size: contain;
}
:root:not(.theme-glass) .msg-pin-mark::before { width: 11px; height: 11px; }
:root:not(.theme-glass) #pin-bar .pin-bar__icon::before { width: 14px; height: 14px; }

.msg-new {
  animation: msgIn 0.62s cubic-bezier(0.22, 0.61, 0.36, 1) 0.06s both;
}
/* Tools идут серийно (Read → Grep → Bash) — каскад translateY-прыжков
   выглядит дёргано. Для них чистый fade без сдвига, чуть быстрее, без
   delay (серия должна идти компактно). */
.msg.tool.msg-new {
  animation: msgInFade 0.36s cubic-bezier(0.4, 0, 0.2, 1) both;
}
/* Thinking-баббл в каскаде «после истории»: появляется ПОСЛЕДНИМ с той же
   fade+slide-анимацией, что и сообщения. Override shell-in (220ms scale)
   на msgIn (320ms translateY). Внутренние элементы (.role-label,
   .chat-thinking-mac, .chat-thinking-dot) сохраняют свои keyframes из
   `#chat-thinking.visible` — продолжат выводиться/пульсировать. */
#chat-thinking.visible.msg-new {
  animation: msgIn 0.62s cubic-bezier(0.22, 0.61, 0.36, 1) 0.06s both;
}
/* Task C: каскадное проявление непрочитанных при тапе по стрелке вниз. Тот же
   msgIn (fade + лёгкий подъезд), но animation-delay ставится inline на каждый
   баббл (revealUnreadPending). fill:both → пока идёт delay (камера едет к низу),
   держится 0%-кадр (opacity:0, translateY 6px), потом бабблы проявляются по
   очереди. delay снимается на animationend. */
.msg.reveal-stagger {
  animation: msgIn 0.62s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}
/* В glass у .msg.bot.msg-new своя glassBotFadeIn !important — на случай быстрого
   тапа в окне 600мс перебиваем, чтобы каскад отыграл и в стекле. */
:root.theme-glass .msg.bot.reveal-stagger {
  animation: msgIn 0.62s cubic-bezier(0.22, 0.61, 0.36, 1) both !important;
}
.modal-card {
  animation: modalIn 0.14s steps(3, end) both;
}
.icon-btn:active,
.chip:active,
.settings-row:active {
  animation: pixelFlash 0.08s steps(2, end) both;
}

/* ===== typewriter cursor (мигающий блок в конце сообщения, пока стримится) =====
   tool-сообщения — без innerHTML rebuild, для них хватает ::after.
   Для bot-сообщений курсор кладёт JS реальным span'ом (.tw-cursor), чтобы
   мигалка стояла внутри последнего параграфа, а не падала на новую строку
   ниже блочного <pre>/<table>. */
.msg.tool.is-streaming .body::after {
  content: "▍";
  display: inline-block;
  margin-left: 1px;
  color: var(--accent);
  font-weight: bold;
  vertical-align: baseline;
  animation: blink 0.9s steps(2, start) infinite;
}
.tw-cursor {
  display: inline-block;
  margin-left: 1px;
  color: var(--accent);
  font-weight: bold;
  vertical-align: baseline;
  animation: blink 0.9s steps(2, start) infinite;
}
/* Появление текста — поблочно: новый абзац / список / pre / table получает
   класс .block-in (ставится в setBodyRich по diff'у DOM при is-streaming),
   проигрывает один раз и сидит на финальном состоянии (animation-fill: both).
   setBodyRich теперь инкрементальный — старые блоки реюзаются, поэтому fade
   НЕ ре-стартует на каждом тике стрима. .rev-word — legacy, на новых стримах
   не появится, оставлен на случай DOM из старого истории. */
.msg .body .block-in {
  animation: blockIn 0.42s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes blockIn {
  0%   { opacity: 0; transform: translateY(3px); }
  100% { opacity: 1; transform: translateY(0); }
}
.rev-word {
  display: inline-block;
  opacity: 1;
  animation: none;
}
:root.theme-glass .msg.tool.is-streaming .body::after,
:root.theme-glass .tw-cursor {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .rev-word { animation: none; opacity: 1; }
  .msg .body .block-in { animation: none; }
}

/* ===== quick-replies pop-in (отдельная анимация для кнопок в конце) =====
   Плавный fade-slide без bounce и без steps() — на glass-теме ступенчатая
   пиксельная анимация смотрится грубо. Каждая следующая кнопка стартует
   с задержкой 100ms — явный каскад «по очереди». */
@keyframes qrPopIn {
  0%   { opacity: 0; transform: translateY(6px); }
  100% { opacity: 1; transform: translateY(0); }
}
.msg .body .quick-replies .qr-btn.qr-pop-in {
  animation: qrPopIn 0.38s cubic-bezier(0.16, 1, 0.3, 1) both;
  animation-delay: calc(var(--qr-i, 0) * 100ms);
}

/* ===== routines pop-in (строго по одной, быстро) ===== */
@keyframes routineSlashOpen {
  0%   { clip-path: inset(0 0 100% 0); opacity: 0; }
  99%  { clip-path: inset(0 0 0 0);   opacity: 1; }
  100% { clip-path: inset(0 0 0 0);   opacity: 1; }
}
.routine-card.routine-pop-in {
  transform-origin: top center;
  opacity: 0;
  clip-path: inset(0 0 100% 0);
  animation: routineSlashOpen 90ms steps(4, end) both;
  animation-delay: calc(var(--r-i, 0) * 90ms);
  will-change: clip-path, opacity;
}

/* Drawer на очень узком экране — 92% а не 86%. */
@media (max-width: 420px) {
  #drawer { max-width: 92vw; }
}

/* ===== debug overlay (тройной тап по топбару) ===== */
.debug-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: rgba(0, 0, 0, 0.92);
  color: #d4ffd4;
  display: flex;
  flex-direction: column;
  font-family: var(--font-ui);
  font-size: 11px;
  line-height: 1.4;
  padding: env(safe-area-inset-top, 0px) 8px env(safe-area-inset-bottom, 0px) 8px;
}
.debug-overlay-bar {
  display: flex;
  gap: 8px;
  padding: 8px 0;
  border-bottom: 1px solid #444;
  flex-shrink: 0;
}
.debug-overlay-btn {
  background: #2a2a2a;
  color: #f0efe9;
  border: 1px solid #555;
  border-radius: 4px;
  padding: 8px 14px;
  font-family: inherit;
  font-size: 12px;
  cursor: pointer;
}
.debug-overlay-btn:active { background: #3a3a3a; }
.debug-overlay-pre {
  flex: 1 1 auto;
  overflow-y: auto;
  margin: 0;
  padding: 8px 0;
  white-space: pre-wrap;
  word-break: break-word;
  -webkit-user-select: text;
  user-select: text;
}

/* ===== voice-mode (8-bit) ===== */
#voice-mode-btn:hover { color: var(--accent); }
#voice-mode-btn.active {
  color: var(--green);
  border-color: var(--green);
  animation: vmo-btn-pulse 2.4s ease-in-out infinite;
}
@keyframes vmo-btn-pulse {
  0%, 100% { box-shadow: 2px 2px 0 0 rgba(0,0,0,0.55), 0 0 0 0 rgba(127, 184, 127, 0); }
  50%      { box-shadow: 2px 2px 0 0 rgba(0,0,0,0.55), 0 0 0 4px rgba(127, 184, 127, 0); }
}

#voice-mode-overlay {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding:
    calc(env(safe-area-inset-top, 0px) + 16px)
    calc(env(safe-area-inset-right, 0px) + 16px)
    calc(env(safe-area-inset-bottom, 0px) + 16px)
    calc(env(safe-area-inset-left, 0px) + 16px);
  font-family: var(--font-ui);
  color: var(--fg);
}
#voice-mode-overlay.hidden { display: none; }
#voice-mode-overlay .vmo-bg {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 50% 40%, rgba(217, 119, 87, 0.12), transparent 60%),
    var(--bg);
  z-index: 0;
}
#voice-mode-overlay.vmo-state-listen .vmo-bg {
  background:
    radial-gradient(circle at 50% 40%, rgba(127, 184, 127, 0.10), transparent 60%),
    var(--bg);
  animation: vmo-bg-listen 4s ease-in-out infinite;
}
#voice-mode-overlay.vmo-state-think .vmo-bg {
  background:
    radial-gradient(circle at 50% 40%, rgba(217, 119, 87, 0.10), transparent 60%),
    var(--bg);
  animation: vmo-bg-think 3.2s ease-in-out infinite;
}
#voice-mode-overlay.vmo-state-speak .vmo-bg {
  background:
    radial-gradient(circle at 50% 40%, rgba(232, 154, 124, 0.12), transparent 60%),
    var(--bg);
  animation: vmo-bg-speak 3s ease-in-out infinite;
}
@keyframes vmo-bg-listen {
  0%, 100% { filter: brightness(0.98); }
  50%      { filter: brightness(1.06); }
}
@keyframes vmo-bg-think {
  0%, 100% { filter: brightness(0.96); }
  50%      { filter: brightness(1.06); }
}
@keyframes vmo-bg-speak {
  0%, 100% { filter: brightness(0.98); }
  50%      { filter: brightness(1.06); }
}

#voice-mode-overlay.vmo-state-listen { box-shadow: inset 0 0 80px 0 rgba(127, 184, 127, 0.10); }
#voice-mode-overlay.vmo-state-think  { box-shadow: inset 0 0 90px 0 rgba(217, 119, 87, 0.12); }
#voice-mode-overlay.vmo-state-speak  { box-shadow: inset 0 0 90px 0 rgba(232, 154, 124, 0.14); }

#vmo-close {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + 12px);
  right: calc(env(safe-area-inset-right, 0px) + 12px);
  z-index: 2;
  min-width: 56px;
  height: 32px;
  padding: 0 10px;
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--fg-dim);
  font-family: inherit;
  font-size: 12px;
  letter-spacing: 0.5px;
  cursor: pointer;
}
#vmo-close:hover { color: var(--accent); border-color: var(--accent); }
#vmo-close:active { transform: translateY(1px); }

.vmo-stage {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  max-width: 560px;
  width: 100%;
  text-align: center;
}

.vmo-mic {
  margin: 0;
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.05;
  letter-spacing: 1px;
  color: var(--accent);
  text-shadow: 0 0 12px rgba(217, 119, 87, 0.55);
  white-space: pre;
  user-select: none;
  -webkit-user-select: none;
  transition: color 120ms steps(2, end), text-shadow 120ms steps(2, end);
}
#voice-mode-overlay.vmo-state-listen .vmo-mic {
  color: var(--green);
  text-shadow: 0 0 10px rgba(127, 184, 127, 0.55);
  animation: vmo-mic-listen 3s ease-in-out infinite;
}
#voice-mode-overlay.vmo-state-think .vmo-mic {
  color: var(--accent);
  text-shadow: 0 0 12px rgba(217, 119, 87, 0.55);
  animation: vmo-mic-think 1.6s ease-in-out infinite;
}
#voice-mode-overlay.vmo-state-speak .vmo-mic {
  color: var(--accent-2);
  text-shadow: 0 0 14px rgba(232, 154, 124, 0.60);
  animation: vmo-mic-speak 2.4s ease-in-out infinite;
}
@keyframes vmo-mic-think {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.75; }
}
@keyframes vmo-mic-listen {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.08); }
}
@keyframes vmo-mic-speak {
  0%, 100% { filter: brightness(1.0); }
  50%      { filter: brightness(1.10); }
}

#voice-mode-overlay.vmo-state-listen .vmo-status {
  color: var(--green);
  text-shadow: 0 0 6px rgba(127, 184, 127, 0.40);
}
#voice-mode-overlay.vmo-state-think .vmo-status {
  color: var(--accent);
  text-shadow: 0 0 6px rgba(217, 119, 87, 0.40);
  animation: vmo-status-think 2.4s ease-in-out infinite;
}
#voice-mode-overlay.vmo-state-speak .vmo-status {
  color: var(--accent-2);
  text-shadow: 0 0 8px rgba(232, 154, 124, 0.50);
}
@keyframes vmo-status-think {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.7; }
}

.vmo-status {
  font-size: 14px;
  letter-spacing: 2px;
  color: var(--fg-dim);
  font-weight: 700;
  text-transform: uppercase;
}
#voice-mode-overlay.vmo-state-listen .vmo-status { color: var(--green); }
#voice-mode-overlay.vmo-state-think  .vmo-status { color: var(--accent); }
#voice-mode-overlay.vmo-state-speak  .vmo-status { color: var(--accent-2); }

.vmo-hint {
  font-size: 12px;
  color: var(--fg-dim);
  opacity: 0.75;
  letter-spacing: 0.5px;
}

.vmo-transcript {
  min-height: 1.4em;
  max-height: 30vh;
  overflow-y: auto;
  font-size: 14px;
  line-height: 1.5;
  color: var(--fg);
  padding: 10px 14px;
  border: 1px solid var(--border);
  background: var(--bg-soft);
  width: 100%;
  word-break: break-word;
  white-space: pre-wrap;
  animation: none !important;
  transition: none !important;
  text-shadow: none !important;
  filter: none !important;
}
.vmo-transcript:empty { display: none; }

@media (max-width: 480px) {
  .vmo-mic { font-size: 12px; }
  .vmo-status { font-size: 12px; letter-spacing: 1.5px; }
  #vmo-close { font-size: 11px; min-width: 48px; }
}

/* ===== voice-mode inline indicators ===== */
body.voice-mode-on #chat {
  box-shadow: inset 0 0 0 1px rgba(127, 184, 127, 0.15);
  transition: box-shadow 0.35s ease;
}
body.voice-mode-on #composer {
  border-top-color: rgba(127, 184, 127, 0.32);
  transition: border-color 0.35s ease;
}

body.voice-state-listen #voice-mode-btn.active {
  color: var(--green);
  border-color: var(--green);
  animation: vmo-btn-pulse 1.6s ease-in-out infinite;
}
body.voice-state-think #voice-mode-btn.active {
  color: var(--accent);
  border-color: var(--accent);
  animation: vmo-btn-think 1.2s ease-in-out infinite;
}
body.voice-state-speak #voice-mode-btn.active {
  color: var(--accent-2);
  border-color: var(--accent-2);
  animation: vmo-btn-speak 0.9s ease-in-out infinite;
}
@keyframes vmo-btn-think {
  0%, 100% { box-shadow: 2px 2px 0 0 rgba(0,0,0,0.55), 0 0 0 0 rgba(217, 119, 87, 0); }
  50%      { box-shadow: 2px 2px 0 0 rgba(0,0,0,0.55), 0 0 0 4px rgba(217, 119, 87, 0); }
}
@keyframes vmo-btn-speak {
  0%, 100% { box-shadow: 2px 2px 0 0 rgba(0,0,0,0.55), 0 0 0 0 rgba(232, 154, 124, 0); }
  50%      { box-shadow: 2px 2px 0 0 rgba(0,0,0,0.55), 0 0 0 5px rgba(232, 154, 124, 0); }
}

body.voice-state-think #composer { border-top-color: rgba(217, 119, 87, 0.45); }
body.voice-state-speak #composer { border-top-color: rgba(232, 154, 124, 0.55); }
body.voice-state-think #chat { box-shadow: inset 0 0 0 1px rgba(217, 119, 87, 0.20); }
body.voice-state-speak #chat { box-shadow: inset 0 0 0 1px rgba(232, 154, 124, 0.24); }

@media (prefers-reduced-motion: reduce) {
  body.voice-state-listen #voice-mode-btn.active,
  body.voice-state-think #voice-mode-btn.active,
  body.voice-state-speak #voice-mode-btn.active { animation: none; }
}

/* ===== voice-mode: волна живёт в облачке-«призраке» #chat (как user-сообщение) ===== */

/* Облачко-«призрак» — placeholder сообщения пользователя пока он диктует.
   Ширина средняя (~60%), правое выравнивание, внутри волна. После
   submitVoiceFinal оно удаляется и заменяется обычным user-пузырём с текстом. */
.msg.user.voice-listening {
  align-self: flex-end;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: auto;
  max-width: none;
  padding: 2px 4px;
  background: transparent;
  border: none;
  box-shadow: none;
  color: var(--accent);
}
.msg.user.voice-listening .voice-listen-wave {
  display: none;
}
.msg.user.voice-listening .voice-listen-timer {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  min-width: 32px;
  text-align: right;
}

/* ===== Liquid Glass theme (ChatGPT-стиль: минимализм + скругления) ===== */
/* Чистый чёрный фон без градиентов и блобов. Системный SF-шрифт вместо
   моноширинного. Все поверхности — pill'ы или сильно скруглённые карточки.
   Юзер-баббл — solid тёмно-синий, ассистент — голый текст без коробки. */
:root.theme-glass {
  --font-ui: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Helvetica Neue", "Segoe UI", system-ui, sans-serif;
  --bg: #000000;
  --bg-soft: rgba(255, 255, 255, 0.045);
  --bg-card: rgba(255, 255, 255, 0.06);
  --fg: #ffffff;
  --fg-dim: #c8c8c8;
  --accent: #2a73e3;
  --accent-2: #4a8df0;
  --accent-dim: rgba(255, 255, 255, 0.40);
  --accent-dim-2: rgba(255, 255, 255, 0.25);
  --green: #30d158;
  --red: #ff453a;
  --border: rgba(255, 255, 255, 0.10);
  --scroll-thumb: rgba(255, 255, 255, 0.20);
  --user-bg: #1f4d8a;
  --user-fg: #ffffff;
  --code-bg: rgba(255, 255, 255, 0.06);
}

/* Чистый чёрный фон, без градиентов. */
:root.theme-glass body {
  background: #000000;
  background-attachment: fixed;
}
/* В glass с включённой aura body становится прозрачным, чтобы был виден
   фиксированный #aura-bg слой за ним (он сам несёт чёрный + цветные пятна). */
:root.theme-glass body.with-aura {
  background: transparent;
}

/* Glass-тема обнуляет letter-spacing JetBrains'а. Сам шрифт — через
   переопределение --font-ui в :root.theme-glass, чтобы выбор пользователя
   в настройках (inline style на documentElement) мог перебить дефолт темы. */
:root.theme-glass,
:root.theme-glass body,
:root.theme-glass #topbar,
:root.theme-glass #composer,
:root.theme-glass #drawer,
:root.theme-glass .modal-card,
:root.theme-glass .msg,
:root.theme-glass .chip,
:root.theme-glass .tech-card,
:root.theme-glass button,
:root.theme-glass input,
:root.theme-glass textarea {
  letter-spacing: normal;
}

/* Топбар — полупрозрачный с iOS-blur, как в ChatGPT/Gemini.
   Делаем его position:absolute поверх #app, а #chat получает padding-top
   равный высоте топбара (через --topbar-h из ResizeObserver). Так контент
   при скролле физически проходит ПОД топбаром и красиво размывается. */
:root.theme-glass #app {
  position: relative;
}
/* Blur на самом #topbar (не на ::before). Раньше его выносили на ::before
   ради #topbar-menu blur'а, но это ломало backdrop-snapshot WK на iOS:
   при скролле #chat остаются призраки прокрученных сообщений вместо
   живого размытого отражения. #topbar-menu сидит вне bbox топбара
   (top: 100%) и имеет свой backdrop-filter — blur меню работает сам по
   себе, isolation: isolate здесь не нужен. */
:root.theme-glass #topbar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 10;
  background: rgba(0, 0, 0, 0.32);
  backdrop-filter: saturate(180%) blur(24px);
  -webkit-backdrop-filter: saturate(180%) blur(24px);
  border-bottom: none;
  box-shadow: none;
}
:root.theme-glass #chat {
  /* Пол под топбар. --topbar-h иногда занижен: ResizeObserver успевает померить
     топбар до того как доедет safe-area-инсет чёлки, и в тредах с однострочным
     заголовком (бар больше не ресайзится) заниженное значение залипает → первое
     сообщение при scrollTop=0 въезжает под полупрозрачную полосу. max() держит
     отступ не меньше реальной высоты бара (чёлка + 44px тело: 2 паддинг-сверху +
     32 ряд кнопок + 10 снизу), считая чёлку из env(safe-area-inset-top) — он в
     CSS доступен сразу, без JS-гонки. Когда --topbar-h корректный, он и берётся
     (он равен или больше пола), лишнего зазора нет. */
  padding-top: calc(max(var(--topbar-h, 56px), var(--safe-top, env(safe-area-inset-top, 0px)) + 44px) + 12px);
}
/* v1156: ОТМЕНА v1055. Кирилл: в новом чате сообщение «улетает наверх» — нужно
   как в мессенджере, короткий тред прижат к низу у композёра. Возвращаем glass
   к базовому поведению: #chat::before (flex:1) прижимает к низу в покое, а
   turn-active гасит прижим для GPT-стиля (как во всех темах). Цена решения —
   под плавающим топбаром в коротком треде снова пустая полоса; Кирилл выбрал низ.
   (Раньше тут был glass-override flex:0, который держал контент топ-алайном.) */

/* Pin bar в glass: топбар position:absolute плавает поверх чата, обычная плашка
   pin-bar в normal flow терялась под ним. Поднимаем pin-bar absolute'ом ровно под
   топбар, со своим blur-слоем ::before (как у топбара). Когда закреп видим —
   chat сдвигается вниз на высоту бара через :has(). */
:root.theme-glass #pin-bar {
  position: absolute;
  top: var(--topbar-h, 56px);
  left: 0;
  right: 0;
  z-index: 9;
  background: transparent;
  border-bottom: none;
  box-shadow: none;
  isolation: isolate;
  min-height: 44px;
}
:root.theme-glass #pin-bar.hidden { display: none; }
:root.theme-glass #pin-bar::before {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  z-index: 0;
  pointer-events: none;
}
/* Светлый glass: тёмный текст должен лежать на светлой подложке, а не на rgba(0,0,0,0.45). */
:root.theme-glass.theme-minimal #pin-bar::before {
  background: rgba(255, 255, 255, 0.55);
}
:root.theme-glass #pin-bar > * {
  position: relative;
  z-index: 1;
}
/* padding-top для glass-темы тоже инлайном из applyChatPinPadding(). */
/* Иконка-пин: пиксельный SVG прячем, рисуем линейную mask на ::before-обёртке span'а.
   Тот же mask-image используется и на маркере сообщения .msg-pin-mark — один значок везде в glass. */
:root.theme-glass #pin-bar .pin-bar__icon svg,
:root.theme-glass .msg-pin-mark svg { display: none; }
:root.theme-glass #pin-bar .pin-bar__icon::before,
:root.theme-glass .msg-pin-mark::before {
  content: "";
  display: block;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M12 17v5'/><path d='M9 10.76a2 2 0 0 1-1.11 1.79L6 13.43V15h12v-1.57l-1.89-.88A2 2 0 0 1 15 10.76V5H9Z'/><path d='M7 5h10'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M12 17v5'/><path d='M9 10.76a2 2 0 0 1-1.11 1.79L6 13.43V15h12v-1.57l-1.89-.88A2 2 0 0 1 15 10.76V5H9Z'/><path d='M7 5h10'/></svg>");
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain; mask-size: contain;
}
:root.theme-glass #pin-bar .pin-bar__icon::before { width: 16px; height: 16px; }
:root.theme-glass .msg-pin-mark::before { width: 11px; height: 11px; }
/* Боковые иконки списка/закрытия — тоже на mask. */
:root.theme-glass #pin-bar .pin-bar__stack svg { display: none; }
:root.theme-glass #pin-bar .pin-bar__stack::before {
  content: "";
  display: block;
  width: 16px; height: 16px;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M4 6h16'/><path d='M4 12h16'/><path d='M4 18h16'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M4 6h16'/><path d='M4 12h16'/><path d='M4 18h16'/></svg>");
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain; mask-size: contain;
}

/* v1006: press-фидбек на pin-bar в glass — как у стрелок ↑/↓
   (scale(0.92) + cubic-bezier bouncy + подсветка ::before-слоя).
   Базовый .pin-bar:active меняет transform на 0.965 и background, но
   в glass background:transparent (рисует ::before-blur), так что
   визуально нажатие почти не видно. */
:root.theme-glass #pin-bar {
  transition: transform 0.22s cubic-bezier(0.3, 1.4, 0.5, 1), background 120ms ease;
}
:root.theme-glass #pin-bar:active { transform: scale(0.92); }
:root.theme-glass #pin-bar::before { transition: background 0.12s linear; }
:root.theme-glass #pin-bar:active::before { background: rgba(255, 255, 255, 0.18); }
:root.theme-glass.theme-minimal #pin-bar:active::before { background: rgba(0, 0, 0, 0.12); }

:root.theme-glass #topbar > #threads-btn,
:root.theme-glass #topbar > #routines-btn {
  background: rgba(255, 255, 255, 0.08);
  border: none;
  border-radius: 999px;
  box-shadow: none;
}
:root.theme-glass #topbar .logo .topbar-title-btn {
  background: rgba(255, 255, 255, 0.08);
  border: none;
  border-radius: 999px;
  padding: 6px 14px;
  height: 32px;
}

/* Композер — полупрозрачный с blur'ом, как топбар. position:absolute
   поверх #app, чат имеет padding-bottom равный --composer-h (из RO),
   так что сообщения физически уходят под композер и размываются.
   backdrop-filter живёт на ::before — даёт blur-слой, который продлевается
   на 90px выше композера до уровня боковых стрелок ↑/↓ для визуальной
   непрерывности стекла. */
:root.theme-glass #composer {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 10;
  background: transparent;
  border-top: none;
  box-shadow: none;
  isolation: isolate;
}
:root.theme-glass body.is-desktop #topbar,
:root.theme-glass body.is-desktop #composer {
  left: var(--drawer-width);
}
:root.theme-glass #composer::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  /* Тянемся выше композера на 90px — ровно до верха #scroll-up-btn
     (top: -90px относительно композера). Лёгкая дымка-непрерывность
     от самого низа экрана до уровня боковых стрелок ↑/↓. */
  top: -90px;
  /* v1175: blur + мягкое затемнение снизу. Раньше было чисто blur (затемнение
     Кирилл просил убрать). Теперь по просьбе — лёгкая тёмная вуаль от самого
     низа, которая ПОСТЕПЕННО уменьшается и светлеет кверху (градиент 0.36→0 к
     ~82% высоты слоя). Слой под чипами/полем (z-index:0), их собственный glass-
     blur не трогает. Маска сверху доводит и blur, и затемнение до нуля у чипов. */
  background: linear-gradient(to top,
    rgba(0, 0, 0, 0.36) 0%,
    rgba(0, 0, 0, 0.12) 50%,
    rgba(0, 0, 0, 0) 82%);
  backdrop-filter: saturate(160%) blur(18px);
  -webkit-backdrop-filter: saturate(160%) blur(18px);
  /* Геометрия маски в layer-coords (y=0 — низ композёра, у layer'а top:-90px):
     - 0..var(--meta-row-bottom) — плотный blur: вся зона ПОД низом чип-ряда
       (включая input-row, attach-row, fav-react-row).
     - var(--meta-row-bottom)..var(--meta-row-top) — плавный фейд через ряд чипов:
       внизу чипов blur полный, к верху чипов — 0.
     - выше var(--meta-row-top) — прозрачно: чат над чипами не размывается.
     Фолбэки на calc(composer_h - ...) — на случай если JS ещё не успел проставить
     переменные на первом тике (примерно соответствуют типичной геометрии
     композера в коллапсе). */
  -webkit-mask-image: linear-gradient(to top,
    rgba(0, 0, 0, 1) 0,
    rgba(0, 0, 0, 1) var(--meta-row-bottom, calc(var(--composer-h, 140px) - 58px)),
    rgba(0, 0, 0, 0) var(--meta-row-top, calc(var(--composer-h, 140px) - 8px)));
          mask-image: linear-gradient(to top,
    rgba(0, 0, 0, 1) 0,
    rgba(0, 0, 0, 1) var(--meta-row-bottom, calc(var(--composer-h, 140px) - 58px)),
    rgba(0, 0, 0, 0) var(--meta-row-top, calc(var(--composer-h, 140px) - 8px)));
  z-index: 0;
  pointer-events: none;
}
/* Светлый glass-minimal — тоже без фоновой дымки, только blur. */
:root.theme-glass.theme-minimal #composer::before {
  background: transparent;
  backdrop-filter: saturate(160%) blur(18px);
  -webkit-backdrop-filter: saturate(160%) blur(18px);
}
/* attach-row open: специальный override НЕ нужен — основная маска уже
   динамическая (opaque до --input-row-top, который JS пересчитывает по
   реальному offset верха #input-row). При открытой шторке input-row уезжает
   ниже chip-ряда на высоту attach-row, --input-row-top уменьшается,
   фейд растягивается ровно через всю attach-row и нижнюю половину chip'ов. */
/* Гласс минимал — основные плашки (поле ввода, чипы, стрелочки) с почти
   непрозрачным белым фоном (~95%). Текст в light-режиме остаётся тёмным
   по умолчанию (var(--fg)), читается чётко. */
:root.theme-glass.theme-minimal .input-wrap,
:root.theme-glass.theme-minimal .chip,
:root.theme-glass.theme-minimal #scroll-up-btn,
:root.theme-glass.theme-minimal #scroll-down-btn {
  background: rgba(255, 255, 255, 0.95) !important;
}
/* Раньше тут было общее затемнение по плашке #composer[data-bg="dark"]::before.
   Теперь инверсия индивидуальная: каждый чип/textarea/иконка ставит свой
   data-bg сам — см. селекторы ниже. */
/* Поднимаем статических детей композера над ::before-blur.
   v1104: исключаем #chip-popover/#slash-suggest — это absolute-оверлеи (см.
   правило ~2118 «position:absolute; bottom:100%»). Без исключения это glass-
   правило (специфичность с 3 id из-за :not(#id)) перебивало им position обратно
   на relative → bottom:100% уезжал вверх на высоту композера, плашка улетала к
   топбару. Теперь они остаются absolute и ложатся ровно над чипами. */
:root.theme-glass #composer > *:not(#scroll-up-btn):not(#scroll-down-btn):not(#chip-popover):not(#slash-suggest) {
  position: relative;
  z-index: 1;
}
:root.theme-glass #chat {
  /* Текст останавливается с зазором ~32px НАД верхним краем композёра —
     не касается ни chips, ни input. 32px = чистый воздух + запас на
     scroll-overshot/недоскролл (~16-20px), иначе маскот зависает над
     самыми чипами когда autoscroll не доехал до самого низа. */
  padding-bottom: calc(var(--composer-h, 200px) + 32px);
}

/* Пилюля поля ввода: стеклянная подложка с blur — как у боковых стрелок
   #scroll-up-btn/#scroll-down-btn. Параметры подобраны 1-в-1 (rgba бэк,
   border-color, box-shadow, backdrop-filter). */
:root.theme-glass .input-wrap {
  --bg-mix: 0;
  position: relative;
  overflow: hidden;
  background: color-mix(in srgb, rgba(255, 255, 255, 0.03) calc((1 - var(--bg-mix)) * 100%), rgba(0, 0, 0, 0.45)) !important;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 28px;
  box-shadow:
    0 8px 14px -6px rgba(0, 0, 0, 0.30),
    inset 0 1px 0 rgba(255, 255, 255, calc(0.10 + var(--bg-mix) * 0.12)),
    inset 0 -2px 6px rgba(0, 0, 0, calc(var(--bg-mix) * 0.18)) !important;
  padding-left: 3vw;
  padding-right: 3vw;
  -webkit-backdrop-filter: blur(6px) saturate(160%);
  backdrop-filter: blur(6px) saturate(160%);
  transition: transform 0.22s cubic-bezier(0.3, 1.4, 0.5, 1),
              background 0.08s linear,
              box-shadow 0.08s linear;
  transform-origin: center;
  will-change: transform;
}
:root.theme-glass .input-wrap.is-pressed {
  transform: scale(0.985);
  background: rgba(255, 255, 255, 0.18) !important;
}
:root.theme-glass .input-wrap:focus-within {
  box-shadow:
    0 8px 14px -6px rgba(0, 0, 0, 0.30),
    inset 0 1px 0 rgba(255, 255, 255, 0.10) !important;
}
/* Базовый ::after из классики рисует прямоугольный фокус-ring без скруглений —
   гасим его в glass, форма пилюли уже задана border самого .input-wrap. */
:root.theme-glass .input-wrap::after,
:root.theme-glass .input-wrap:focus-within::after {
  display: none !important;
}
:root.theme-glass #input {
  --bg-mix: 0;
  background: transparent !important;
  border: none !important;
  border-radius: 28px;
  box-shadow: none !important;
  color: color-mix(in srgb, var(--fg) calc((1 - var(--bg-mix)) * 100%), #ffffff) !important;
  caret-color: color-mix(in srgb, var(--fg) calc((1 - var(--bg-mix)) * 100%), #ffffff);
  transition: color 0.08s linear, caret-color 0.08s linear;
}
:root.theme-glass #input::placeholder {
  color: color-mix(in srgb, var(--fg-dim) calc((1 - var(--bg-mix)) * 100%), rgba(255, 255, 255, 0.72));
  transition: color 0.08s linear;
}
:root.theme-glass #input:focus {
  border: none !important;
  border-color: transparent !important;
  outline: none !important;
  box-shadow: none !important;
}
/* Правый отступ внутри пилюли: на iOS WebKit textarea периодически «съедает»
   padding-right (caret-bias / scrollbar-gutter на overflow:auto), и последнее
   слово строки прилипает к правому краю. Левый padding визуально остаётся, а
   правый пропадает. Поднимаем запас до 24px на input, mirror и ghost
   синхронно — чтобы рендер mirror'а и фактический textarea не разъезжались. */
:root.theme-glass #input,
:root.theme-glass #input-mirror,
:root.theme-glass #input-ghost {
  padding-right: 24px;
}
:root.theme-glass #input { scrollbar-width: none; }
:root.theme-glass #input::-webkit-scrollbar { display: none; width: 0; height: 0; }
/* Скроллбар #chat в glass — как в дефолте (4px thumb на var(--scroll-thumb)).
   Нижняя часть thumb может уходить под blur-слой композёра, но без скроллбара
   совсем неочевидно где ты в чате (особенно при долгом скролле истории). */
/* Нижняя плашка пилюли (иконки attach/voice/mic/send) — в дефолте имеет
   собственный var(--bg-card) и border, из-за чего верх пилюли (textarea
   transparent) и низ (input-bottom-bar) разъезжаются по цвету: верх
   тёмный (виден композер сквозь transparent), низ светлее. Делаем низ
   тоже прозрачным — пилюля становится единого тона. */
:root.theme-glass .input-bottom-bar {
  background: transparent !important;
  border: none !important;
  /* Дефолтный padding-left = 4px сдвигал скрепку на 16px от левого края
     пилюли, а текст в textarea — на 12px (padding-left у #input). В glass
     это даёт ступеньку. Сдвигаем барик левее так, чтобы центр иконки
     скрепки совпал с левым краем текста. */
  padding-left: 0 !important;
}

/* Чипы под композером: стеклянная подложка с blur и направленной вниз
   drop-shadow. Формула с negative spread (-8px при blur 10px) даёт тень
   ТОЛЬКО снизу — по бокам blur почти не вытекает, поэтому в gap между
   соседними чипами не образуется серая полоска. */
/* Чипы: пропорциональная инверсия от user-баббла. JS ставит --bg-mix (0..1) =
   доля площади чипа, перекрытая user-бабблом. calc() интерполирует все три
   слоя (background / color / box-shadow) — инверсия идёт за пальцем по скроллу,
   без 0.9s transition-таймера. Короткий 80ms linear сглаживает дискретные
   rAF-апдейты scheduleChipBgUpdate'а. */
:root.theme-glass .chip {
  --bg-mix: 0;
  background: color-mix(in srgb, rgba(255, 255, 255, 0.03) calc((1 - var(--bg-mix)) * 100%), rgba(0, 0, 0, 0.40)) !important;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  text-transform: none;
  letter-spacing: 0;
  color: color-mix(in srgb, var(--fg) calc((1 - var(--bg-mix)) * 100%), #ffffff) !important;
  padding: 6px 12px;
  font-size: 12px;
  box-shadow:
    0 8px 14px -6px rgba(0, 0, 0, 0.30),
    inset 0 1px 0 rgba(255, 255, 255, calc(0.10 + var(--bg-mix) * 0.12)),
    inset 0 -2px 6px rgba(0, 0, 0, calc(var(--bg-mix) * 0.18)) !important;
  -webkit-backdrop-filter: blur(6px) saturate(160%);
  backdrop-filter: blur(6px) saturate(160%);
  transition: background 0.08s linear, color 0.08s linear, box-shadow 0.08s linear;
}
:root.theme-glass .chip:active {
  background: rgba(255, 255, 255, 0.18) !important;
}
/* Danger-чип сохраняет красный текст вне зависимости от mix'а. */
:root.theme-glass .chip.danger { color: var(--red) !important; }
/* input-wrap / #input / .input-bar-btn инверсия — через --bg-mix calc()
   на базовых правилах выше, [data-bg="dark"] больше не используется. */
/* Dark glass (не light, не minimal): композёр висит над тёмным фоном, серый
   --fg-dim сливается. По дефолту делаем иконки белёсо-белыми, тогда вся
   панель читается одинаково. Перекрытие синим пузырём поверх — уже отрабатывает
   правило выше через [data-bg="dark"] !important, остаётся синхронным. */
/* В дарк-glass все кнопки композера наследуют base = var(--fg) = #ffffff,
   как и стрелочка-ghost ">". Без перекрытия base'а они были чуть приглушённые
   (rgba .82) и визуально серее ghost — теперь весь ряд белый одинаково. */
/* Призрак-подсказка «>» слева от текстарии: дефолтным цветом был --fg-dim (серый),
   и на glass-dark стрелка визуально отбивалась от соседних иконок композёра
   (которые выше получают rgba(255,255,255,0.82)). Уравниваем — теперь весь
   набор знаков на полосе composer'а читается одним цветом. Инверсия под dark
   data-bg (синий пузырь сообщения за input'ом) у ghost уже работает выше через
   правила 6992/7002, она забивает это правило за счёт data-bg-селектора. */
:root.theme-glass:not(.theme-light):not(.theme-minimal) #input-ghost,
:root.theme-glass:not(.theme-light):not(.theme-minimal) #input-ghost .ghost-cur {
  color: rgba(255, 255, 255, 0.82);
}
/* Прикреплённые файлы — чип с превью и именем над синим бабблом. Тот же приём:
   фон прозрачный, белая обводка, белый текст. Включая крестик отмены. */
/* attach-chip / fav-react-chip / scroll-btn инверсия — через --bg-mix calc()
   на базовых правилах, [data-bg="dark"] здесь больше не используется. */

/* Юзер-баббл — нейтральный графит, без привязки к accent (раньше был
   var(--accent), но смена дефолта пикера на teal делает все user-бабблы
   ярко-бирюзовыми — Кириллу не нравится). Берём fg с прозрачностью —
   адаптируется и в dark, и в light. */
:root.theme-glass .msg.user .msg-bubble,
:root.theme-glass .msg.user {
  /* v908: solid --accent (без 75%-разбавления). Кирилл явно сказал «должен
     быть акцентного цвета», --user-bg (тёмно-синий #1f4d8a) воспринимался
     как унылый ночной синий, а --accent (#2a73e3) даёт яркий «голубой»
     цвет, перекликающийся с quick-reply баблами. */
  background: var(--accent);
  border: none;
  border-radius: 18px;
  box-shadow: none;
  color: var(--fg);
  padding: 10px 12px;
}

/* Имя ("ты") и время поверх user-баббла — приглушённый fg, без
   хардкода белого (фон теперь нейтральный, белая роль не нужна). */
:root.theme-glass .msg.user .role,
:root.theme-glass .msg.user .role .role-label,
:root.theme-glass .msg.user .msg-time {
  color: color-mix(in srgb, var(--fg) 70%, transparent) !important;
}

/* Заголовки сообщений ассистента/тулов (МАККОД, READ и т.д.) — в цвет акцента,
   чтобы выделять отправителя ярко. Время не трогаем. */
:root.theme-glass .msg:not(.user) .role .role-label {
  color: var(--accent) !important;
}
/* v1153: НО надпись плашки команды (tool) — зелёным, не акцентным. Правило выше
   красит все не-user заголовки в accent с !important; здесь перебиваем именно tool
   на green (та же спецификность 0,6,0, но ниже по каскаду → выигрывает). Кирилл:
   «надпись команды зелёненькой» — в тон зелёному времени/грани плашки. */
:root.theme-glass .msg.tool .role .role-label {
  color: var(--green) !important;
}

/* Ассистент пишет в открытую — без коробки, без рамки. */
:root.theme-glass .msg:not(.user),
:root.theme-glass .msg:not(.user) .msg-bubble {
  background: transparent;
  border: none;
  box-shadow: none;
}
/* .msg.bot.typing и .msg.thinking-phrase в дефолте имеют хардкод
   box-shadow: 2px 2px 0 rgba(255,138,61,0.25) (оранжевый) и анимацию
   botTypingPulse, которая каждый кадр перезаписывает box-shadow на
   оранжевый — это создаёт тонкую вертикальную оранжевую полосу
   справа от длинного баббла (2px-сдвинутый клон). В glass убиваем
   анимацию и шэдоу полностью. */
:root.theme-glass .msg.bot,
:root.theme-glass .msg.bot.typing,
:root.theme-glass .msg.thinking-phrase {
  box-shadow: none !important;
  border: none !important;
}
/* Fade-in только для свежих ботовых сообщений (.msg-new). Без гейта правило
   срабатывает повторно при смене класса (например, после bubbleTapFlash —
   когда снимается .bubble-tap, animation возвращается к glassBotFadeIn и
   запускается заново → визуально «вторая загрузка с фейдом» после press).
   .msg-new выставляется только в pushMsg при !renderingHistory и снимается
   через 600мс — за это окно fade успевает отыграть один раз. */
:root.theme-glass .msg.bot.msg-new,
:root.theme-glass .msg.bot.typing.msg-new {
  animation: glassBotFadeIn 0.85s cubic-bezier(0.22, 0.61, 0.36, 1) !important;
}
@keyframes glassBotFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
:root.theme-glass .msg.bot,
:root.theme-glass .msg.bot.typing,
:root.theme-glass .msg.bot .body,
:root.theme-glass .msg.bot .body p,
:root.theme-glass .msg.bot .body li,
:root.theme-glass .msg.bot .body span,
:root.theme-glass .msg.bot .body div,
:root.theme-glass .msg.bot .body strong,
:root.theme-glass .msg.bot .body b,
:root.theme-glass .msg.bot .body em,
:root.theme-glass .msg.bot .body i,
:root.theme-glass .msg.bot .body h1,
:root.theme-glass .msg.bot .body h2,
:root.theme-glass .msg.bot .body h3,
:root.theme-glass .msg.bot .body h4,
:root.theme-glass .msg.bot .body h5,
:root.theme-glass .msg.bot .body h6,
:root.theme-glass .msg.thinking-phrase,
:root.theme-glass .msg.thinking-phrase .phrase,
:root.theme-glass .msg.thinking-phrase .phrase-text {
  color: var(--fg);
}
:root.theme-glass .msg.bot .body strong,
:root.theme-glass .msg.bot .body b {
  font-weight: 700;
}

/* Quick-reply кнопки от бота — в дефолте острые прямоугольники с
   border-radius: 0. В glass скругляем в тон бирюзовым баблам (18px),
   не в pill — иначе много-строчные варианты выглядят чужеродно. */
:root.theme-glass .msg .body .quick-replies .qr-btn {
  border-radius: 18px;
  padding: 8px 14px;
}

/* Драуэр — полупрозрачный с blur'ом (стекло). Под ним проступает чат
   и красиво размывается.
   ВАЖНО: iOS WKWebView ломает backdrop-filter на элементах с transform.
   Поэтому в glass-теме слайд-анимация drawer'а идёт через left, а не
   через translateX (drag-to-close всё ещё ставит transform inline на
   время свайпа — на этот момент blur пропадает, но это ~200мс).
   БЕЗ !important на transform: иначе inline-style drawer.style.transform,
   который setDrawerDrag ставит каждый кадр свайпа, не применяется — и
   шторка не едет за пальцем, а прыгает в конечную точку через left. */
:root.theme-glass #drawer {
  transform: none;
  left: 0;
  transition: left 0.32s cubic-bezier(.22,.61,.36,1);
  background: rgba(13, 13, 13, 0.62);
  backdrop-filter: saturate(180%) blur(60px);
  -webkit-backdrop-filter: saturate(180%) blur(60px);
  border-right: 1px solid rgba(255, 255, 255, 0.06);
  box-shadow: none;
  border-top-right-radius: 18px;
  border-bottom-right-radius: 18px;
  overflow: hidden;
}
:root.theme-glass #drawer.hidden {
  transform: none;
  left: calc(-1 * (var(--drawer-width) + 24px));
}
/* Во время свайпа JS управляет позицией через inline transform каждый
   кадр — CSS-transition на left здесь только мешает (создаёт рывки). */
:root.theme-glass #drawer.dragging {
  transition: none;
}
:root.theme-glass body.is-desktop #drawer:not(.hidden) {
  transform: none;
  left: 0;
}
/* Нижняя панель drawer'а (5 иконок) — прозрачная, без своего фона.
   Сам drawer уже стеклянный — отдельная плашка под иконками только мешает. */
:root.theme-glass .drawer-footer {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}
/* Overlay поверх чата при открытом drawer'е — затемняем и размываем
   всё, что не относится к шторке. iOS-style. */
:root.theme-glass #drawer-overlay {
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: saturate(140%) blur(14px);
  -webkit-backdrop-filter: saturate(140%) blur(14px);
}

/* Поиск в драуэре — «⚡» (AI-поиск) и «×» (clear) видимы; «×» сам управляется
   классом .hidden из JS и появляется когда есть текст. */

/* Модалки — округлая карточка на лёгком бэкдропе с blur'ом контента сзади. */
:root.theme-glass #modal {
  background: rgba(0, 0, 0, 0.20);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
:root.theme-glass .modal-card {
  background: rgba(22, 22, 22, 0.70);
  backdrop-filter: saturate(180%) blur(30px);
  -webkit-backdrop-filter: saturate(180%) blur(30px);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 24px;
  box-shadow: none;
}
:root.theme-glass .modal-header {
  text-transform: none;
  letter-spacing: 0;
  font-size: 15px;
  font-weight: 600;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
:root.theme-glass .modal-body button,
:root.theme-glass .modal-body button.btn-rich,
:root.theme-glass .modal-body label.modal-pick {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 18px;
}
:root.theme-glass .modal-body button:hover,
:root.theme-glass .modal-body button.btn-rich:hover,
:root.theme-glass .modal-body label.modal-pick:hover {
  background: rgba(255, 255, 255, 0.08);
}
:root.theme-glass .modal-body button.active,
:root.theme-glass .modal-body label.modal-pick.active {
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  color: #ffffff;
}

/* Tech-карты и start-page карточки — округлые, без блюра. */
:root.theme-glass .tech-card,
:root.theme-glass .start-page .start-card,
:root.theme-glass .start-page .start-suggest {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 20px;
  box-shadow: none;
}

/* ===== Результат генерации (картинки/видео) в стекле =====
   Базово медиа-результат — острый pixel-бокс (.pixel-player / img.inline-img +
   мета-подпись codeblock). В glass-теме переоформляем под стекло: скругления,
   светлый полупрозрачный бордер, мягкая тень, blur на панели контролов.
   Scoped к :root.theme-glass — Ретро/Ликвид сохраняют острый вид. */
:root.theme-glass .msg .body img.inline-img {
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 16px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.30);
}
:root.theme-glass .msg .body .pixel-player {
  background: rgba(0, 0, 0, 0.28);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 16px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.30);
  overflow: hidden;
}
/* Видео обрезается по скруглению контейнера — своего радиуса не несёт. */
:root.theme-glass .msg .body .pixel-player video.inline-vid { border-radius: 0; }
/* Панель контролов — стекло вместо плоского чёрного градиента. */
:root.theme-glass .msg .body .pixel-player .pp-bar {
  background: rgba(0, 0, 0, 0.30);
  -webkit-backdrop-filter: saturate(160%) blur(12px);
  backdrop-filter: saturate(160%) blur(12px);
  border-top: 1px solid rgba(255, 255, 255, 0.10);
}
/* Кнопки плеера — стеклянные светлые, скруглённые, мягкий переход. */
:root.theme-glass .msg .body .pixel-player .pp-btn {
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  border-radius: 8px;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: background 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
:root.theme-glass .msg .body .pixel-player .pp-btn:hover { background: rgba(255, 255, 255, 0.28); }
:root.theme-glass .msg .body .pixel-player .pp-btn:active { background: rgba(255, 255, 255, 0.42); }
:root.theme-glass .msg .body .pixel-player .pp-track { height: 6px; border-radius: 4px; }
:root.theme-glass .msg .body .pixel-player .pp-track .pp-fill { border-radius: 4px; background: rgba(255, 255, 255, 0.92); }
/* Иконки кнопок: пиксель (classic) ↔ тонкий outline (glass). */
:root.theme-glass .msg .body .pixel-player .pp-ico.classic-icon { display: none; }
:root.theme-glass .msg .body .pixel-player .pp-ico.glass-icon { display: inline-flex; }
:root.theme-glass .msg .body .pixel-player .pp-btn,
:root.theme-glass .msg .body .pixel-player .pp-play { image-rendering: auto; }
/* Карусель: рамку/скругление несёт слайд; внутреннее медиа без своей рамки. */
:root.theme-glass .msg .body .media-carousel-track .cslide {
  border-radius: 16px;
  border: 1px solid rgba(255, 255, 255, 0.12);
}
:root.theme-glass .msg .body .media-carousel-track .cslide img.inline-img,
:root.theme-glass .msg .body .media-carousel-track .cslide .pixel-player {
  border: none;
  border-radius: 0;
  box-shadow: none;
}
:root.theme-glass .msg .body .media-carousel .ccount {
  border-radius: 12px;
  background: rgba(0, 0, 0, 0.40);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
:root.theme-glass .msg .body .media-carousel-dots .cdot { border-radius: 50%; }
/* Мета-подпись генерации — markdown того же сообщения: codeblock (многострочная
   мета video) и inline-code (seed). В glass — стеклянная плашка вместо плоского
   code-bg; текст/моноширинность не трогаем (читаемость кода сохранена). */
:root.theme-glass .msg .body pre.codeblock {
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: 14px;
  -webkit-backdrop-filter: saturate(140%) blur(10px);
  backdrop-filter: saturate(140%) blur(10px);
}
:root.theme-glass .msg .body code.inline {
  background: rgba(255, 255, 255, 0.10);
  border-radius: 6px;
}

/* Topbar-меню — округлый dropdown стеклянный.
   Стиль зеркалит #composer (нижний композер): полупрозрачный чёрный фон
   + blur, чтобы под плашкой реально читалось размытие, а не сплошной слой. */
:root.theme-glass #topbar-menu {
  left: 8px;
  right: 8px;
  /* Фрост ровно как у бокового меню (#drawer): полупрозрачный фон + сильный
     blur. КЛЮЧЕВОЕ для iOS WKWebView: на самой плашке НЕЛЬЗЯ держать
     will-change / анимацию opacity / transform / clip-path — любой из них
     промоутит её в отдельный композ-слой, и WK отдаёт «стейл-снапшот» фона
     вместо живого размытия (так и было в 1090: will-change:opacity заморозил
     блюр). Драуэр блюрит штатно именно потому, что will-change у него нет, а
     едет он через left. Повторяем его дисциплину: статичный backdrop-filter,
     ноль слой-промоутеров на плашке. Плавность открытия выносим на строки
     (.topbar-menu-row) — у них нет backdrop-filter, их анимировать безопасно. */
  background: rgba(13, 13, 13, 0.34);
  -webkit-backdrop-filter: saturate(180%) blur(60px);
  backdrop-filter: saturate(180%) blur(60px);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 16px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
  overflow: hidden;
  animation: none;
  will-change: auto;
}
:root.theme-glass #topbar-menu.closing {
  /* На закрытии плашка исчезает — живость блюра уже неважна, можно фейдить. */
  animation: topbarMenuGlassOut 160ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
@keyframes topbarMenuGlassOut { from { opacity: 1; } to { opacity: 0; } }
:root.theme-glass .topbar-menu-row {
  background: transparent;
  /* v1092: в glass подсветка строки — скруглённый «островок» с плавным фейдом
     (как в iOS-меню), а не квадрат во всю ширину. Жёсткие разделители убраны,
     строки разводит inset-зазор (margin). border-radius клипует фон-подсветку в
     скруглённый прямоугольник, transition делает её появление/уход плавным. */
  border-bottom: none;
  border-radius: 12px;
  margin: 2px 6px;
  transition: background 170ms cubic-bezier(0.4, 0, 0.2, 1);
  animation: topbarMenuRowIn 220ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
}
@keyframes topbarMenuRowIn { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
:root.theme-glass .topbar-menu-row:hover {
  background: rgba(255, 255, 255, 0.07);
}
:root.theme-glass .topbar-menu-row:active {
  background: rgba(255, 255, 255, 0.13);
}
/* v1038: уходим от серого в описаниях — как просил Кирилл по скрину выпадашки.
   Заголовок строки и так акцентный (бирюза), описание делаем читаемым --fg. */
:root.theme-glass .topbar-menu-row-desc {
  color: var(--fg);
}

/* Кодоблоки — едва-светлая заливка, чтобы отличались от ассистент-текста. */
:root.theme-glass pre,
:root.theme-glass .code-block {
  background: rgba(255, 255, 255, 0.05) !important;
  border: 1px solid rgba(255, 255, 255, 0.06) !important;
  border-radius: 14px !important;
}
:root.theme-glass code {
  background: rgba(255, 255, 255, 0.08) !important;
  border-radius: 6px;
  padding: 2px 6px;
}

/* Inline-код и codeblock — НЕ зелёный (кислотно на чёрном); просто моно белый. */
:root.theme-glass .msg .body code.inline,
:root.theme-glass .msg .body pre.codeblock,
:root.theme-glass .msg .body pre.codeblock code {
  color: #e6e6e6 !important;
}
/* Исключение для tool-карточек: технический текст внутри tool-body
   (пути файлов, аргументы, +N) — оставляем зелёным, как сам бордер карточки.
   В glass это даёт визуальную привязку: тех-карточка = зелёный акцент. */
:root.theme-glass .msg.tool .body,
:root.theme-glass .msg.tool .body code.inline,
:root.theme-glass .msg.tool .body pre.codeblock,
:root.theme-glass .msg.tool .body pre.codeblock code,
:root.theme-glass .msg.tool .body span,
:root.theme-glass .msg.tool .body p {
  color: var(--green) !important;
}
@property --tool-reveal-y {
  syntax: '<percentage>';
  initial-value: 0%;
  inherits: false;
}
:root.theme-glass .msg.tool.tool-wipe .body {
  animation: glass-tool-fade-reveal 0.9s cubic-bezier(0.4, 0, 0.2, 1) both;
  clip-path: none !important;
  -webkit-mask-image: linear-gradient(to bottom,
    #000 calc(var(--tool-reveal-y) - 28px),
    transparent var(--tool-reveal-y));
          mask-image: linear-gradient(to bottom,
    #000 calc(var(--tool-reveal-y) - 28px),
    transparent var(--tool-reveal-y));
}
@keyframes glass-tool-fade-reveal {
  from { --tool-reveal-y: 0%; opacity: 0.6; }
  to   { --tool-reveal-y: 130%; opacity: 1; }
}
:root.theme-glass .msg .body pre.codeblock {
  border-left: 1px solid rgba(255, 255, 255, 0.08) !important;
  background: rgba(255, 255, 255, 0.05) !important;
  border-radius: 14px;
}
:root.theme-glass .msg .body code.inline {
  background: rgba(255, 255, 255, 0.08) !important;
  border-radius: 6px;
  padding: 1px 6px;
  border: none !important;
}

/* Сессии в драуэре: фон прозрачный, чтобы blur драуэра был виден.
   Swipe-actions (закрепить/архив/удалить) лежат слоем ПОД .thread-item; при
   прозрачном item они бы проступали → гасим их opacity:0 по умолчанию и
   показываем только когда юзер реально свайпит (inline transform: translateX
   на .thread-item, проверяем через :has()). */
:root.theme-glass .thread-item {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 0;
  margin: 0;
  padding: 8px 12px;
}
:root.theme-glass .thread-item:hover {
  background: rgba(255, 255, 255, 0.04);
}
:root.theme-glass .thread-item.active {
  background: rgba(255, 255, 255, 0.08);
  border-color: transparent;
  color: var(--fg);
}
:root.theme-glass .thread-swipe-wrap > .thread-pin-act,
:root.theme-glass .thread-swipe-wrap > .thread-mark-act,
:root.theme-glass .thread-swipe-wrap > .thread-settings-act,
:root.theme-glass .thread-swipe-wrap > .thread-restore-act {
  opacity: 0;
  transition: opacity 0.12s ease;
}
:root.theme-glass .thread-swipe-wrap:has(.thread-item[style*="translate"]) > .thread-pin-act,
:root.theme-glass .thread-swipe-wrap:has(.thread-item[style*="translate"]) > .thread-mark-act,
:root.theme-glass .thread-swipe-wrap:has(.thread-item[style*="translate"]) > .thread-settings-act,
:root.theme-glass .thread-swipe-wrap:has(.thread-item[style*="translate"]) > .thread-restore-act {
  opacity: 1;
}
/* Во время свайпа даём строке солид-фон, иначе action-кнопки сзади
   просвечивают сквозь прозрачный thread-item. !important — чтобы
   перебить .thread-item:hover/.active правки в theme-light. */
:root.theme-glass .thread-item[style*="translate"],
:root.theme-glass.theme-light .thread-item[style*="translate"] {
  background: var(--bg) !important;
}

/* Шапка драуэра: usage-card (4ч/3д таймер) + поиск + scope-chips — прозрачные
   обёртки, чтобы blur драуэра был виден сквозь весь верхний блок. */
:root.theme-glass .drawer-header {
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
:root.theme-glass .drawer-sticky-shared {
  background: transparent;
}
:root.theme-glass .claude-usage-card {
  background: transparent;
  border: none;
  border-radius: 0;
  padding-left: 0;
  padding-right: 0;
}
:root.theme-glass .claude-usage-card .uc-title {
  border-bottom: 1px dashed rgba(255, 255, 255, 0.10);
}
:root.theme-glass .claude-usage-card .uc-bar,
:root.theme-glass .claude-usage-card .uc-compact-bar {
  background: transparent !important;
  border: none !important;
  border-radius: 0 !important;
  height: 2px !important;
}
:root.theme-glass .claude-usage-card .uc-bar-fill,
:root.theme-glass .claude-usage-card .uc-compact-fill {
  border-radius: 0 !important;
}
:root.theme-glass .claude-usage-card.is-loading .uc-compact-bar {
  border-color: transparent !important;
}
/* 4.11: батарейка должна жить и в стекле — возвращаем корпус (правило выше
   плющит и .uc-bar, и .uc-compact-bar в полоску 2px). Только компакт. */
:root.theme-glass .claude-usage-card .uc-compact-bar {
  border: 1px solid var(--accent, #ffb454) !important;
  border-radius: 4px !important;
  height: 18px !important;
  width: 38px;
  background: rgba(255, 255, 255, 0.04) !important;
}
:root.theme-glass .claude-usage-card .uc-compact-fill {
  border-radius: 3px !important;
}
:root.theme-glass .claude-usage-card .uc-rows,
:root.theme-glass .claude-usage-card .uc-foot {
  border-top: 1px dashed rgba(255, 255, 255, 0.10);
}
:root.theme-glass .thread-search {
  background: transparent !important;
  border: none !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}
:root.theme-glass .thread-search-input {
  background: transparent !important;
  border: none !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  outline: none !important;
  -webkit-appearance: none !important;
  appearance: none !important;
}
:root.theme-glass .thread-search-status {
  border: none !important;
  background: transparent !important;
}
:root.theme-glass .search-history-row {
  background: transparent !important;
  border: none !important;
}
:root.theme-glass .search-history-row:active {
  background: rgba(255, 255, 255, 0.06) !important;
  border: none !important;
}
:root.theme-glass .search-hist-del {
  opacity: 0;
  transition: opacity 0.12s ease;
}
:root.theme-glass .search-hist-wrap:has(.search-history-row[style*="translate"]) > .search-hist-del {
  opacity: 1;
}
:root.theme-glass .scope-chip {
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  -webkit-backdrop-filter: blur(6px) saturate(160%);
  backdrop-filter: blur(6px) saturate(160%);
}
:root.theme-glass .scope-chip.is-active {
  border-color: var(--accent);
  color: var(--accent);
}
:root.theme-glass .search-history {
  background: transparent;
}

/* Домашняя вкладка драуэра: избранное / непрочитанные / сессии / автоматизации.
   .start-row-swipe-внутри ставит фон var(--bg) (солид, чтобы прятать swipe-actions),
   а .start-busy-row без обёртки имеет border 1px var(--border). Всё это в стекле
   надо убрать: фон прозрачный, рамка тонкая полупрозрачная, swipe-actions гасим
   по умолчанию и показываем только когда юзер реально свайпит. */
:root.theme-glass .start-row-swipe .start-unread-row,
:root.theme-glass .start-row-swipe .start-busy-row {
  background: transparent;
}
:root.theme-glass .start-row-swipe .start-unread-row.current,
:root.theme-glass .start-row-swipe .start-busy-row.current {
  background: rgba(255, 255, 255, 0.08);
}
:root.theme-glass .start-busy-row {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 0;
}
:root.theme-glass .start-busy-row.current {
  background: rgba(255, 255, 255, 0.08);
  border-color: transparent;
  color: var(--fg);
}
:root.theme-glass .start-section {
  text-transform: lowercase;
  letter-spacing: 0;
  font-size: 10px;
  font-weight: 400;
  color: var(--fg-dim);
  border-bottom: none;
  padding: 22px 4px 12px;
}
:root.theme-glass .start-row-swipe > .thread-pin-act,
:root.theme-glass .start-row-swipe > .thread-mark-act,
:root.theme-glass .start-row-swipe > .thread-settings-act,
:root.theme-glass .start-row-swipe > .thread-restore-act {
  opacity: 0;
  transition: opacity 0.12s ease;
}
:root.theme-glass .start-row-swipe:has(.start-unread-row[style*="translate"]) > .thread-pin-act,
:root.theme-glass .start-row-swipe:has(.start-unread-row[style*="translate"]) > .thread-mark-act,
:root.theme-glass .start-row-swipe:has(.start-unread-row[style*="translate"]) > .thread-settings-act,
:root.theme-glass .start-row-swipe:has(.start-unread-row[style*="translate"]) > .thread-restore-act,
:root.theme-glass .start-row-swipe:has(.start-busy-row[style*="translate"]) > .thread-pin-act,
:root.theme-glass .start-row-swipe:has(.start-busy-row[style*="translate"]) > .thread-mark-act,
:root.theme-glass .start-row-swipe:has(.start-busy-row[style*="translate"]) > .thread-settings-act,
:root.theme-glass .start-row-swipe:has(.start-busy-row[style*="translate"]) > .thread-restore-act,
:root.theme-glass .start-row-swipe:has(.routine-run-row[style*="translate"]) > .thread-pin-act,
:root.theme-glass .start-row-swipe:has(.routine-run-row[style*="translate"]) > .thread-mark-act,
:root.theme-glass .start-row-swipe:has(.routine-run-row[style*="translate"]) > .thread-settings-act,
:root.theme-glass .start-row-swipe:has(.routine-run-row[style*="translate"]) > .thread-restore-act {
  opacity: 1;
}
/* Glass-стиль самой подложки свайп-action'ов: вместо плотного цветного
   квадрата (--accent синий для mark, #3a8c5f зелёный для restore) —
   полупрозрачный тонированный фон с тонкой правой границей. Иконка
   рендерится в fg-цвете для контраста на стеклянной подложке. */
:root.theme-glass .start-row-swipe > .thread-mark-act,
:root.theme-glass .thread-swipe-wrap > .thread-mark-act {
  background: rgba(42, 115, 227, 0.18);
  color: var(--fg);
  border-right: 1px solid var(--border);
}
:root.theme-glass .start-row-swipe > .thread-restore-act,
:root.theme-glass .thread-swipe-wrap > .thread-restore-act {
  background: rgba(48, 209, 88, 0.18);
  color: var(--fg);
  border-right: 1px solid var(--border);
}
:root.theme-glass.theme-light .start-row-swipe > .thread-mark-act,
:root.theme-glass.theme-light .thread-swipe-wrap > .thread-mark-act {
  background: rgba(42, 115, 227, 0.10);
}
:root.theme-glass.theme-light .start-row-swipe > .thread-restore-act,
:root.theme-glass.theme-light .thread-swipe-wrap > .thread-restore-act {
  background: rgba(48, 160, 80, 0.13);
}
/* v1039: «Закреп» (зелёный тон) и «Меню/···» (нейтральный) на свайпе чата —
   glass-подложки в тон конверту: полупрозрачный фон + тонкая правая граница,
   иконка в fg. Иначе pin падал на солид-зелёный #3a8c5f, more — на амбер. */
:root.theme-glass .thread-swipe-wrap > .thread-pin-act {
  background: rgba(48, 209, 88, 0.18);
  color: var(--fg);
  border-right: 1px solid var(--border);
}
:root.theme-glass .thread-swipe-wrap > .thread-pin-act.pinned {
  background: rgba(255, 255, 255, 0.10);
}
:root.theme-glass .start-row-swipe > .thread-settings-act,
:root.theme-glass .thread-swipe-wrap > .thread-settings-act {
  background: rgba(255, 255, 255, 0.08);
  color: var(--fg);
  border-right: 1px solid var(--border);
}
:root.theme-glass.theme-light .thread-swipe-wrap > .thread-pin-act {
  background: rgba(48, 160, 80, 0.13);
}
:root.theme-glass.theme-light .start-row-swipe > .thread-settings-act,
:root.theme-glass.theme-light .thread-swipe-wrap > .thread-settings-act {
  background: rgba(0, 0, 0, 0.05);
}
/* В glass-теме строки журнала рутин не закрашиваем --bg
   (в glass-dark это #000 — солидный чёрный поверх полупрозрачной
   карточки рутины). Прозрачный фон даёт строкам слиться с карточкой.
   Свайп-action под строкой и так скрыт opacity:0 в покое — фон не нужен.
   !important — на случай если базовое правило .start-row-swipe
   .routine-run-row { background: var(--bg) } выигрывает по порядку. */
:root.theme-glass .routine-run-swipe-wrap,
:root.theme-glass .start-row-swipe .routine-run-row {
  background: transparent !important;
}
:root.theme-glass .routine-run-row:hover,
:root.theme-glass .routine-run-row:active {
  background: rgba(255, 255, 255, 0.06) !important;
}
/* Во время свайпа даём строке солид-фон, иначе action-кнопки сзади
   просвечивают сквозь прозрачный текст строки. !important — чтобы
   перебить current/active правки в обоих стеклах. */
:root.theme-glass .start-unread-row[style*="translate"],
:root.theme-glass .start-busy-row[style*="translate"],
:root.theme-glass .routine-run-row[style*="translate"],
:root.theme-glass.theme-light .start-unread-row[style*="translate"],
:root.theme-glass.theme-light .start-busy-row[style*="translate"],
:root.theme-glass.theme-light .routine-run-row[style*="translate"] {
  background: var(--bg) !important;
}

/* Квадратные пиксельные status-маркеры → кружочки. Размер совпадает с
   классической темой (16×16, font-size: 10px), чтобы цифра непрочитанных
   тоже была видна, как у соответствующих статусов в светлой/тёмной теме. */
:root.theme-glass .thread-status {
  border-radius: 50%;
  width: 16px;
  height: 16px;
  margin-right: 10px;
  font-size: 10px;
}

/* Плавающие стрелки ▲/▼ в правом нижнем углу чата — круглые стеклянные
   плашки. position: absolute внутри #composer (top: -46px / -90px из
   базового правила), стрелки физически привязаны к контейнеру и
   двигаются с ним как единое целое — этим закрыт баг «дрейфа» при
   сворачивании iOS-клавиатуры. Backdrop-filter работает на дочернем
   элементе backdrop-родителя — blur(6px) saturate(160%) даёт привычный
   стеклянный эффект поверх ленты сообщений. Animation: arrowAppear
   глушим — на iOS в комбинации с transform-ом родителя при kb-active
   даёт визуальные дёрги. */
:root.theme-glass #scroll-up-btn,
:root.theme-glass #scroll-down-btn {
  /* v948 #1 (фикс): --bg-mix пишется JS через applyMixToRow по объединённому
     rect обеих стрелок. Раньше background и box-shadow были захардкожены
     с !important — переменная писалась, но CSS её не читал, инверсии не
     было. Теперь background/color/box-shadow интерполируются calc'ом 1-в-1
     как у .input-wrap: подложка с пузырём → стрелки темнеют пропорционально. */
  --bg-mix: 0;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: color-mix(in srgb, rgba(255, 255, 255, 0.03) calc((1 - var(--bg-mix)) * 100%), rgba(0, 0, 0, 0.45)) !important;
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow:
    0 8px 14px -6px rgba(0, 0, 0, 0.30),
    inset 0 1px 0 rgba(255, 255, 255, calc(0.10 + var(--bg-mix) * 0.12)),
    inset 0 -2px 6px rgba(0, 0, 0, calc(var(--bg-mix) * 0.18)) !important;
  color: color-mix(in srgb, var(--fg) calc((1 - var(--bg-mix)) * 100%), #ffffff);
  animation: none !important;
  transition: transform 0.22s cubic-bezier(0.3, 1.4, 0.5, 1),
              color 0.08s linear,
              background 0.08s linear,
              box-shadow 0.08s linear,
              opacity 0.18s ease !important;
  transform-origin: center;
  will-change: transform;
  -webkit-backdrop-filter: blur(6px) saturate(160%);
  backdrop-filter: blur(6px) saturate(160%);
}
:root.theme-glass #scroll-up-btn.pressing,
:root.theme-glass #scroll-down-btn.pressing {
  transform: scale(0.92);
  background: rgba(255, 255, 255, 0.22) !important;
}
/* Бейдж непрочитанных на стрелке-вниз — круглый, цифра в тон самой стрелке
   (через color: inherit), чтобы инверсия по --bg-mix совпадала. */
:root.theme-glass #scroll-down-btn .scroll-badge {
  border-radius: 50%;
  color: inherit;
  transition: color 0.08s linear;
}
/* #68: растущий бейдж непрочитанных. В glass общий badgeBump со steps(4)
   выглядит рубленым (он для ретро) — даём плавный pop с лёгким overshoot.
   Каждый bot-баббл за экраном перезапускает .bump через reflow-трюк в
   updateUnreadBadge → цифра «оживает» на КАЖДЫЙ инкремент (Кирилл: «с
   приятной анимацией причём они меняются»). */
:root.theme-glass #scroll-down-btn.has-unread .scroll-badge.bump {
  animation: badgeBumpGlass 0.34s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes badgeBumpGlass {
  0%   { transform: scale(0.7); }
  45%  { transform: scale(1.28); }
  100% { transform: scale(1); }
}

/* Баг 5b (Кирилл/Маня): в теме «стекло» переход стрелка→цифра, между цифрами
   и цифра→стрелка должен быть ПЛАВНЫМ, а не мгновенной подменой display
   (как в ретро, styles.css:1936-1937). Держим обе сущности всегда в потоке
   стопкой через inset:0 и крестфейдим opacity+transform. Число→число
   по-прежнему оживляет badgeBumpGlass (см. выше — animation бьёт transition). */
:root.theme-glass #scroll-down-btn .scroll-arrow,
:root.theme-glass #scroll-down-btn.has-unread .scroll-arrow,
:root.theme-glass #scroll-down-btn .scroll-badge,
:root.theme-glass #scroll-down-btn.has-unread .scroll-badge {
  display: flex !important;
  position: absolute;
  inset: 0;
  align-items: center;
  justify-content: center;
  transition: opacity 0.24s cubic-bezier(0.4, 0, 0.2, 1),
              transform 0.24s cubic-bezier(0.4, 0, 0.2, 1),
              color 0.08s linear;
}
:root.theme-glass #scroll-down-btn .scroll-arrow {
  opacity: 1;
  transform: translateY(0) scale(1);
}
:root.theme-glass #scroll-down-btn .scroll-badge {
  opacity: 0;
  transform: translateY(-28%) scale(0.55);
}
:root.theme-glass #scroll-down-btn.has-unread .scroll-arrow {
  opacity: 0;
  transform: translateY(28%) scale(0.55);
}
:root.theme-glass #scroll-down-btn.has-unread .scroll-badge {
  opacity: 1;
  transform: translateY(0) scale(1);
}

/* Плавное скрытие самой кнопки в стекле: animation:none !important глушит
   arrowDisappear, поэтому fade-out даём через opacity (в transition кнопки
   уже есть opacity 0.18s ease). JS-страховка в setArrowVisible снимет
   .leaving→.hidden по таймеру 260мс, когда фейд досчитает. */
:root.theme-glass #scroll-down-btn.leaving,
:root.theme-glass #scroll-up-btn.leaving {
  opacity: 0;
}

/* Заголовки секций в драуэре («▸ СЕССИИ») — строчными, без капс-letter-spacing. */
:root.theme-glass .thread-section-hdr,
:root.theme-glass .drawer-section {
  text-transform: lowercase;
  letter-spacing: 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--fg-dim);
  text-align: left;
  border-top: none;
  padding: 14px 14px 6px;
}

/* Send-кнопка: маскот должен быть белым на синем круге (не чёрным). */
:root.theme-glass .input-bottom-bar > #send.input-bar-btn-send {
  /* v943: в тёмной glass — без акцентного фона. Прозрачно-тёмный с белой
     обводкой, маскот белый — единая палитра «всё, что не контент, белое».
     В светлой теме оставляем акцент как primary CTA (контраст со светлым). */
  background: rgba(255, 255, 255, 0.10);
  border-color: rgba(255, 255, 255, 0.30);
  border-radius: 999px;
}
:root.theme-glass.theme-light .input-bottom-bar > #send.input-bar-btn-send {
  /* v952: круг АКЦЕНТНОГО цвета (форма как в тёмной Glass), маскот внутри
     белый. Кирилл: «маскот запихай в круг как у тёмной темы, круг должен
     быть акцентного цвета». До этого v951 #1 убрал подложку — маскот был
     акцентный без круга; v952 откатывает к кругу, но красит сам круг
     акцентом, а маскот делает белым для контраста (как is-stop ниже). */
  background: var(--accent);
  border-color: var(--accent);
  border-radius: 999px;
}
:root.theme-glass .input-bottom-bar > #send.input-bar-btn-send .send-mascot > g,
:root.theme-glass .input-bottom-bar > #send.input-bar-btn-send .send-mascot-eyelids {
  fill: rgba(255, 255, 255, 0.95) !important;
}
:root.theme-glass.theme-light .input-bottom-bar > #send.input-bar-btn-send .send-mascot > g,
:root.theme-glass.theme-light .input-bottom-bar > #send.input-bar-btn-send .send-mascot-eyelids,
:root.theme-glass.theme-light .input-bottom-bar > #send.input-bar-btn-send .send-mascot rect {
  /* v952: маскот в светлой Glass — белый на акцентном круге. Раньше
     (v948 #5) был fill: var(--accent), но без круга. Теперь круг сам
     акцентный, поэтому маскот должен контрастировать — белым.
     v953: добавили селектор `.send-mascot rect` — в Glass маскот собран из
     <rect>'ов (см. L9759 базовое правило + L9767 v949-fix красит rect
     в accent). Без перебивки rect оставался акцентным на акцентном
     круге — маскот становился невидимым. */
  fill: #ffffff !important;
}
/* v948 #5: STOP-режим в светлой glass — оставляем акцентный круг с белым
   квадратиком (это всё ещё нужно как «горячее» состояние, в отличие от
   обычного send). Снимаем дребезг — color наследуется от is-stop ниже. */
:root.theme-glass.theme-light .input-bottom-bar > #send.input-bar-btn-send.is-stop {
  background: var(--accent);
  border-color: var(--accent);
}
:root.theme-glass.theme-light #send.is-stop .send-stop > rect {
  fill: #ffffff;
}
:root.theme-glass.theme-light #send.is-stop {
  color: #ffffff;
  border-color: var(--accent);
}

/* Поисковое поле в драуэре и любые input — pill. */
:root.theme-glass #drawer input[type="text"],
:root.theme-glass #drawer input[type="search"] {
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 999px;
  color: var(--fg);
}

/* Подстраницы (Настройки / Рутины / Инструменты / Память / Просмотр файла) —
   стеклянные как драуэр: полупрозрачный фон + backdrop blur, чтобы содержимое
   чата сзади просвечивало с размытием. */
:root.theme-glass #memory-edit-page,
:root.theme-glass #file-view-page {
  background: rgba(13, 13, 13, 0.40);
  -webkit-backdrop-filter: saturate(180%) blur(60px);
  backdrop-filter: saturate(180%) blur(60px);
}
/* Подстраницы внутри drawer'а (Настройки / Сервисы / Диагностика) — прозрачные.
   Стекло + blur даёт сам drawer; своя плашка делает их визуально плотнее
   зоны footer'а (где нет своего background+blur). С transparent все три
   секции drawer'а размываются одинаково с нижним баром иконок. */
:root.theme-glass #settings-page,
:root.theme-glass #health-page,
:root.theme-glass #connection-page,
:root.theme-glass #notifications-page,
:root.theme-glass #routines-page,
:root.theme-glass #tech-page {
  background: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
/* Строки и фильтр уведомлений на стекле — прозрачный фон, чтобы blur драуэра
   проступал; разделители/обводки полупрозрачно-белые, как у списка чатов. */
:root.theme-glass .notif-row { background: transparent; }
:root.theme-glass .notif-row:active { background: rgba(255, 255, 255, 0.06); }
/* Прозрачная строка раскрывала бы accent-кнопку «прочитать», лежащую под ней
   (notif-mark-act, left:0) — на стекле она просвечивала акцентной полосой с
   галочкой. Прячем её в покое и показываем только при свайпе (inline translate
   на .notif-row), а едущей строке даём солид-фон — один в один как у чатов. */
:root.theme-glass .notif-swipe-wrap > .notif-mark-act {
  opacity: 0;
  transition: opacity 0.12s ease;
}
:root.theme-glass .notif-swipe-wrap:has(.notif-row[style*="translate"]) > .notif-mark-act {
  opacity: 1;
}
:root.theme-glass .notif-row[style*="translate"] { background: var(--bg) !important; }
:root.theme-glass .notif-filter { border-bottom-color: rgba(255, 255, 255, 0.06); }
:root.theme-glass .notif-filter-btn {
  border-color: rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.03);
}
:root.theme-glass .notif-filter-btn.active {
  color: var(--accent);
  border-color: var(--accent);
  background: rgba(255, 255, 255, 0.08);
}
:root.theme-glass .settings-header {
  background: transparent;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
/* Подстраницы (settings/health/connection) рендерятся ВНУТРИ драуэра поверх
   списка чатов. С прозрачным фоном сквозь них видны чаты драуэра — а должен
   быть виден основной чат за драуэром. Скрываем "подложку" драуэра, когда
   открыта одна из этих подстраниц: header / usage-card+поиск / drawer-views.
   Сам стеклянный фон драуэра остаётся — через него и виден чат. */
:root.theme-glass body.settings-open #drawer .drawer-header,
:root.theme-glass body.settings-open #drawer .drawer-sticky-shared,
:root.theme-glass body.settings-open #drawer #drawer-views,
:root.theme-glass body.health-open #drawer .drawer-header,
:root.theme-glass body.health-open #drawer .drawer-sticky-shared,
:root.theme-glass body.health-open #drawer #drawer-views,
:root.theme-glass body.connection-open #drawer .drawer-header,
:root.theme-glass body.connection-open #drawer .drawer-sticky-shared,
:root.theme-glass body.connection-open #drawer #drawer-views,
:root.theme-glass body.news-open #drawer .drawer-header,
:root.theme-glass body.news-open #drawer .drawer-sticky-shared,
:root.theme-glass body.news-open #drawer #drawer-views,
:root.theme-glass body.notifications-open #drawer .drawer-header,
:root.theme-glass body.notifications-open #drawer .drawer-sticky-shared,
:root.theme-glass body.notifications-open #drawer #drawer-views,
:root.theme-glass body.routines-open #drawer .drawer-header,
:root.theme-glass body.routines-open #drawer .drawer-sticky-shared,
:root.theme-glass body.routines-open #drawer #drawer-views,
:root.theme-glass body.tech-open #drawer .drawer-header,
:root.theme-glass body.tech-open #drawer .drawer-sticky-shared,
:root.theme-glass body.tech-open #drawer #drawer-views {
  visibility: hidden;
}

/* Футер на странице уведомлений — дефолтный стеклянный, как в «Сервисах»:
   страница #notifications-page теперь останавливается НАД баром (bottom:
   --drawer-foot-h, см. правило #notifications-page выше), поэтому лента не
   заезжает под футер и не просвечивает. Отдельная подложка бару не нужна —
   раньше тут стоял непрозрачный rgba(13,13,13,0.97), но он смотрелся чёрным
   бруском на стеклянном фоне. Убрано. */

/* Маккод-индикатор «думает» (#chat-thinking) — в стекле минимализм:
   снимаем карточку, обводку, левую полоску и тень; всё (надпись, маскот, точки)
   красим акцентным цветом. Анимации входа/выхода/морфинга не трогаем.
   Горизонтальный padding 10px — совпадает с .msg (8px 10px), чтобы
   индикатор выравнивался по левому краю с сообщениями Маккода.
   v941: в тёмной glass возвращаем var(--accent) (раньше было белое #ffffff —
   Кирилл попросил вернуть акцент: «в темной теме маккод написано белым,
   а должно акцентным цветом»). */
:root.theme-glass #chat-thinking {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  padding: 6px 10px !important;
  color: var(--accent) !important;
}
:root.theme-glass #chat-thinking .role {
  color: var(--accent) !important;
}
:root.theme-glass #chat-thinking .chat-thinking-mac {
  color: rgba(255, 255, 255, 0.95) !important;
}
:root.theme-glass #chat-thinking .chat-thinking-mac > g {
  fill: rgba(255, 255, 255, 0.95) !important;
}
:root.theme-glass #chat-thinking .chat-thinking-row {
  color: rgba(255, 255, 255, 0.95) !important;
}
/* v948 #4: в светлой glass-теме маскот печати и точки — акцентным цветом
   (раньше были чёрные rgba(0,0,0,0.78), Кирилл попросил «в светлой теме
   маскот который печатает сделай цветом акцентным»). В тёмной — белый
   (см. v942 #4), не трогаем. */
:root.theme-glass.theme-light #chat-thinking .chat-thinking-mac {
  color: var(--accent) !important;
}
:root.theme-glass.theme-light #chat-thinking .chat-thinking-mac > g {
  fill: var(--accent) !important;
}
:root.theme-glass.theme-light #chat-thinking .chat-thinking-row {
  color: var(--accent) !important;
}

/* thread-done-pop в glass: убираем пиксельные рамки и острые углы. */
:root.theme-glass #thread-done-pop {
  background: rgba(18, 18, 18, 0.72);
  backdrop-filter: saturate(180%) blur(24px);
  -webkit-backdrop-filter: saturate(180%) blur(24px);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-left: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 18px;
  box-shadow:
    0 16px 48px rgba(0, 0, 0, 0.60),
    0 2px 10px rgba(0, 0, 0, 0.35),
    inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
:root.theme-glass #thread-done-pop.is-error {
  border-color: rgba(255, 69, 58, 0.45);
}
:root.theme-glass #thread-done-pop.is-stopped {
  border-color: rgba(255, 255, 255, 0.12);
}

/* Светлый glass: белая полупрозрачная подложка вместо тёмной (иначе серое
   уведомление выглядит чужеродно на светлой теме — см. v680). */
:root.theme-glass.theme-light #thread-done-pop {
  background: rgba(255, 255, 255, 0.72);
  border: 1px solid rgba(0, 0, 0, 0.14);
  box-shadow:
    0 16px 48px rgba(0, 0, 0, 0.16),
    0 2px 10px rgba(0, 0, 0, 0.08),
    inset 0 1px 0 rgba(255, 255, 255, 0.50);
}
:root.theme-glass.theme-light #thread-done-pop.is-error {
  border-color: rgba(200, 40, 40, 0.45);
}
:root.theme-glass.theme-light #thread-done-pop.is-stopped {
  border-color: rgba(0, 0, 0, 0.18);
}

/* toast в glass: тот же язык — полупрозрачная капсула с blur, без
   пиксельной угловатости и без uppercase-letterspacing. Лежит ниже
   топбара (top увеличиваем, чтобы не наезжал на gauss-blur топбара). */
:root.theme-glass #toast {
  top: calc(var(--topbar-h, 56px) + var(--safe-top, 0px) + 8px);
  background: rgba(18, 18, 18, 0.72);
  backdrop-filter: saturate(180%) blur(24px);
  -webkit-backdrop-filter: saturate(180%) blur(24px);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 14px;
  padding: 8px 14px;
  font-size: 13px;
  text-transform: none;
  letter-spacing: 0;
  color: var(--fg);
  box-shadow:
    0 16px 48px rgba(0, 0, 0, 0.60),
    0 2px 10px rgba(0, 0, 0, 0.35),
    inset 0 1px 0 rgba(255, 255, 255, 0.08);
}

/* Светлый glass-тост: белая полупрозрачная подложка с blur, чтобы соответствовал
   стилю темы; цвет текста — var(--fg) (в светлой это уже тёмный). */
:root.theme-glass.theme-light #toast {
  background: rgba(255, 255, 255, 0.72);
  border: 1px solid rgba(0, 0, 0, 0.14);
  color: var(--fg);
  box-shadow:
    0 16px 48px rgba(0, 0, 0, 0.16),
    0 2px 10px rgba(0, 0, 0, 0.08),
    inset 0 1px 0 rgba(255, 255, 255, 0.60);
}

/* Tactile ripple на кнопках композера (attach, mic, voice-mode, send, chips).
   JS на pointerdown создаёт <span class="ripple"> с координатами клика и размером;
   CSS анимирует scale 0 → 2.4 + opacity 0.32 → 0. Хост-кнопкам ставим
   position:relative + overflow:hidden, чтобы волна обрезалась границей формы. */
.input-bar-btn,
.input-bar-btn-send,
#meta-row .chip {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.ripple {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  /* Плавные края: плотный центр до 55%, мягкое затухание к 100% радиуса.
     В сочетании с финальным scale 1.05 видимая часть точно добегает до
     дальнего угла хоста (диаметр в JS = 2× дистанции до дальнего угла). */
  background: radial-gradient(circle, currentColor 0%, currentColor 55%, transparent 100%);
  transform: translate(-50%, -50%) scale(0.1);
  opacity: 0.5;
  animation: ripple-expand 480ms cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
  z-index: 0;
  will-change: transform, opacity;
}
@keyframes ripple-expand {
  0%   { transform: translate(-50%, -50%) scale(0.1);  opacity: 0.5; }
  60%  { opacity: 0.3; }
  100% { transform: translate(-50%, -50%) scale(1.05); opacity: 0; }
}
/* Дети кнопки (svg/span) поднимаем над ripple, чтобы волна шла ПОД иконкой.
   Сам .ripple исключаем — иначе z-index: 1 переопределит наш z-index: 0 и
   волна окажется ПОВЕРХ иконки (выглядело как вспышка из центра кнопки).
   .send-mascot и .send-stop исключаем тоже — у них свой position: absolute
   для геометрического центрирования в круге #send (иначе relative ломает
   top:50%/left:50% и маскот съезжает вниз-вправо в круге). */
.input-bar-btn > *:not(.ripple),
.input-bar-btn-send > *:not(.ripple):not(.send-mascot):not(.send-stop),
#meta-row .chip > *:not(.ripple) {
  position: relative;
  z-index: 1;
}
#send.input-bar-btn-send > .send-mascot,
#send.input-bar-btn-send > .send-stop {
  z-index: 1;
}

/* Ripple для textarea #input: сам textarea — replaced element, child'ов в
   него не добавишь, поэтому ripple крепим на .input-wrap (родителя) и
   позиционируем по координатам тапа внутри .input-wrap. Текстовое поле
   непрозрачное, поэтому ripple должен быть НАД textarea (z-index выше
   #input), но с pointer-events: none — ввод не блокируется. Заодно ставим
   overflow:hidden на input-wrap, чтобы волна обрезалась рамкой поля. */
.input-wrap { overflow: hidden; }
.input-wrap > .ripple {
  /* Textarea непрозрачное, ripple должен быть НАД ним (но pointer-events:none).
     v1175: заметная волна от пальца до краёв поля. Раньше opacity 0.22 + core 20%
     + blur 14px давали почти невидимую дымку; Кирилл просил «волна идёт до краёв
     поля ввода». Плотнее ядро (40%), меньше блюр (9px) и своя кривая ripple-input
     с пиком 0.5 — фронт читается, край мягко растворяется. Диаметр в JS = 2× до
     дальнего угла, scale доводит фронт до границы; overflow:hidden клипует по полю. */
  z-index: 5;
  background: radial-gradient(circle, currentColor 0%, currentColor 40%, transparent 100%);
  filter: blur(9px);
  -webkit-filter: blur(9px);
  opacity: 0.5;
  animation: ripple-input 560ms cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
}
@keyframes ripple-input {
  0%   { transform: translate(-50%, -50%) scale(0.1);  opacity: 0.5; }
  55%  { opacity: 0.36; }
  100% { transform: translate(-50%, -50%) scale(1.05); opacity: 0; }
}

/* ===== Global ripple — волна от кнопки композера по всему окну (glass-only).
   В отличие от .ripple (живёт внутри хоста, обрезается overflow:hidden),
   .ripple-global крепится на <body> с position: fixed и не клипится. Размер
   рассчитывается в JS из координат тапа до дальнего угла viewport'а. */
.ripple-global {
  position: fixed;
  border-radius: 50%;
  pointer-events: none;
  z-index: 9999;
  /* Софт-волна: размазанные стопы (0/20/45/80/100) + filter:blur делают
     край расплывчатым — нет видимых колец на градиенте. */
  background: radial-gradient(
    circle,
    rgba(255, 255, 255, 0.18) 0%,
    rgba(255, 255, 255, 0.12) 20%,
    rgba(255, 255, 255, 0.06) 45%,
    rgba(255, 255, 255, 0.02) 80%,
    transparent 100%
  );
  transform: translate(-50%, -50%) scale(0.02);
  opacity: 0.55;
  animation: ripple-global-expand 820ms cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
  mix-blend-mode: screen;
  filter: blur(28px);
  -webkit-filter: blur(28px);
  will-change: transform, opacity, filter;
}
:root.theme-glass.theme-light .ripple-global {
  /* В светлой glass multiply поверх #f5f3ee требует более плотных стопов,
     иначе волна почти не читается. Поднимаем alphas в ~2× и opacity 0.55→0.95,
     чтобы интенсивность тапа совпадала с тёмной версией. */
  background: radial-gradient(
    circle,
    rgba(0, 0, 0, 0.30) 0%,
    rgba(0, 0, 0, 0.20) 20%,
    rgba(0, 0, 0, 0.10) 45%,
    rgba(0, 0, 0, 0.03) 80%,
    transparent 100%
  );
  mix-blend-mode: multiply;
  opacity: 0.95;
}
@keyframes ripple-global-expand {
  0%   { transform: translate(-50%, -50%) scale(0.02); opacity: 0.55; }
  55%  { opacity: 0.35; }
  100% { transform: translate(-50%, -50%) scale(1.0);  opacity: 0; }
}

/* ===== Theme: glass — image viewer + attachments polish ===== */
/* Кнопки в попапе картинок: круглые, с блюром, минималистичные иконки. */
:root.theme-glass #image-viewer-close {
  width: 40px;
  height: 40px;
  min-width: 40px;
  min-height: 40px;
  padding: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.34);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  backdrop-filter: blur(18px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: transparent;
  font-size: 0;
  line-height: 0;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}
:root.theme-glass #image-viewer-close::before {
  content: "";
  width: 16px;
  height: 16px;
  background: rgba(255, 255, 255, 0.92);
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round'><line x1='5' y1='5' x2='19' y2='19'/><line x1='19' y1='5' x2='5' y2='19'/></svg>") center / contain no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round'><line x1='5' y1='5' x2='19' y2='19'/><line x1='19' y1='5' x2='5' y2='19'/></svg>") center / contain no-repeat;
}

:root.theme-glass #image-viewer .iv-action {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.34);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  backdrop-filter: blur(18px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: rgba(255, 255, 255, 0.95);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}
:root.theme-glass #image-viewer .iv-action svg {
  width: 18px;
  height: 18px;
  stroke: rgba(255, 255, 255, 0.95);
}
:root.theme-glass #image-viewer .iv-actions { gap: 14px; }

/* Счётчик «1 / 2» в glass-теме — pill с лёгким blur'ом, под общую эстетику. */
:root.theme-glass #image-viewer .iv-count {
  border-radius: 999px;
  padding: 5px 12px;
  background: rgba(0, 0, 0, 0.42);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  backdrop-filter: blur(14px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.16);
  color: rgba(255, 255, 255, 0.92);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
}

/* Превью прикреплённых файлов: в input-баре и внутри сообщений — glass + скруглённые. */
:root.theme-glass .attach-chip {
  --bg-mix: 0;
  background: color-mix(in srgb, rgba(255, 255, 255, 0.03) calc((1 - var(--bg-mix)) * 100%), rgba(0, 0, 0, 0.40));
  -webkit-backdrop-filter: blur(6px) saturate(160%);
  backdrop-filter: blur(6px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 14px;
  padding: 4px 8px 4px 4px;
  color: color-mix(in srgb, var(--fg) calc((1 - var(--bg-mix)) * 100%), #ffffff);
  box-shadow:
    0 10px 10px -8px rgba(0, 0, 0, 0.50),
    inset 0 1px 0 rgba(255, 255, 255, calc(0.10 + var(--bg-mix) * 0.12)),
    inset 0 -2px 6px rgba(0, 0, 0, calc(var(--bg-mix) * 0.18));
  transition: background 0.08s linear,
              color 0.08s linear,
              box-shadow 0.08s linear;
}
/* Дети .attach-chip (имя, размер, ×, retry, %) в дефолте имеют свой var(--fg-dim)
   или var(--accent) — серое/синее. В glass переопределяем на inherit, тогда они
   следуют за calc-color самого .attach-chip (var(--fg)→#fff по --bg-mix). Это
   и есть «вес/крестик/имя должны белеть как сам чип» из feedback Кирилла. */
:root.theme-glass .attach-chip .attach-name,
:root.theme-glass .attach-chip .attach-size,
:root.theme-glass .attach-chip .attach-rm {
  color: inherit;
  transition: color 0.08s linear;
}
:root.theme-glass .attach-chip .attach-retry,
:root.theme-glass .attach-chip .attach-progress-pct {
  transition: color 0.08s linear;
}
:root.theme-glass .attach-chip .attach-thumb {
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.12);
}
:root.theme-glass .attach-chip.pending,
:root.theme-glass .attach-chip.uploading {
  border-style: solid;
}

/* Стартовая колонка непрочитанных: убираем пунктиры (выглядят как «выделение» при тапе). */
:root.theme-glass .start-unread-row {
  border-style: solid;
  border-color: transparent;
  border-bottom-color: rgba(255, 255, 255, 0.08);
}
:root.theme-glass .start-unread-row:active,
:root.theme-glass .start-unread-row.current {
  border-style: solid;
}

/* Настройки: дефолт ставит border-top на КАЖДУЮ строку → лесенка серых полос.
   В стекле оставляем разделитель только между секциями (через .settings-group-title). */
:root.theme-glass .settings-row {
  border-top: 0;
}
:root.theme-glass .settings-row:last-child {
  border-bottom: 0;
}
:root.theme-glass .settings-group-title {
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  margin-top: 6px;
}
:root.theme-glass .settings-group-title:first-child {
  border-top: 0;
  margin-top: 0;
}

/* Health-page и стартовый health-блок: убираем пунктирные разделители в стекле. */
:root.theme-glass .health-summary,
:root.theme-glass .health-page .start-section,
:root.theme-glass .start-health-list,
:root.theme-glass .start-health-sub,
:root.theme-glass .start-health-sep {
  border-top: 0;
  border-bottom: 0;
}

:root.theme-glass .msg-attach-chip {
  background: rgba(255, 255, 255, 0.06);
  -webkit-backdrop-filter: blur(12px) saturate(150%);
  backdrop-filter: blur(12px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 14px;
  padding: 4px 10px 4px 4px;
}
:root.theme-glass .msg-attach-chip .msg-attach-thumb {
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.12);
}

/* ===== Theme: glass — modal close + reactions + actions polish ===== */

/* Все ✕-крестики закрытия — круглые glass-плашки с линейным X.
   Модалка, дровер, settings/health/connection/routines/tech back-кнопки,
   file-view-close, image-viewer-close. */
:root.theme-glass .icon-btn[aria-label="Закрыть"],
:root.theme-glass #drawer-close,
:root.theme-glass #modal-close {
  position: relative;
  width: 36px; height: 36px; min-width: 36px; min-height: 36px;
  padding: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.06);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  backdrop-filter: blur(14px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, 0.10);
  color: transparent;
  font-size: 0;
  line-height: 0;
  overflow: hidden;
  isolation: isolate;
  display: inline-flex; align-items: center; justify-content: center;
}
:root.theme-glass .icon-btn[aria-label="Закрыть"]::before,
:root.theme-glass #drawer-close::before,
:root.theme-glass #modal-close::before {
  content: "";
  width: 14px; height: 14px;
  background-color: rgba(255, 255, 255, 0.92);
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round'><line x1='4' y1='4' x2='12' y2='12'/><line x1='12' y1='4' x2='4' y2='12'/></svg>") center / contain no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round'><line x1='4' y1='4' x2='12' y2='12'/><line x1='12' y1='4' x2='4' y2='12'/></svg>") center / contain no-repeat;
  position: relative;
  z-index: 1;
}
/* drawer-close перепрофилирован в тоггл уведомлений — рисуем колокольчик вместо ✕.
   Цвет наследуется от базовых правил (белый в тёмной glass, тёмный в светлой). */
:root.theme-glass #drawer-close::before {
  width: 16px; height: 16px;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><path d='M10 3a4.5 4.5 0 0 0-4.5 4.5c0 4-1.7 5.2-1.7 5.2h12.4s-1.7-1.2-1.7-5.2A4.5 4.5 0 0 0 10 3Z'/><path d='M8.4 16a1.8 1.8 0 0 0 3.2 0'/></svg>") center / contain no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><path d='M10 3a4.5 4.5 0 0 0-4.5 4.5c0 4-1.7 5.2-1.7 5.2h12.4s-1.7-1.2-1.7-5.2A4.5 4.5 0 0 0 10 3Z'/><path d='M8.4 16a1.8 1.8 0 0 0 3.2 0'/></svg>") center / contain no-repeat;
}
/* Колокольчик — единственная видимая иконка кнопки. Прячем настоящий <svg>
   (он невидим из-за color:transparent, но занимал место во флексе и сдвигал
   ::before-колокольчик влево от центра). Теперь ::before центрируется один. */
:root.theme-glass #drawer-close > svg { display: none; }
/* Бейдж-счётчик «как у рутин»: колокольчик в центре круга, а число —
   маленьким accent-кружком торчит из правого-верхнего края. overflow:visible
   нужен, чтобы круг кнопки не обрезал бейдж; ripple на #drawer-close не
   вешается (его нет в RIPPLE_*_SEL), так что прятать оверфлоу незачем.
   Сами overflow:visible и absolute-бейдж выставлены НИЖЕ — после ripple-групп
   (10602/10612), иначе те перебьют (равная/выше специфичность). */
:root.theme-glass #drawer-notif-dot {
  top: -4px; right: -5px;
  min-width: 15px; height: 15px;
  padding: 0 4px;
  font-size: 9.5px;
  line-height: 15px;
}
/* В шапках экранов (settings/routines/tech и т.п.) — чуть компактнее. */
:root.theme-glass .settings-header .icon-btn[aria-label="Закрыть"] {
  width: 32px; height: 32px; min-width: 32px; min-height: 32px;
}

/* Ripple-хосты в модалке/фильтре: ловят ripple от глобального делегата. */
:root.theme-glass .icon-btn[aria-label="Закрыть"],
:root.theme-glass #drawer-close,
:root.theme-glass #modal-close,
:root.theme-glass .modal-body .msg-react-bar .msg-react-btn,
:root.theme-glass .modal-body > button,
:root.theme-glass .fav-react-chip {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
:root.theme-glass .icon-btn[aria-label="Закрыть"] > *:not(.ripple),
:root.theme-glass #drawer-close > *:not(.ripple),
:root.theme-glass #modal-close > *:not(.ripple),
:root.theme-glass .modal-body .msg-react-bar .msg-react-btn > *:not(.ripple),
:root.theme-glass .modal-body > button > *:not(.ripple),
:root.theme-glass .fav-react-chip > *:not(.ripple) {
  position: relative;
  z-index: 1;
}

/* #drawer-close (тоггл уведомлений) ripple НЕ ловит — значит overflow:hidden и
   position:relative из ripple-групп выше ему только вредят: первый обрезает
   бейдж кругом, второй роняет число в флекс-поток (встаёт сбоку, а не в угол).
   Возвращаем угловой absolute-бейдж «как у .routine-unread-badge»: число
   accent-кружком торчит из правого-верхнего угла. Селектор с двумя id бьёт
   групповое `#drawer-close > *:not(.ripple)` по специфичности. */
:root.theme-glass #drawer-close { overflow: visible; }
:root.theme-glass #drawer-close > #drawer-notif-dot {
  position: absolute;
  top: -4px;
  right: -4px;
  z-index: 2;
}

/* Реакции в action sheet — круглые glass-пилюли с минималистичной линейной иконкой. */
:root.theme-glass .modal-body .msg-react-bar { gap: 8px; }
:root.theme-glass .modal-body .msg-react-bar .msg-react-btn {
  flex: 0 0 36px;
  width: 36px;
  height: 36px;
  padding: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.05);
  -webkit-backdrop-filter: blur(12px) saturate(150%);
  backdrop-filter: blur(12px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, 0.10);
  color: rgba(255, 255, 255, 0.92);
  display: inline-flex; align-items: center; justify-content: center;
}
:root.theme-glass .modal-body .msg-react-bar .msg-react-btn.active {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
  color: #ffffff;
}
:root.theme-glass .modal-body .msg-react-bar .msg-react-btn svg { display: none; }
:root.theme-glass .modal-body .msg-react-bar .msg-react-btn::before {
  content: "";
  width: 18px; height: 18px;
  background-color: currentColor;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain; mask-size: contain;
}

/* Иконки реакций (общий генератор glass): mask-image по data-rid. */
:root.theme-glass .msg-react-btn[data-rid="star"]::before,
:root.theme-glass .fav-react-chip[data-rid="star"]::before,
:root.theme-glass .msg-reaction[data-rid="star"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round'><path d='M8 1.8l1.85 4.05 4.45.5-3.35 3.05.95 4.4L8 11.55 4.1 13.8l.95-4.4L1.7 6.35l4.45-.5z'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round'><path d='M8 1.8l1.85 4.05 4.45.5-3.35 3.05.95 4.4L8 11.55 4.1 13.8l.95-4.4L1.7 6.35l4.45-.5z'/></svg>");
}
:root.theme-glass .msg-react-btn[data-rid="like"]::before,
:root.theme-glass .fav-react-chip[data-rid="like"]::before,
:root.theme-glass .msg-reaction[data-rid="like"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M3 8h2.2v6H3zM5.2 8l2.7-5c.4-.7 1.5-.5 1.6.4l.4 2.4h3.6c.7 0 1.2.7 1.1 1.4l-.7 4.5c-.1.7-.7 1.3-1.4 1.3H5.2'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M3 8h2.2v6H3zM5.2 8l2.7-5c.4-.7 1.5-.5 1.6.4l.4 2.4h3.6c.7 0 1.2.7 1.1 1.4l-.7 4.5c-.1.7-.7 1.3-1.4 1.3H5.2'/></svg>");
}
:root.theme-glass .msg-react-btn[data-rid="fire"]::before,
:root.theme-glass .fav-react-chip[data-rid="fire"]::before,
:root.theme-glass .msg-reaction[data-rid="fire"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M8 1.6s2.2 2.8 2.2 4.8c0 .8.4.8.4.8s1.4-1.5 1.4-1.5c1.4 1.4 2.5 2.9 2.5 4.8 0 2.7-2.3 4.8-6.5 4.8S1.5 13.2 1.5 10.5c0-2.3 2.5-4.3 2.5-5.7'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M8 1.6s2.2 2.8 2.2 4.8c0 .8.4.8.4.8s1.4-1.5 1.4-1.5c1.4 1.4 2.5 2.9 2.5 4.8 0 2.7-2.3 4.8-6.5 4.8S1.5 13.2 1.5 10.5c0-2.3 2.5-4.3 2.5-5.7'/></svg>");
}
:root.theme-glass .msg-react-btn[data-rid="bulb"]::before,
:root.theme-glass .fav-react-chip[data-rid="bulb"]::before,
:root.theme-glass .msg-reaction[data-rid="bulb"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M8 1.8a4.3 4.3 0 0 0-2.4 7.9v1.8c0 .3.2.5.5.5h3.8c.3 0 .5-.2.5-.5V9.7A4.3 4.3 0 0 0 8 1.8zM6.2 13.5h3.6M6.7 14.6h2.6'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M8 1.8a4.3 4.3 0 0 0-2.4 7.9v1.8c0 .3.2.5.5.5h3.8c.3 0 .5-.2.5-.5V9.7A4.3 4.3 0 0 0 8 1.8zM6.2 13.5h3.6M6.7 14.6h2.6'/></svg>");
}
:root.theme-glass .msg-react-btn[data-rid="q"]::before,
:root.theme-glass .fav-react-chip[data-rid="q"]::before,
:root.theme-glass .msg-reaction[data-rid="q"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M5.6 5.8C5.6 4 6.6 3 8 3s2.5 1 2.5 2.5c0 1.6-2.5 1.9-2.5 3.7'/><circle cx='8' cy='12.2' r='.8' fill='black' stroke='none'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M5.6 5.8C5.6 4 6.6 3 8 3s2.5 1 2.5 2.5c0 1.6-2.5 1.9-2.5 3.7'/><circle cx='8' cy='12.2' r='.8' fill='black' stroke='none'/></svg>");
}
:root.theme-glass .msg-react-btn[data-rid="check"]::before,
:root.theme-glass .fav-react-chip[data-rid="check"]::before,
:root.theme-glass .msg-reaction[data-rid="check"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.7' stroke-linejoin='round' stroke-linecap='round'><path d='M3 8.5l3.5 3.5L13 5'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.7' stroke-linejoin='round' stroke-linecap='round'><path d='M3 8.5l3.5 3.5L13 5'/></svg>");
}
:root.theme-glass .msg-react-btn[data-rid="heart"]::before,
:root.theme-glass .fav-react-chip[data-rid="heart"]::before,
:root.theme-glass .msg-reaction[data-rid="heart"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M8 13.7s-5.2-3-5.2-7c0-1.9 1.5-3 3-3 1 0 1.7.5 2.2 1.2.5-.7 1.2-1.2 2.2-1.2 1.5 0 3 1.1 3 3 0 4-5.2 7-5.2 7z'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M8 13.7s-5.2-3-5.2-7c0-1.9 1.5-3 3-3 1 0 1.7.5 2.2 1.2.5-.7 1.2-1.2 2.2-1.2 1.5 0 3 1.1 3 3 0 4-5.2 7-5.2 7z'/></svg>");
}
:root.theme-glass .msg-react-btn[data-rid="down"]::before,
:root.theme-glass .fav-react-chip[data-rid="down"]::before,
:root.theme-glass .msg-reaction[data-rid="down"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M13 8h-2.2V2H13zM10.8 8L8.1 13c-.4.7-1.5.5-1.6-.4l-.4-2.4H2.5c-.7 0-1.2-.7-1.1-1.4l.7-4.5C2.2 3.6 2.8 3 3.5 3h7.3'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M13 8h-2.2V2H13zM10.8 8L8.1 13c-.4.7-1.5.5-1.6-.4l-.4-2.4H2.5c-.7 0-1.2-.7-1.1-1.4l.7-4.5C2.2 3.6 2.8 3 3.5 3h7.3'/></svg>");
}
:root.theme-glass .msg-react-btn[data-rid="eye"]::before,
:root.theme-glass .fav-react-chip[data-rid="eye"]::before,
:root.theme-glass .msg-reaction[data-rid="eye"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M1.5 8s2.5-4.5 6.5-4.5S14.5 8 14.5 8s-2.5 4.5-6.5 4.5S1.5 8 1.5 8z'/><circle cx='8' cy='8' r='2.2'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M1.5 8s2.5-4.5 6.5-4.5S14.5 8 14.5 8s-2.5 4.5-6.5 4.5S1.5 8 1.5 8z'/><circle cx='8' cy='8' r='2.2'/></svg>");
}
:root.theme-glass .msg-react-btn[data-rid="rocket"]::before,
:root.theme-glass .fav-react-chip[data-rid="rocket"]::before,
:root.theme-glass .msg-reaction[data-rid="rocket"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M8 1.5c2 2 3 4.2 3 6.5v3l-2 1.5h-2L5 11V8c0-2.3 1-4.5 3-6.5zM5 11l-2 2.2v.8l2-.8M11 11l2 2.2v.8l-2-.8'/><circle cx='8' cy='6.5' r='1.1'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M8 1.5c2 2 3 4.2 3 6.5v3l-2 1.5h-2L5 11V8c0-2.3 1-4.5 3-6.5zM5 11l-2 2.2v.8l2-.8M11 11l2 2.2v.8l-2-.8'/><circle cx='8' cy='6.5' r='1.1'/></svg>");
}
:root.theme-glass .msg-react-btn[data-rid="clock"]::before,
:root.theme-glass .fav-react-chip[data-rid="clock"]::before,
:root.theme-glass .msg-reaction[data-rid="clock"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><circle cx='8' cy='8' r='6'/><path d='M8 4.5V8l2.5 2'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><circle cx='8' cy='8' r='6'/><path d='M8 4.5V8l2.5 2'/></svg>");
}

/* Кнопки действий: скрыть пиксельный .menu-ico и вывести линейные иконки. */
:root.theme-glass .modal-body > button[data-mact],
:root.theme-glass .modal-body > button[data-pick] { padding-left: 14px; }
:root.theme-glass .modal-body > button[data-mact] .menu-ico,
:root.theme-glass .modal-body > button[data-pick] .menu-ico,
:root.theme-glass .modal-body > label.modal-pick[data-pick] .menu-ico { display: none; }
:root.theme-glass .modal-body > button[data-mact]::before {
  content: "";
  display: inline-block;
  vertical-align: -10px;
  width: 28px; height: 28px;
  margin-right: 10px;
  border-radius: 50%;
  background-color: rgba(255, 255, 255, 0.05);
  background-position: center;
  background-repeat: no-repeat;
  background-size: 16px 16px;
  -webkit-backdrop-filter: blur(12px) saturate(150%);
  backdrop-filter: blur(12px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, 0.10);
}
:root.theme-glass .modal-body > button[data-mact="pin"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(255,255,255,0.92)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M5 3h6M6.5 3v3L4.5 8.5h7L9.5 6V3M8 8.5v5.5'/></svg>");
}
:root.theme-glass .modal-body > button[data-mact="unpin"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(255,255,255,0.92)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M5 3h6M6.5 3v3L4.5 8.5h7L9.5 6V3M8 8.5v5.5M2 14L14 2'/></svg>");
}
:root.theme-glass .modal-body > button[data-mact="reply"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(255,255,255,0.92)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M6.5 4.5L3 8l3.5 3.5M3 8h6.5c1.7 0 3 1.3 3 3v1.5'/></svg>");
}
:root.theme-glass .modal-body > button[data-mact="favorite"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(255,255,255,0.92)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M8 13.5S3 10.6 3 6.6c0-1.9 1.4-3 2.9-3 .9 0 1.6.5 2.1 1.1.5-.6 1.2-1.1 2.1-1.1 1.5 0 2.9 1.1 2.9 3 0 4-5 6.9-5 6.9z'/></svg>");
}
:root.theme-glass .modal-body > button[data-mact="to_input"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(255,255,255,0.92)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M8 2.5v7.5M5 7l3 3 3-3M2.5 13.5h11'/></svg>");
}
:root.theme-glass .modal-body > button[data-mact="new_thread"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(255,255,255,0.92)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M2.5 3.5a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1V10a1 1 0 0 1-1 1H6l-3 2.5V11a1 1 0 0 1-1-1z'/><path d='M8 5v4M6 7h4'/></svg>");
}
:root.theme-glass .modal-body > button[data-mact="copy"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(255,255,255,0.92)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><rect x='5.5' y='5.5' width='7.5' height='8' rx='1'/><path d='M3 10.5V3.5a1 1 0 0 1 1-1h6'/></svg>");
}
:root.theme-glass .modal-body > button[data-mact="delete_fav"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(255,255,255,0.92)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M3 4.5h10M6 4.5V3h4v1.5M4.5 4.5l.6 9.1c0 .5.5.9 1 .9h3.8c.5 0 .9-.4 1-.9l.6-9.1M6.7 7v5M9.3 7v5'/></svg>");
}
:root.theme-glass .modal-body > button[data-mact="cancel"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(255,255,255,0.92)' stroke-width='1.6' stroke-linejoin='round' stroke-linecap='round'><path d='M4.5 4.5l7 7M11.5 4.5l-7 7'/></svg>");
}

/* Фильтр-чипы реакций над инпутом (#fav-react-row): glass круглые, иконочные.
   Параметры подложки (bg/border/blur/shadow) совпадают с .chip — реакции
   читаются как часть одной системы, отличаясь только формой (круг под иконку
   вместо пилюли под текст). */
:root.theme-glass #fav-react-row { gap: 8px; }
:root.theme-glass .fav-react-chip {
  --bg-mix: 0;
  width: 34px; height: 34px;
  border-radius: 50%;
  background: color-mix(in srgb, rgba(255, 255, 255, 0.03) calc((1 - var(--bg-mix)) * 100%), rgba(0, 0, 0, 0.40));
  -webkit-backdrop-filter: blur(6px) saturate(160%);
  backdrop-filter: blur(6px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: color-mix(in srgb, var(--fg) calc((1 - var(--bg-mix)) * 100%), #ffffff);
  box-shadow:
    0 8px 14px -6px rgba(0, 0, 0, 0.30),
    inset 0 1px 0 rgba(255, 255, 255, calc(0.10 + var(--bg-mix) * 0.12)),
    inset 0 -2px 6px rgba(0, 0, 0, calc(var(--bg-mix) * 0.18));
  transition: background 0.08s linear, color 0.08s linear, box-shadow 0.08s linear;
}
:root.theme-glass .fav-react-chip.active {
  /* Активный чип («все» по умолчанию): на тёмном фоне — обычный accent-tint,
     на синем баббле — переходим в тёмную плашку как остальные чипы ряда. */
  background: color-mix(in srgb, color-mix(in srgb, var(--accent) 22%, transparent) calc((1 - var(--bg-mix)) * 100%), rgba(0, 0, 0, 0.40)) !important;
  border-color: color-mix(in srgb, color-mix(in srgb, var(--accent) 60%, transparent) calc((1 - var(--bg-mix)) * 100%), rgba(255, 255, 255, 0.14));
  color: #ffffff;
}
:root.theme-glass .fav-react-chip svg { display: none; }
:root.theme-glass .fav-react-chip[data-rid]::before {
  content: "";
  width: 18px; height: 18px;
  background-color: currentColor;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain; mask-size: contain;
}

/* Бейдж реакции на сообщении: glass-чип с линейной иконкой вместо пиксельной. */
:root.theme-glass .msg-reaction {
  background: rgba(255, 255, 255, 0.06);
  -webkit-backdrop-filter: blur(8px) saturate(160%);
  backdrop-filter: blur(8px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  padding: 3px 6px;
  color: rgba(255, 255, 255, 0.92);
  display: inline-flex; align-items: center; justify-content: center;
}
:root.theme-glass .msg-reaction svg { display: none; }
:root.theme-glass .msg-reaction[data-rid]::before {
  content: "";
  display: block;
  width: 14px; height: 14px;
  background-color: currentColor;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain; mask-size: contain;
}
/* «все» и «без реакций» — текстовые, не пилюли с иконкой. */
:root.theme-glass .fav-react-chip[data-rid="__all__"],
:root.theme-glass .fav-react-chip[data-rid="__none__"] {
  width: auto;
  min-width: 34px;
  padding: 0 12px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
}
:root.theme-glass .fav-react-chip[data-rid="__all__"]::before,
:root.theme-glass .fav-react-chip[data-rid="__none__"]::before,
:root.theme-glass .msg-reaction[data-rid="__none__"]::before {
  display: none;
}

/* Прикреплялка (data-pick): glass line-иконки.
   В дефолтной теме остаются пиксельные .menu-ico как есть. */
:root.theme-glass .modal-body .modal-pick[data-pick]::before {
  content: "";
  display: inline-block;
  vertical-align: -3px;
  width: 16px; height: 16px;
  margin-right: 8px;
  background-color: currentColor;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain; mask-size: contain;
}
:root.theme-glass .modal-body .modal-pick[data-pick="photos"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><rect x='2' y='3' width='12' height='10' rx='1.5'/><circle cx='6' cy='7' r='1.2'/><path d='M14 11l-3.5-3.5L4 13'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><rect x='2' y='3' width='12' height='10' rx='1.5'/><circle cx='6' cy='7' r='1.2'/><path d='M14 11l-3.5-3.5L4 13'/></svg>");
}
:root.theme-glass .modal-body .modal-pick[data-pick="camera"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M2 5.5h2.5L6 3.5h4l1.5 2H14v7H2z'/><circle cx='8' cy='9' r='2.3'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M2 5.5h2.5L6 3.5h4l1.5 2H14v7H2z'/><circle cx='8' cy='9' r='2.3'/></svg>");
}
:root.theme-glass .modal-body .modal-pick[data-pick="folder"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M2 4.5a1 1 0 0 1 1-1h3l1.2 1.5H13a1 1 0 0 1 1 1V12a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1z'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M2 4.5a1 1 0 0 1 1-1h3l1.2 1.5H13a1 1 0 0 1 1 1V12a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1z'/></svg>");
}
:root.theme-glass .modal-body .modal-pick[data-pick="cancel"]::before {
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.6' stroke-linejoin='round' stroke-linecap='round'><path d='M4 4l8 8M12 4l-8 8'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.6' stroke-linejoin='round' stroke-linecap='round'><path d='M4 4l8 8M12 4l-8 8'/></svg>");
}

/* ===== Accent picker (7 цветов от синего к зелёному) ===== */
.row-accent-swatch {
  display: inline-block;
  width: 16px; height: 16px;
  border-radius: 50%;
  vertical-align: -3px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.10);
}
:root.theme-light .row-accent-swatch {
  border-color: rgba(0, 0, 0, 0.18);
}
.accent-picker {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 14px;
  padding: 14px 8px 8px;
}
/* Прямоугольный пиксельный свотч в классических темах. Перебиваем
   .modal-body button { width: 100% } через двойной селектор, иначе
   правило выше делает свотчи на всю ширину модала (овалы при border-radius:50%). */
.accent-picker .accent-swatch {
  width: 44px; height: 44px;
  border-radius: 0;
  border: 2px solid var(--border);
  background: var(--accent);
  cursor: pointer;
  padding: 0;
  position: relative;
  display: inline-block;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.accent-picker .accent-swatch:active { transform: scale(0.92); }
.accent-picker .accent-swatch.active {
  border-color: var(--fg);
  box-shadow: 0 0 0 3px var(--bg), 0 0 0 5px var(--fg);
}
.accent-hint {
  text-align: center;
  font-size: 12px;
  color: var(--fg-dim);
  padding: 8px 16px 4px;
}
:root.theme-glass .accent-picker { padding: 18px 8px 10px; }
:root.theme-glass .accent-picker .accent-swatch {
  border-radius: 50%;
  border-color: rgba(255, 255, 255, 0.18);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
:root.theme-glass .accent-picker .accent-swatch.active {
  border-color: #fff;
  box-shadow: 0 0 0 3px rgba(12, 12, 24, 0.85), 0 0 0 5px rgba(255, 255, 255, 0.92);
}

/* Composer-иконки: outline-вариант (SF Symbols-style) только в glass.
   В дефолтных темах остаётся пиксельный SVG. */
.input-bar-btn .glass-icon { display: none; }
:root.theme-glass .input-bar-btn .classic-icon { display: none; }
:root.theme-glass .input-bar-btn .glass-icon {
  display: inline-block;
  vertical-align: middle;
}

/* В glass — кнопки composer'а круглые, чтобы ripple/active-стейт
   рисовался в кружке. Ширина=высота фиксируется для idealной окружности.
   ripple внутри получает blur — даёт «liquid»-смазанный росчерк. */
:root.theme-glass .input-bar-btn {
  --bg-mix: 0;
  --btn-base-color: var(--fg);
  border-radius: 50% !important;
  width: 32px;
  min-width: 32px;
  height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: none !important;
  /* !important нужен — базовое правило `.input-bottom-bar > #attach-btn`
     (id-селектор) ставит color: var(--fg-dim) и без !important побеждает. */
  color: color-mix(in srgb, var(--btn-base-color) calc((1 - var(--bg-mix)) * 100%), #ffffff) !important;
  transition: color 0.08s linear;
}
/* Дублируем для каждого id-кнопки — id-селектор сильнее class'а, поэтому
   общего правила выше может не хватить в edge cases (Safari иногда теряет
   !important у inherited custom-property). */
:root.theme-glass .input-bottom-bar > #attach-btn,
:root.theme-glass .input-bottom-bar > #paste-input-btn,
:root.theme-glass .input-bottom-bar > #clear-input-btn,
:root.theme-glass .input-bottom-bar > #copy-input-btn,
:root.theme-glass .input-bottom-bar > #voice-mode-btn,
:root.theme-glass .input-bottom-bar > #mic-btn {
  color: color-mix(in srgb, var(--btn-base-color, var(--fg)) calc((1 - var(--bg-mix, 0)) * 100%), #ffffff) !important;
}
/* Плавная инверсия для кнопки отправки — тот же тайминг, что и у chip/иконок */
:root.theme-glass .input-bar-btn-send {
  --bg-mix: 0;
  --btn-base-color: var(--fg);
  color: color-mix(in srgb, var(--btn-base-color) calc((1 - var(--bg-mix)) * 100%), #ffffff);
  transition: color 0.08s linear;
}
/* SVG-наполнение кнопок (paperclip, mic, copy и пр.) тоже плавно меняет цвет.
   В WebKit currentColor резолвится при paint, но если SVG композитится в
   отдельный слой (isolation: isolate на хосте), interpolation между двумя
   currentColor-значениями может терять плавность. Принудительно ставим
   fill/stroke = currentColor как CSS-свойства на inner-элементах и даём
   им свой transition — тогда WebKit интерполирует именно эти свойства,
   а не attribute-render. */
:root.theme-glass .input-bar-btn svg,
:root.theme-glass .input-bar-btn-send svg {
  transition: color 0.08s linear,
              fill 0.08s linear,
              stroke 0.08s linear;
}
:root.theme-glass .input-bar-btn svg path,
:root.theme-glass .input-bar-btn svg polyline,
:root.theme-glass .input-bar-btn svg polygon,
:root.theme-glass .input-bar-btn svg circle,
:root.theme-glass .input-bar-btn svg rect,
:root.theme-glass .input-bar-btn svg line,
:root.theme-glass .input-bar-btn-send svg path,
:root.theme-glass .input-bar-btn-send svg polyline,
:root.theme-glass .input-bar-btn-send svg polygon,
:root.theme-glass .input-bar-btn-send svg circle,
:root.theme-glass .input-bar-btn-send svg rect,
:root.theme-glass .input-bar-btn-send svg line {
  stroke: currentColor;
  transition: fill 0.18s ease,
              stroke 0.18s ease;
}
/* Маскот send-кнопки — пиксельный спрайт, должен быть константно белым на
   синем круге. Базовое правило выше ставит stroke: currentColor на все
   rect внутри .input-bar-btn-send, а currentColor зависит от data-bg
   (инверсии): когда вычислится "светлый фон", stroke становится тёмным
   и обводит каждую пиксельную клетку мелкими тёмными линиями — маскот
   выглядит грязным. Маскот не должен реагировать на инверсии. */
:root.theme-glass .send-mascot,
:root.theme-glass .send-mascot * {
  transition: none !important;
}
:root.theme-glass .send-mascot rect {
  stroke: none !important;
  fill: #ffffff !important;
}
/* v949-fix: в светлой Glass маскот собран из <rect>'ов с захардкоженным
   белым выше — на светлой подложке rgba(0,0,0,0.10) он сливается с фоном.
   Перекрываем именно <rect>'ы акцентным цветом (предыдущая правка трогала
   только <g>, а fill на rect выигрывал у наследования от g). */
:root.theme-glass.theme-light .send-mascot rect {
  fill: var(--accent) !important;
}
:root.theme-glass .input-bar-btn > .ripple {
  filter: blur(4px);
  -webkit-filter: blur(4px);
}
/* v1176: Кирилл — «уменьши размеры иконок в поле ввода, нижнем и верхнем меню».
   Жмём только визуальный глиф; тап-таргеты кнопок (32/40px) не трогаем — палец
   попадает как прежде. Строго glass (активная тема Кирилла), light/dark целы. */
:root.theme-glass #topbar > #threads-btn .threads-btn__icon { font-size: 12px; }
:root.theme-glass .drawer-foot-icon .foot-icon-glass svg { width: 17px; height: 17px; }
:root.theme-glass .input-bar-btn .glass-icon { width: 17px; height: 17px; }
:root.theme-glass .input-bottom-bar > #send.input-bar-btn-send .send-mascot { width: 16px; height: 13px; }
/* Глушим 8-bit пиксельную тень и keyframe-пульсацию активной voice-кнопки в glass:
   они рисуют чёрную дугу под круглой кнопкой. */
:root.theme-glass #voice-mode-btn,
:root.theme-glass #voice-mode-btn.active,
:root.theme-glass body.voice-state-listen #voice-mode-btn.active,
:root.theme-glass body.voice-state-think #voice-mode-btn.active,
:root.theme-glass body.voice-state-speak #voice-mode-btn.active {
  animation: none !important;
  box-shadow: none !important;
}

/* Иконки шапки/действий деталки записи (память, файл): чат-бабл «В чат» и
   мусорка «Удалить» — outline-вариант только в glass. */
#memory-edit-insert .glass-icon,
#file-view-insert .glass-icon,
#memory-edit-delete .glass-icon,
#file-view-delete .glass-icon { display: none; }
:root.theme-glass #memory-edit-insert .classic-icon,
:root.theme-glass #file-view-insert .classic-icon,
:root.theme-glass #memory-edit-delete .classic-icon,
:root.theme-glass #file-view-delete .classic-icon { display: none; }
:root.theme-glass #memory-edit-insert .glass-icon,
:root.theme-glass #file-view-insert .glass-icon,
:root.theme-glass #memory-edit-delete .glass-icon,
:root.theme-glass #file-view-delete .glass-icon {
  display: inline-block;
  vertical-align: middle;
}

/* Свайп-иконки треда (закреп/прочитано/бургер/восстановить) и значок-закреп
   в строке pinned-треда + в tech-drawer — outline-вариант только в glass. */
.thread-pin .glass-icon,
.tech-pin .glass-icon,
.thread-pin-act .glass-icon,
.thread-mark-act .glass-icon,
.thread-settings-act .glass-icon,
.thread-restore-act .glass-icon { display: none; }
:root.theme-glass .thread-pin .classic-icon,
:root.theme-glass .tech-pin .classic-icon,
:root.theme-glass .thread-pin-act .classic-icon,
:root.theme-glass .thread-mark-act .classic-icon,
:root.theme-glass .thread-settings-act .classic-icon,
:root.theme-glass .thread-restore-act .classic-icon { display: none; }
:root.theme-glass .thread-pin .glass-icon,
:root.theme-glass .tech-pin .glass-icon,
:root.theme-glass .thread-pin-act .glass-icon,
:root.theme-glass .thread-mark-act .glass-icon,
:root.theme-glass .thread-settings-act .glass-icon,
:root.theme-glass .thread-restore-act .glass-icon {
  display: inline-block;
  vertical-align: middle;
}

/* Иконка «Закрепить» в menu-попапах (сообщение/тред/тех) — outline в glass. */
.menu-ico.glass-icon { display: none; }
:root.theme-glass .menu-ico.classic-icon { display: none; }
:root.theme-glass .menu-ico.glass-icon {
  display: inline-block;
  vertical-align: middle;
}

/* Стрелки прокрутки чата (вверх/вниз в нижнем правом углу) — outline в glass. */
#scroll-up-btn .glass-icon,
#scroll-down-btn .glass-icon { display: none; }
:root.theme-glass #scroll-up-btn .classic-icon,
:root.theme-glass #scroll-down-btn .classic-icon { display: none; }
:root.theme-glass #scroll-up-btn .glass-icon,
:root.theme-glass #scroll-down-btn .glass-icon {
  display: inline-block;
  vertical-align: middle;
}

/* ===== Theme: glass — debricking (убираем «коробки» из шапок и списков) =====
   В дефолтных темах всё остаётся пиксельным/квадратным, в glass — мягкие
   капсулы и круги, как в нативных iOS-настройках. */

/* Кнопки шапки: ⟳ (Обновить) и + (Новая ...) — круглые glass-плашки 32×32. */
:root.theme-glass .settings-header .icon-btn[aria-label="Обновить"],
:root.theme-glass .settings-header #routines-add,
:root.theme-glass .settings-header #tech-add {
  width: 32px; height: 32px; min-width: 32px; min-height: 32px;
  padding: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.06);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  backdrop-filter: blur(14px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, 0.10);
  color: rgba(255, 255, 255, 0.92);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 16px;
  line-height: 1;
}
:root.theme-glass .settings-header #routines-add,
:root.theme-glass .settings-header #tech-add { font-size: 20px; font-weight: 400; }

/* Табы сортировки и фильтра — капсулы. */
:root.theme-glass .routines-sort-btn,
:root.theme-glass .tech-chip {
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.12);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  backdrop-filter: blur(10px) saturate(140%);
}
:root.theme-glass .routines-sort-btn.is-active,
:root.theme-glass .routines-sort-btn.active,
:root.theme-glass .tech-chip.is-active {
  background: rgba(255, 255, 255, 0.10);
  border-color: rgba(255, 255, 255, 0.32);
  color: #fff;
}

/* mtype-chip (фильтр субтипов памяти: user/feedback/project/...) —
   тоже капсула, base даёт border-radius:0 (квадрат). */
:root.theme-glass .tech-mtype-chip {
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.12);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  backdrop-filter: blur(10px) saturate(140%);
}
:root.theme-glass .tech-mtype-chip.is-active {
  background: rgba(255, 255, 255, 0.10);
  border-color: rgba(255, 255, 255, 0.32);
  color: #fff;
}

/* Sort-toggle ⇅ в техническом — круглая плашка, не квадрат. */
:root.theme-glass .tech-sort-toggle {
  width: 32px; height: 32px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.12);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  backdrop-filter: blur(10px) saturate(140%);
  color: rgba(255, 255, 255, 0.92);
}
:root.theme-glass .tech-sort-toggle.is-active {
  background: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.32);
  color: #fff;
}

/* Карточки рутин и записей «Техническое» — мягкие радиусы. */
:root.theme-glass .routine-card,
:root.theme-glass .tech-card {
  border-radius: 18px;
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.10);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  backdrop-filter: blur(10px) saturate(140%);
}

/* В Glass всё должно быть плавно — базовые анимации сворачивания используют
   steps()/display:none, что даёт «хлопок» при collapse. Здесь заменяем на
   гладкие cubic-bezier'ы. Симметрия expand↔collapse обязательна. */

/* routine-card: в базе .routine-body коллапсится через steps(6, end).
   Переопределяем на гладкий cubic-bezier ~520мс — чтобы коллапс читался
   глазом как «складывается», а не «дёрнулось». */
:root.theme-glass .routine-body {
  transition:
    max-height 0.52s cubic-bezier(0.4, 0, 0.2, 1),
    clip-path 0.52s cubic-bezier(0.4, 0, 0.2, 1),
    opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important;
}
/* Шеврон рутины: тоже плавный поворот вместо steps(2). */
:root.theme-glass .routine-chevron {
  transition: transform 0.36s cubic-bezier(0.4, 0, 0.2, 1) !important;
}

/* tool-bubble в glass: только opacity-transition при появлении.
   Раньше тут стоял max-height: 600px + overflow: hidden + transition по
   max-height/padding/margin/border-width — это нужно было для плавного
   collapse через max-height: 0. После фикса v922 collapse делается через
   display:none у :not(:last-child) (см. 9794), max-height-transition больше
   не работает. Но `max-height: 600px + overflow: hidden` на видимом last-child
   давал WK-баг: bubble подрезалась до тонкой полоски в 1-2px (видны только
   нижние пиксели букв READ/EDIT/GREP), в ретро того же не было — там нет этого
   правила. Снимаем max-height/overflow полностью; transition оставляем только
   на opacity (для msgInFade и плавных появлений). Инцидент 2026-05-27. */
:root.theme-glass .tool-group > .msg.tool {
  transition: opacity 0.34s cubic-bezier(0.4, 0, 0.2, 1);
}
/* v1175: выравнивание зелёных надписей плашек по акцентным (МАККОД). В glass у
   .msg.bot снят бордюр (border:none !important выше), и надпись бота встаёт на
   padding-left 10px. У .msg.tool зелёный border-left 3px остаётся → текст был на
   13px (на 3px правее). Срезаем левый паддинг до 7px: 3(border)+7 = 10px — зелёная
   надпись ложится РОВНО под акцентную. Кирилл: «зелёные выровни по акцентным». */
:root.theme-glass .msg.tool {
  padding-left: 7px;
}
/* Свёрнутая tool-group: переопределяем `display: contents` на flex-column
   с gap: 0. Иначе скрытые `.msg.tool` остаются flex-items в #chat (через
   contents) и каждое подтягивает gap: 10px родителя — получается ~100px
   пустоты между ответами Маккода, разделёнными цепочкой тулз. */
:root.theme-glass .tool-group:not(.expanded):not(.single) {
  display: flex;
  flex-direction: column;
  gap: 0;
  position: relative;
}
/* not-last-child в коллапсе — полностью убираем из layout. Раньше делали
   max-height: 0 ради плавного collapse, но WK на iOS иногда оставляет
   огрызок (видны нижние пиксели букв `READ`/`EDIT` пунктиром между
   Маккод-сообщениями) — paint происходит до того, как layout зафиксирует 0px.
   display:none надёжно: элемент не существует, артефакта нет. */
:root.theme-glass .tool-group:not(.expanded):not(.single) > .msg.tool:not(:last-child) {
  display: none;
}
/* .tool-exiting перебивает display:none выше — старая tool-карточка в
   момент смены остаётся в layout как position:absolute (поверх новой),
   гладко тает opacity. Без этого новая команда возникала мгновенно:
   старая сразу схлопывалась в display:none, ничем не плавно. JS снимает
   класс через ~460ms — после этого срабатывает базовое display:none. */
:root.theme-glass .tool-group:not(.expanded):not(.single) > .msg.tool.tool-exiting:not(:last-child) {
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  pointer-events: none;
  animation: glass-tool-fade-out 0.42s cubic-bezier(0.4, 0, 0.2, 1) both;
  z-index: 1;
}
@keyframes glass-tool-fade-out {
  0%   { opacity: 1; }
  100% { opacity: 0; }
}

/* thinking-body: в базе display:none — резкий обрыв. В Glass — max-height fade. */
:root.theme-glass .msg.thinking .body {
  display: block;
  overflow: hidden;
  max-height: 9999px;
  opacity: 1;
  transition:
    max-height 0.44s cubic-bezier(0.4, 0, 0.2, 1),
    opacity 0.32s cubic-bezier(0.4, 0, 0.2, 1);
}
:root.theme-glass .msg.thinking.collapsed .body {
  display: block;
  max-height: 0;
  opacity: 0;
}
/* Шеврон thinking — тоже плавный поворот. */
:root.theme-glass .msg.thinking .role-chev,
:root.theme-glass .msg.tool .role-chev {
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
}

/* Статус-маркеры сервисов (на главной и в «Сервисы и скиллы») — кружки. */
:root.theme-glass .start-health-dot,
:root.theme-glass .routine-status-dot {
  border-radius: 50%;
}

/* Бэйджи прогонов рутин («ок», «споткнулся», «exit N») — капсулы. */
:root.theme-glass .routine-run-tag {
  border-radius: 999px;
  padding: 2px 8px;
}

/* Поле «Поиск» в шапке «Техническое» — капсула с лёгким glass-фоном. */
:root.theme-glass .tech-search-bar {
  padding: 8px 12px;
  border-bottom-color: rgba(255, 255, 255, 0.06);
  background: transparent;
}
:root.theme-glass .tech-search-bar .thread-search-input {
  background: rgba(255, 255, 255, 0.05) !important;
  border: 1px solid rgba(255, 255, 255, 0.10) !important;
  border-radius: 999px !important;
  padding: 6px 12px !important;
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  backdrop-filter: blur(10px) saturate(140%);
}

/* Шапка деталки (память/файл): кнопка-иконка «В чат» — круглая glass-плашка
   как и остальные шапочные кнопки. */
:root.theme-glass #memory-edit-insert,
:root.theme-glass #file-view-insert {
  width: 32px; height: 32px; min-width: 32px; min-height: 32px;
  padding: 0;
  border-radius: 50%;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: rgba(255, 255, 255, 0.92);
  display: inline-flex; align-items: center; justify-content: center;
}

/* Бэйдж-крошка «ПАМЯТЬ / КОНФИГИ / СКИЛЛЫ / MCP / CLI» — капсула. */
:root.theme-glass .file-view-crumb {
  border-radius: 999px;
  padding: 4px 12px;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.24);
  color: rgba(255, 255, 255, 0.82);
  letter-spacing: 0.04em;
}
:root.theme-glass .file-view-crumb:active {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.38);
  color: #fff;
}
/* Светлый Glass — фон бирюзовый, белая надпись «ПАМЯТЬ/КОНФИГИ/…» сливалась.
   Прозрачный фон + тёмный бордер и тёмный текст, чтоб крошка читалась
   на бежевой подложке. */
:root.theme-glass.theme-light .file-view-crumb {
  background: transparent;
  border-color: rgba(0, 0, 0, 0.28);
  color: rgba(0, 0, 0, 0.78);
}
:root.theme-glass.theme-light .file-view-crumb:active {
  background: rgba(0, 0, 0, 0.06);
  border-color: rgba(0, 0, 0, 0.42);
  color: #000;
}

/* Кнопки действий деталки (✓ сохранить, 🗑 удалить) — круглые плашки. */
:root.theme-glass .memory-edit-action-btn,
:root.theme-glass .file-view-action-btn {
  width: 36px; height: 36px; min-width: 36px; min-height: 36px;
  padding: 0;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 16px;
  line-height: 1;
}

/* × Закрыть на деталке (память/файл) — прозрачный фон, бордер ярче,
   крестик читаем. Скоупим к ID, чтоб не сломать модалку/дровер. */
:root.theme-glass #memory-edit-close,
:root.theme-glass #file-view-close {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.22);
}
:root.theme-glass #memory-edit-close::before,
:root.theme-glass #file-view-close::before {
  background-color: rgba(255, 255, 255, 0.92);
}
:root.theme-glass #memory-edit-save,
:root.theme-glass #file-view-save {
  background: transparent;
  border: 1px solid rgba(140, 190, 255, 0.55);
  color: #cfe2ff;
}
:root.theme-glass #memory-edit-save:active,
:root.theme-glass #file-view-save:active {
  background: rgba(91, 156, 255, 0.18);
  border-color: rgba(170, 210, 255, 0.75);
}
:root.theme-glass #memory-edit-delete,
:root.theme-glass #file-view-delete {
  background: transparent;
  border: 1px solid rgba(240, 130, 120, 0.55);
  color: rgba(255, 200, 195, 0.95);
}
:root.theme-glass #memory-edit-delete:active,
:root.theme-glass #file-view-delete:active {
  background: rgba(220, 90, 80, 0.18);
  border-color: rgba(250, 160, 150, 0.75);
}

/* Форма «Новая рутина» — поля, чипы пресетов cron и кнопки. */
:root.theme-glass .routine-new-form {
  border-radius: 20px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.10);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  backdrop-filter: blur(14px) saturate(150%);
}
:root.theme-glass .routine-cron-input,
:root.theme-glass .routine-textarea {
  border-radius: 12px !important;
  background: rgba(255, 255, 255, 0.05) !important;
  border: 1px solid rgba(255, 255, 255, 0.10) !important;
  color: rgba(255, 255, 255, 0.92) !important;
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  backdrop-filter: blur(8px) saturate(140%);
}
:root.theme-glass .routine-cron-input:focus,
:root.theme-glass .routine-textarea:focus {
  background: rgba(255, 255, 255, 0.08) !important;
  border-color: rgba(255, 255, 255, 0.22) !important;
  outline: none;
}
:root.theme-glass .routine-chip {
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.12);
  color: rgba(255, 255, 255, 0.88);
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  backdrop-filter: blur(8px) saturate(140%);
}
:root.theme-glass .routine-chip:active {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.28);
}
:root.theme-glass .routine-chat,
:root.theme-glass .routine-save {
  border-radius: 999px;
  padding: 8px 18px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: rgba(255, 255, 255, 0.92);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  backdrop-filter: blur(10px) saturate(140%);
}
:root.theme-glass .routine-save {
  background: rgba(91, 156, 255, 0.16);
  border-color: rgba(140, 190, 255, 0.40);
  color: #cfe2ff;
}
:root.theme-glass .routine-chat:active {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.30);
}
:root.theme-glass .routine-save:active {
  background: rgba(91, 156, 255, 0.26);
  border-color: rgba(170, 210, 255, 0.60);
}

/* Форма «Память» / «Файл» — текстовые инпуты, textarea, дроп «Тип». */
:root.theme-glass .memory-edit-input,
:root.theme-glass .memory-edit-textarea,
:root.theme-glass .file-view-textarea,
:root.theme-glass .mem-type-trigger {
  border-radius: 12px !important;
  background: rgba(255, 255, 255, 0.05) !important;
  border: 1px solid rgba(255, 255, 255, 0.10) !important;
  color: rgba(255, 255, 255, 0.92) !important;
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  backdrop-filter: blur(8px) saturate(140%);
}
:root.theme-glass .memory-edit-input:focus,
:root.theme-glass .memory-edit-textarea:focus,
:root.theme-glass .file-view-textarea:focus {
  background: rgba(255, 255, 255, 0.08) !important;
  border-color: rgba(255, 255, 255, 0.22) !important;
  outline: none;
}
:root.theme-glass .mem-type-list {
  border-radius: 14px;
  background: rgba(20, 20, 30, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.12);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  backdrop-filter: blur(20px) saturate(160%);
  overflow: hidden;
}

/* Поповер выбора чипов (model / effort / view) над инпут-баром — glass-плашка.
   Параметры синхронизированы с #composer и #topbar-menu: полупрозрачный
   чёрный фон + blur, чтобы под плашкой было реальное размытие. */
:root.theme-glass #chip-popover,
:root.theme-glass #slash-suggest {
  border-radius: 16px;
  /* v1093: «в том же стиле, что топбар-меню» — мэтчим тон и блюр топбара
     (saturate 180% / blur 60). Альфа чуть плотнее топбара: поповер сидит в
     backdrop-root композера, и на iOS блюр может не дотянуться до чата —
     повышенная непрозрачность глушит резкий просвет, если фрост не сработал. */
  background: rgba(13, 13, 13, 0.40);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
  -webkit-backdrop-filter: saturate(180%) blur(60px);
  backdrop-filter: saturate(180%) blur(60px);
  overflow-x: hidden;
  overflow-y: auto;
}
:root.theme-glass #chip-popover .slash-suggest-row,
:root.theme-glass #slash-suggest .slash-suggest-row {
  /* v1093: скруглённый «островок» со скользящим фоном вместо жёсткого
     разделителя — зеркалит .topbar-menu-row (border-radius + margin + transition). */
  border-bottom: none;
  border-radius: 12px;
  margin: 2px 6px;
  transition: background 170ms cubic-bezier(0.4, 0, 0.2, 1);
}
:root.theme-glass #chip-popover .slash-suggest-row.active,
:root.theme-glass #slash-suggest .slash-suggest-row.active {
  background: rgba(255, 255, 255, 0.13);
}
:root.theme-glass #chip-popover .slash-suggest-row:hover,
:root.theme-glass #slash-suggest .slash-suggest-row:hover {
  background: rgba(255, 255, 255, 0.07);
}
/* v1038: «откажись от серого в шрифте» — описания пунктов выпадашки (effort/
   model/view) делаем читаемыми --fg вместо приглушённого серого. */
:root.theme-glass #chip-popover .slash-suggest-desc {
  color: var(--fg);
}

/* v1039: в glass-теме базовая степовая (8-bit, clip-path/steps) анимация
   поповеров нижних чипов (модель/effort/view) и slash смотрится резко.
   Заменяем на плавное появление/сворачивание — opacity + лёгкий подъём
   снизу, дефолтный glass-easing. Ретро-тему не трогаем (там steps остаётся).
   Закрытие фронт ждёт по animationend, так что длительность тут свободна. */
:root.theme-glass #chip-popover,
:root.theme-glass #slash-suggest {
  /* v1093: плашка-фрост НЕ должна промоутиться в композ-слой (will-change /
     transform / scale) — иначе iOS WK морозит снимок блюра (так зависал топбар
     в 1090). Анимируем ТОЛЬКО opacity, will-change снимаем: на покое плашка
     де-промоучена, блюр живой. Живость входа/выхода — на opacity-фейде, без сдвига. */
  animation: chipGlassOpen 220ms cubic-bezier(0.4, 0, 0.2, 1);
  will-change: auto;
}
:root.theme-glass #chip-popover.closing,
:root.theme-glass #slash-suggest.closing {
  animation: chipGlassClose 180ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
@keyframes chipGlassOpen {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes chipGlassClose {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* ============================================================
   GLASS — LIGHT MODE
   style=glass + mode=light: тот же эффект жидкого стекла,
   но на светлой подложке. Полупрозрачные слои становятся
   тёмными rgba(0,0,0,X), фон композера/драуэра/модалок —
   светлый rgba(255,255,255,X) поверх off-white подложки.
   ============================================================ */
:root.theme-glass.theme-light {
  --bg: #f5f3ee;
  --bg-soft: rgba(0, 0, 0, 0.04);
  --bg-card: rgba(0, 0, 0, 0.05);
  --fg: #2a2a2a;
  --fg-dim: #6e6e6e;
  --accent: #2a73e3;
  --accent-2: #4a8df0;
  --accent-dim: rgba(0, 0, 0, 0.40);
  --accent-dim-2: rgba(0, 0, 0, 0.25);
  --green: #30a050;
  --red: #d83a30;
  --border: rgba(0, 0, 0, 0.10);
  --scroll-thumb: rgba(0, 0, 0, 0.20);
  --user-bg: #1f4d8a;
  --user-fg: #ffffff;
  --code-bg: rgba(0, 0, 0, 0.06);
}
:root.theme-glass.theme-light body {
  background: #f5f3ee;
}

/* Топбар-блюр */
:root.theme-glass.theme-light #topbar {
  /* Светлый glass: тёмный текст на белёсой стеклянной подложке. */
  background: rgba(255, 255, 255, 0.32);
}
:root.theme-glass.theme-light #topbar > #threads-btn,
:root.theme-glass.theme-light #topbar > #routines-btn,
:root.theme-glass.theme-light #topbar .logo .topbar-title-btn {
  background: rgba(0, 0, 0, 0.05);
}

/* Композер-блюр в светлой теме — белая дымка такая же, как у glass-minimal. */
:root.theme-glass.theme-light #composer::before {
  background: transparent;
}

/* Поле ввода — light: стеклянная подложка с blur, параметры 1-в-1 со
   светлыми боковыми стрелками. */
:root.theme-glass.theme-light .input-wrap {
  background: color-mix(in srgb, rgba(255, 255, 255, 0.55) calc((1 - var(--bg-mix)) * 100%), rgba(0, 0, 0, 0.45)) !important;
  border-color: rgba(0, 0, 0, 0.14);
  box-shadow:
    0 8px 14px -6px rgba(0, 0, 0, calc(0.16 + var(--bg-mix) * 0.14)),
    inset 0 1px 0 rgba(255, 255, 255, calc(0.60 - var(--bg-mix) * 0.40)),
    inset 0 -2px 6px rgba(0, 0, 0, calc(var(--bg-mix) * 0.18)) !important;
}
:root.theme-glass.theme-light .input-wrap.is-pressed {
  background: rgba(255, 255, 255, 0.70) !important;
}
:root.theme-glass.theme-light .input-wrap:focus-within {
  box-shadow:
    0 8px 14px -6px rgba(0, 0, 0, 0.16),
    inset 0 1px 0 rgba(255, 255, 255, 0.60) !important;
}

/* Чипы M/E/V/C под полем ввода — light: стеклянная подложка с направленной
   вниз drop-shadow (negative spread, чтобы между чипами не было полоски).
   --bg-mix интерполирует фон 0.55→0.40 (светлый → перекрыт user-бабблом) и
   box-shadow от полупрозрачной к насыщенной. color наследуется из базового
   правила (color-mix var(--fg) → #fff) и шуршит правильно из чёрного в белый. */
:root.theme-glass.theme-light .chip {
  background: color-mix(in srgb, rgba(255, 255, 255, 0.55) calc((1 - var(--bg-mix)) * 100%), rgba(0, 0, 0, 0.40)) !important;
  border-color: rgba(0, 0, 0, 0.14);
  box-shadow:
    0 8px 14px -6px rgba(0, 0, 0, calc(0.16 + var(--bg-mix) * 0.14)),
    inset 0 1px 0 rgba(255, 255, 255, calc(0.60 - var(--bg-mix) * 0.38)),
    inset 0 -2px 6px rgba(0, 0, 0, calc(var(--bg-mix) * 0.18)) !important;
}
:root.theme-glass.theme-light .chip:active {
  background: rgba(255, 255, 255, 0.70) !important;
}

/* Сообщения ассистента — тёмный текст */
:root.theme-glass.theme-light .msg:not(.user) {
  color: var(--fg);
}

/* Драуэр */
:root.theme-glass.theme-light #drawer {
  background: rgba(245, 243, 238, 0.62);
  border-right-color: rgba(0, 0, 0, 0.08);
}
:root.theme-glass.theme-light .drawer-footer {
  background: transparent;
  border-top-color: rgba(0, 0, 0, 0.08);
}
:root.theme-glass.theme-light #drawer-overlay {
  background: rgba(0, 0, 0, 0.12);
}

/* ── Стекло драуэра в IPA (body.maccode-native) ────────────────────────────
   v1175: blur в WKWebView композитится нормально (прежнее допущение «не
   композитится» оказалось неверным). Кирилл: «боковое меню — прозрачным и с
   размытием, чтобы под ним немного был виден чат». Возвращаем драуэру
   полупрозрачный фон поверх общего blur(60px): чат сзади мягко читается.
   v1176: 0.55 «плохо считывается из-за высокой прозрачности» — поднял до 0.80
   (light до 0.86): список сессий читается уверенно, но blur(60px) и остаток
   прозрачности оставляют лёгкий намёк на чат сзади. Overlay не трогаю.
   Фуллскрин-подстраницы (редактор памяти/файла) остаются плотными ниже. */
:root.theme-glass body.maccode-native #drawer {
  background: rgba(13, 13, 13, 0.80);
}
:root.theme-glass.theme-light body.maccode-native #drawer {
  background: rgba(245, 243, 238, 0.86);
}
:root.theme-glass body.maccode-native #drawer-overlay {
  background: rgba(0, 0, 0, 0.42);
}
:root.theme-glass.theme-light body.maccode-native #drawer-overlay {
  background: rgba(0, 0, 0, 0.28);
}
:root.theme-glass body.maccode-native #memory-edit-page,
:root.theme-glass body.maccode-native #file-view-page {
  background: rgba(13, 13, 13, 0.85);
}
:root.theme-glass.theme-light body.maccode-native #memory-edit-page,
:root.theme-glass.theme-light body.maccode-native #file-view-page {
  background: rgba(245, 243, 238, 0.92);
}

/* Сессии в драуэре */
:root.theme-glass.theme-light .thread-item:hover {
  background: rgba(0, 0, 0, 0.04);
}
:root.theme-glass.theme-light .thread-item.active {
  background: rgba(0, 0, 0, 0.08);
}

/* Драуэр-шапка, юзедж карта */
:root.theme-glass.theme-light .drawer-header {
  border-bottom-color: rgba(0, 0, 0, 0.08);
}
:root.theme-glass.theme-light .claude-usage-card .uc-title {
  border-bottom-color: rgba(0, 0, 0, 0.12);
}
:root.theme-glass.theme-light .claude-usage-card .uc-rows,
:root.theme-glass.theme-light .claude-usage-card .uc-foot {
  border-top-color: rgba(0, 0, 0, 0.12);
}

/* История поиска в драуэре */
:root.theme-glass.theme-light .search-history-row:active {
  background: rgba(0, 0, 0, 0.06) !important;
}

/* Scope-чипы */
:root.theme-glass.theme-light .scope-chip {
  background: rgba(0, 0, 0, 0.04);
  border-color: rgba(0, 0, 0, 0.14);
}

/* Start-page строки */
:root.theme-glass.theme-light .start-row-swipe .start-unread-row.current,
:root.theme-glass.theme-light .start-row-swipe .start-busy-row.current {
  background: rgba(0, 0, 0, 0.08);
}
:root.theme-glass.theme-light .start-unread-row {
  border-bottom-color: rgba(0, 0, 0, 0.08);
}
:root.theme-glass.theme-light .start-busy-row.current {
  background: rgba(0, 0, 0, 0.08);
}

/* Модалка */
:root.theme-glass.theme-light #modal {
  background: rgba(0, 0, 0, 0.12);
}
:root.theme-glass.theme-light .modal-card {
  background: rgba(255, 255, 255, 0.80);
  border-color: rgba(0, 0, 0, 0.08);
}
:root.theme-glass.theme-light .modal-header {
  border-bottom-color: rgba(0, 0, 0, 0.08);
}
:root.theme-glass.theme-light .modal-body button,
:root.theme-glass.theme-light .modal-body button.btn-rich,
:root.theme-glass.theme-light .modal-body label.modal-pick {
  background: rgba(0, 0, 0, 0.03);
  border-color: rgba(0, 0, 0, 0.08);
}
:root.theme-glass.theme-light .modal-body button:hover,
:root.theme-glass.theme-light .modal-body button.btn-rich:hover,
:root.theme-glass.theme-light .modal-body label.modal-pick:hover {
  background: rgba(0, 0, 0, 0.06);
}
:root.theme-glass.theme-light .modal-body button.active,
:root.theme-glass.theme-light .modal-body label.modal-pick.active {
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  color: #1a1a1a;
}

/* Tech-карточки и start-page карты */
:root.theme-glass.theme-light .tech-card,
:root.theme-glass.theme-light .start-page .start-card,
:root.theme-glass.theme-light .start-page .start-suggest {
  background: rgba(0, 0, 0, 0.03);
  border-color: rgba(0, 0, 0, 0.08);
}

/* Topbar-меню */
:root.theme-glass.theme-light #topbar-menu {
  /* Фрост как у светлого бокового меню (#drawer): тёплый полупрозрачный фон
     0.40 + тот же blur. Без clip-path (см. .theme-glass #topbar-menu) — иначе
     на iOS блюр не живой и композер всплывает. Зеркалит .theme-light #drawer. */
  background: rgba(245, 243, 238, 0.30);
  -webkit-backdrop-filter: saturate(180%) blur(60px);
  backdrop-filter: saturate(180%) blur(60px);
  border-color: rgba(0, 0, 0, 0.08);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.10);
}
:root.theme-glass.theme-light .topbar-menu-row:hover {
  background: rgba(0, 0, 0, 0.05);
}
:root.theme-glass.theme-light .topbar-menu-row:active {
  background: rgba(0, 0, 0, 0.10);
}

/* Кодоблоки */
:root.theme-glass.theme-light pre,
:root.theme-glass.theme-light .code-block {
  background: rgba(0, 0, 0, 0.04) !important;
  border-color: rgba(0, 0, 0, 0.08) !important;
}
:root.theme-glass.theme-light code {
  background: rgba(0, 0, 0, 0.06) !important;
}
:root.theme-glass.theme-light .msg .body code.inline,
:root.theme-glass.theme-light .msg .body pre.codeblock,
:root.theme-glass.theme-light .msg .body pre.codeblock code {
  color: #1a1a1a !important;
}
:root.theme-glass.theme-light .msg .body pre.codeblock {
  background: rgba(0, 0, 0, 0.04) !important;
  border-left-color: rgba(0, 0, 0, 0.10) !important;
}
:root.theme-glass.theme-light .msg .body code.inline {
  background: rgba(0, 0, 0, 0.06) !important;
}

/* User-баббл в светлой gласс: фон — accent (тёмно-оранжевый / синий, зависит
   от пикера), на нём ЧЁРНЫЙ var(--fg) выглядит грязно. Делаем весь текст
   юзер-сообщения белым: тело, label «ТЫ», время, ссылки, код. */
:root.theme-glass.theme-light .msg.user,
:root.theme-glass.theme-light .msg.user .msg-bubble,
:root.theme-glass.theme-light .msg.user .body {
  color: #ffffff !important;
}
:root.theme-glass.theme-light .msg.user .role,
:root.theme-glass.theme-light .msg.user .role .role-label,
:root.theme-glass.theme-light .msg.user .msg-time {
  color: rgba(255, 255, 255, 0.75) !important;
}
:root.theme-glass.theme-light .msg.user .body a.link {
  color: #ffffff !important;
  text-decoration: underline;
  text-decoration-color: rgba(255, 255, 255, 0.5);
}
:root.theme-glass.theme-light .msg.user .body code.inline,
:root.theme-glass.theme-light .msg.user .body pre.codeblock,
:root.theme-glass.theme-light .msg.user .body pre.codeblock code {
  color: #ffffff !important;
  background: rgba(0, 0, 0, 0.18) !important;
}
:root.theme-glass.theme-light .msg.user .body pre.codeblock {
  border-left-color: rgba(255, 255, 255, 0.30) !important;
}

/* Стрелки прокрутки — light: на белом var(--fg), на синем переключаемся в
   тёмную плашку с белой иконкой по --bg-mix. */
:root.theme-glass.theme-light #scroll-up-btn,
:root.theme-glass.theme-light #scroll-down-btn {
  /* v941: тот же приём — фиксируем mix=0, чтобы обе стрелки одинаковые. */
  --bg-mix: 0 !important;
  background: rgba(255, 255, 255, 0.55) !important;
  border-color: rgba(0, 0, 0, 0.14);
  box-shadow:
    0 8px 14px -6px rgba(0, 0, 0, 0.16),
    inset 0 1px 0 rgba(255, 255, 255, 0.60) !important;
  color: var(--fg);
}
:root.theme-glass.theme-light #scroll-up-btn.pressing,
:root.theme-glass.theme-light #scroll-down-btn.pressing {
  background: rgba(255, 255, 255, 0.70) !important;
}

/* Поиск/инпуты в драуэре */
:root.theme-glass.theme-light #drawer input[type="text"],
:root.theme-glass.theme-light #drawer input[type="search"] {
  background: rgba(0, 0, 0, 0.04);
  border-color: rgba(0, 0, 0, 0.08);
}

/* Полноэкранные редакторы в светлом стекле — свой беж + blur. Рутины и
   Техническое отсюда убраны: они теперь in-drawer и прозрачные (ниже). */
:root.theme-glass.theme-light #memory-edit-page,
:root.theme-glass.theme-light #file-view-page {
  background: rgba(245, 243, 238, 0.40);
}
/* Subpages внутри drawer'а — прозрачные, стекло наследуется от drawer'а
   (так же как у footer). */
:root.theme-glass.theme-light #settings-page,
:root.theme-glass.theme-light #health-page,
:root.theme-glass.theme-light #connection-page,
:root.theme-glass.theme-light #routines-page,
:root.theme-glass.theme-light #tech-page {
  background: transparent;
}
:root.theme-glass.theme-light .settings-header {
  background: transparent;
  border-bottom-color: rgba(0, 0, 0, 0.08);
}

/* Кнопки закрытия (✕): сама плашка и сама палочка иконки */
:root.theme-glass.theme-light .icon-btn[aria-label="Закрыть"],
:root.theme-glass.theme-light #drawer-close,
:root.theme-glass.theme-light #modal-close {
  background: rgba(0, 0, 0, 0.05);
  border-color: rgba(0, 0, 0, 0.12);
}
:root.theme-glass.theme-light .icon-btn[aria-label="Закрыть"]::before,
:root.theme-glass.theme-light #drawer-close::before,
:root.theme-glass.theme-light #modal-close::before {
  background-color: rgba(0, 0, 0, 0.85);
}

/* Реакции (msg-react-btn / fav-react-chip) */
:root.theme-glass.theme-light .modal-body .msg-react-bar .msg-react-btn {
  background: rgba(0, 0, 0, 0.04);
  border-color: rgba(0, 0, 0, 0.12);
  color: rgba(0, 0, 0, 0.85);
}
:root.theme-glass.theme-light .modal-body .msg-react-bar .msg-react-btn.active {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
  color: #1a1a1a;
}
/* Реакции в light-glass: подложка/обводка/тень совпадают с .chip
   (метачипы C/M/E/V над инпутом), отличается только круглая форма. */
:root.theme-glass.theme-light .fav-react-chip {
  background: color-mix(in srgb, rgba(255, 255, 255, 0.55) calc((1 - var(--bg-mix)) * 100%), rgba(0, 0, 0, 0.40));
  border-color: rgba(0, 0, 0, 0.14);
  color: color-mix(in srgb, rgba(0, 0, 0, 0.85) calc((1 - var(--bg-mix)) * 100%), #ffffff);
  box-shadow:
    0 8px 14px -6px rgba(0, 0, 0, calc(0.16 + var(--bg-mix) * 0.14)),
    inset 0 1px 0 rgba(255, 255, 255, calc(0.60 - var(--bg-mix) * 0.38)),
    inset 0 -2px 6px rgba(0, 0, 0, calc(var(--bg-mix) * 0.18));
}
:root.theme-glass.theme-light .fav-react-chip:active {
  background: rgba(255, 255, 255, 0.70);
}
:root.theme-glass.theme-light .fav-react-chip.active {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
  color: #1a1a1a;
}

/* Иконки action-row попапа «сообщение» в светлой теме: исходные SVG нарисованы
   белым stroke (для тёмной темы), на светлой плашке они сливаются. Перерисовываем
   каждую с чёрным stroke + усиливаем фон самой плашки. */
:root.theme-glass.theme-light .modal-body > button[data-mact]::before {
  background-color: rgba(0, 0, 0, 0.05);
  border-color: rgba(0, 0, 0, 0.12);
}
:root.theme-glass.theme-light .modal-body > button[data-mact="pin"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(0,0,0,0.85)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M5 3h6M6.5 3v3L4.5 8.5h7L9.5 6V3M8 8.5v5.5'/></svg>");
}
:root.theme-glass.theme-light .modal-body > button[data-mact="unpin"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(0,0,0,0.85)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M5 3h6M6.5 3v3L4.5 8.5h7L9.5 6V3M8 8.5v5.5M2 14L14 2'/></svg>");
}
:root.theme-glass.theme-light .modal-body > button[data-mact="reply"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(0,0,0,0.85)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M6.5 4.5L3 8l3.5 3.5M3 8h6.5c1.7 0 3 1.3 3 3v1.5'/></svg>");
}
:root.theme-glass.theme-light .modal-body > button[data-mact="favorite"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(0,0,0,0.85)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M8 13.5S3 10.6 3 6.6c0-1.9 1.4-3 2.9-3 .9 0 1.6.5 2.1 1.1.5-.6 1.2-1.1 2.1-1.1 1.5 0 2.9 1.1 2.9 3 0 4-5 6.9-5 6.9z'/></svg>");
}
:root.theme-glass.theme-light .modal-body > button[data-mact="to_input"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(0,0,0,0.85)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M8 2.5v7.5M5 7l3 3 3-3M2.5 13.5h11'/></svg>");
}
:root.theme-glass.theme-light .modal-body > button[data-mact="new_thread"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(0,0,0,0.85)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M2.5 3.5a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1V10a1 1 0 0 1-1 1H6l-3 2.5V11a1 1 0 0 1-1-1z'/><path d='M8 5v4M6 7h4'/></svg>");
}
:root.theme-glass.theme-light .modal-body > button[data-mact="copy"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(0,0,0,0.85)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><rect x='5.5' y='5.5' width='7.5' height='8' rx='1'/><path d='M3 10.5V3.5a1 1 0 0 1 1-1h6'/></svg>");
}
:root.theme-glass.theme-light .modal-body > button[data-mact="delete_fav"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(0,0,0,0.85)' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'><path d='M3 4.5h10M6 4.5V3h4v1.5M4.5 4.5l.6 9.1c0 .5.5.9 1 .9h3.8c.5 0 .9-.4 1-.9l.6-9.1M6.7 7v5M9.3 7v5'/></svg>");
}
:root.theme-glass.theme-light .modal-body > button[data-mact="cancel"]::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgba(0,0,0,0.85)' stroke-width='1.6' stroke-linejoin='round' stroke-linecap='round'><path d='M4.5 4.5l7 7M11.5 4.5l-7 7'/></svg>");
}

/* Chip-popover / slash-suggest */
:root.theme-glass.theme-light #chip-popover,
:root.theme-glass.theme-light #slash-suggest {
  /* v1093: тёплый off-white топбара (245,243,238) вместо плоского белого 0.70.
     v1112: 0.55 → 0.38 — Кирилл назвал поповер «белыми полотнищами»; опускаем
     ближе к топбару (0.30), чтобы чат просвечивал. Блюр (60px) остаётся от базы. */
  background: rgba(245, 243, 238, 0.38);
  border-color: rgba(0, 0, 0, 0.12);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.10);
}
:root.theme-glass.theme-light #chip-popover .slash-suggest-row,
:root.theme-glass.theme-light #slash-suggest .slash-suggest-row {
  border-bottom-color: rgba(0, 0, 0, 0.06);
}
:root.theme-glass.theme-light #chip-popover .slash-suggest-row.active,
:root.theme-glass.theme-light #slash-suggest .slash-suggest-row.active {
  background: rgba(0, 0, 0, 0.10);
}
:root.theme-glass.theme-light #chip-popover .slash-suggest-row:hover,
:root.theme-glass.theme-light #slash-suggest .slash-suggest-row:hover {
  background: rgba(0, 0, 0, 0.05);
}

/* Attach-chip / msg-attach-chip */
:root.theme-glass.theme-light .attach-chip {
  background: color-mix(in srgb, rgba(255, 255, 255, 0.55) calc((1 - var(--bg-mix)) * 100%), rgba(0, 0, 0, 0.40));
  border-color: rgba(0, 0, 0, 0.10);
}
:root.theme-glass.theme-light .msg-attach-chip {
  background: rgba(0, 0, 0, 0.04);
  border-color: rgba(0, 0, 0, 0.08);
}

/* Settings-rows, routine/tech */
:root.theme-glass.theme-light .routines-sort-btn,
:root.theme-glass.theme-light .tech-chip {
  background: rgba(0, 0, 0, 0.04);
  border-color: rgba(0, 0, 0, 0.10);
  color: var(--fg);
}
:root.theme-glass.theme-light .tech-mtype-chip {
  background: rgba(0, 0, 0, 0.03);
  border-color: rgba(0, 0, 0, 0.10);
  color: var(--fg-dim);
}
:root.theme-glass.theme-light .tech-mtype-chip.is-active {
  background: rgba(0, 0, 0, 0.06);
  border-color: var(--accent);
  color: var(--accent);
}
/* Контейнер обоих рядов чипов — на светлом стекле серая плашка лишняя,
   фон бежевый сам по себе уже подложка. */
:root.theme-glass.theme-light .tech-chips-row {
  background: transparent;
  border-bottom-color: rgba(0, 0, 0, 0.08);
}
:root.theme-glass.theme-light .tech-sort-toggle {
  background: rgba(0, 0, 0, 0.04);
  border-color: rgba(0, 0, 0, 0.10);
  color: var(--fg-dim);
}
:root.theme-glass.theme-light .tech-sort-toggle.is-active {
  color: var(--accent);
}
:root.theme-glass.theme-light .routine-card,
:root.theme-glass.theme-light .tech-card {
  background: rgba(0, 0, 0, 0.03);
  border-color: rgba(0, 0, 0, 0.08);
}
:root.theme-glass.theme-light .memory-edit-input,
:root.theme-glass.theme-light .memory-edit-textarea,
:root.theme-glass.theme-light .file-view-textarea,
:root.theme-glass.theme-light .mem-type-trigger {
  background: rgba(255, 255, 255, 0.55) !important;
  border-color: rgba(0, 0, 0, 0.14) !important;
  color: rgba(0, 0, 0, 0.86) !important;
}
:root.theme-glass.theme-light .memory-edit-input:focus,
:root.theme-glass.theme-light .memory-edit-textarea:focus,
:root.theme-glass.theme-light .file-view-textarea:focus {
  background: rgba(255, 255, 255, 0.78) !important;
  border-color: rgba(0, 0, 0, 0.28) !important;
}
:root.theme-glass.theme-light .memory-edit-input::placeholder,
:root.theme-glass.theme-light .memory-edit-textarea::placeholder,
:root.theme-glass.theme-light .file-view-textarea::placeholder {
  color: rgba(0, 0, 0, 0.40);
}
:root.theme-glass.theme-light .routine-cron-input,
:root.theme-glass.theme-light .routine-textarea {
  background: rgba(0, 0, 0, 0.04);
  border-color: rgba(0, 0, 0, 0.08);
}
:root.theme-glass.theme-light .routine-chip {
  background: rgba(0, 0, 0, 0.04);
  border-color: rgba(0, 0, 0, 0.10);
}
:root.theme-glass.theme-light .memory-edit-action-btn,
:root.theme-glass.theme-light .file-view-action-btn {
  background: rgba(0, 0, 0, 0.04);
  border-color: rgba(0, 0, 0, 0.08);
  color: var(--fg);
}
/* Save (✓) и Delete (🗑) — на светлой glass-теме делаем прозрачный фон,
   тёмный цветной бордер + тёмная иконка, чтоб читалось на бежевом и
   на белом контентном фоне одинаково. Та же специфичность ID-правил. */
:root.theme-glass.theme-light #memory-edit-save,
:root.theme-glass.theme-light #file-view-save {
  background: transparent;
  border-color: rgba(45, 110, 220, 0.65);
  color: #1f4d8a;
}
:root.theme-glass.theme-light #memory-edit-save:active,
:root.theme-glass.theme-light #file-view-save:active {
  background: rgba(45, 110, 220, 0.14);
  border-color: rgba(30, 80, 180, 0.85);
  color: #14365e;
}
:root.theme-glass.theme-light #memory-edit-delete,
:root.theme-glass.theme-light #file-view-delete {
  background: transparent;
  border-color: rgba(180, 50, 42, 0.65);
  color: #952520;
}
:root.theme-glass.theme-light #memory-edit-delete:active,
:root.theme-glass.theme-light #file-view-delete:active {
  background: rgba(216, 58, 48, 0.14);
  border-color: rgba(160, 40, 35, 0.85);
  color: #6e1814;
}
:root.theme-glass.theme-light .settings-header .icon-btn[aria-label="Обновить"],
:root.theme-glass.theme-light .settings-header #routines-add,
:root.theme-glass.theme-light .settings-header #tech-add {
  background: rgba(0, 0, 0, 0.05);
  border-color: rgba(0, 0, 0, 0.12);
  color: rgba(0, 0, 0, 0.85);
}
/* На светлой glass-теме «В чат»-кнопка в шапке деталки (файл/память):
   прозрачный фон + тёмный контур + тёмная иконка — кружок читается на
   бежевом фоне без серой плашки. */
:root.theme-glass.theme-light #file-view-insert,
:root.theme-glass.theme-light #memory-edit-insert {
  background: transparent;
  border-color: rgba(0, 0, 0, 0.28);
  color: rgba(0, 0, 0, 0.85);
}
:root.theme-glass.theme-light #file-view-insert:active,
:root.theme-glass.theme-light #memory-edit-insert:active {
  background: rgba(0, 0, 0, 0.06);
  border-color: rgba(0, 0, 0, 0.42);
  color: rgba(0, 0, 0, 0.95);
}

/* × Закрыть на деталке (память/файл) на светлой glass-теме — прозрачный
   фон, тёмный контур, чёрный крестик. Скоупим к ID, чтоб не сломать
   модалку/дровер (там оставляем серую плашку из общего правила выше). */
:root.theme-glass.theme-light #memory-edit-close,
:root.theme-glass.theme-light #file-view-close {
  background: transparent;
  border-color: rgba(0, 0, 0, 0.28);
}
:root.theme-glass.theme-light #memory-edit-close::before,
:root.theme-glass.theme-light #file-view-close::before {
  background-color: rgba(0, 0, 0, 0.85);
}

/* ===== Apple Liquid Glass theme (v697) =====
   По официальной Apple HIG (Materials): functional layer (topbar/composer)
   only — content (bubbles) не трогаем. theme-liquid в коде включается
   ВМЕСТЕ с theme-glass (см. applyTheme() в app.js: isGlass = ... || isLiquid),
   поэтому glass уже рисует ::before с тёмной плёнкой 0.55 + blur 22px.
   Наш фикс — переопределить ИМЕННО ::before для liquid: светлый Apple-tint
   и blur посильнее. Edge-light кладём как box-shadow на сам #topbar/#composer
   (он у glass position: absolute, тень видна).

   Бонус-displacement (Chrome/Edge) через @supports на ::before. WKWebView
   пропустит — там SVG-фильтр в backdrop-filter не работает. */
:root.theme-liquid {
  --liquid-tint: rgba(255, 255, 255, 0.18);
  --liquid-tint-top: rgba(255, 255, 255, 0.28);
  --liquid-tint-bot: rgba(255, 255, 255, 0.10);
  --liquid-edge-top: rgba(255, 255, 255, 0.65);
  --liquid-edge-bot: rgba(0, 0, 0, 0.18);
  --liquid-outer: rgba(0, 0, 0, 0.35);
}
:root.theme-liquid.theme-light {
  --liquid-tint: rgba(255, 255, 255, 0.55);
  --liquid-tint-top: rgba(255, 255, 255, 0.72);
  --liquid-tint-bot: rgba(255, 255, 255, 0.42);
  --liquid-edge-top: rgba(255, 255, 255, 0.95);
  --liquid-edge-bot: rgba(0, 0, 0, 0.08);
  --liquid-outer: rgba(0, 0, 0, 0.14);
}

/* Перекрываем тёмный ::before-слой glass'а светлым Liquid-tint'ом.
   Specificity (1,2,1) > glass (1,2,1) — побеждаем порядком в файле.
   Linear-gradient вместо плоского tint — даёт визуальный объём, чтобы
   стекло читалось на айфоне даже без SVG-преломления. */
:root.theme-liquid #topbar::before {
  background: linear-gradient(to bottom, var(--liquid-tint-top), var(--liquid-tint-bot));
  backdrop-filter: saturate(200%) blur(36px) brightness(1.05);
  -webkit-backdrop-filter: saturate(200%) blur(36px) brightness(1.05);
}
:root.theme-liquid #composer::before {
  background: linear-gradient(to top, var(--liquid-tint-top), var(--liquid-tint-bot));
  backdrop-filter: saturate(200%) blur(36px) brightness(1.05);
  -webkit-backdrop-filter: saturate(200%) blur(36px) brightness(1.05);
  /* glass-маска делала fade-in blur'а снизу вверх. Для liquid убираем —
     равномерное матовое стекло по всей высоте композёра. */
  -webkit-mask-image: none;
  mask-image: none;
  /* Базовый glass-::before тянется на top:-90px выше композёра и ГАСИТ этот
     перехлёст fade-маской. В liquid маску сняли (строки выше) — без неё
     перехлёст превращался в полупрозрачный прямоугольник с жёсткой верхней
     кромкой, висящий над полем ввода (репорт Кирилла «прямоугольник за полем
     ввода»). Прибиваем плашку ровно к телу композёра (top:0) — совпадает с
     ::after-бликом (height:--composer-h); боковые стрелки ↑/↓ своё стекло
     носят сами (#scroll-*-btn), без перехлёста не лысеют. */
  top: 0;
}

/* Edge-light сверху + edge-shadow снизу + outer drop shadow — три уровня
   подсветки рёбер, как у Apple Liquid Glass в Control Center/Notes. */
:root.theme-liquid #topbar {
  box-shadow:
    inset 0 1px 0 var(--liquid-edge-top),
    inset 0 -1px 0 var(--liquid-edge-bot),
    0 8px 28px var(--liquid-outer);
}
:root.theme-liquid #composer {
  box-shadow:
    inset 0 1px 0 var(--liquid-edge-top),
    inset 0 -1px 0 var(--liquid-edge-bot),
    0 -8px 28px var(--liquid-outer);
}

/* Бонус-refraction только в Chromium.
   Мелкий blur (4px) + крупный displacement (scale=180 в SVG) даёт эффект
   «живой воды» из статьи habr.com/ru/publications/974058; большой blur
   убил бы преломление и дал бы обычный матовый glass. */
@supports (backdrop-filter: url(#liquid-glass-refract)) {
  :root.theme-liquid #topbar::before,
  :root.theme-liquid #composer::before {
    backdrop-filter: saturate(180%) blur(4px) url(#liquid-glass-refract);
    -webkit-backdrop-filter: saturate(180%) blur(4px) url(#liquid-glass-refract);
  }
}

/* ===== Variant A снят (v1030) =====
   Нативная стеклянная полоса (UIGlassEffect) под прозрачным webview не читалась
   как Liquid Glass: преломляла искусственный градиент, а не чат, плюс цветная
   аура заливала весь экран, а композёр оставался без стекла. Кирилл забраковал.
   Возврат к чистому CSS: композёр снова получает матовый ::before (см. выше), а
   «жидкость» даём спекулярным бликом ::after + усиленной кромкой (#composer выше).
   Бывшие правила (#composer::before → transparent; #aura-bg → mask-окно) удалены. */

/* Спекулярный блик — имитация света на выпуклой стеклянной поверхности.
   Отличие от плоского матового «Стекла»: яркая световая дуга по верхней кромке
   + мягкое свечение от верхних углов → ощущение толщины и глянца.
   Чистые градиенты, без backdrop-фильтра → работает на iOS WKWebView (в отличие
   от displacement-преломления, которое WebKit в backdrop-filter не умеет). */
:root.theme-liquid #composer::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  /* блик на теле композёра (не на 90px-продолжении ::before) — высота по
     --composer-h, фолбэк на типичную геометрию */
  height: var(--composer-h, 140px);
  pointer-events: none;
  z-index: 0;
  background:
    /* верхняя световая дуга (specular highlight) */
    linear-gradient(to bottom,
      rgba(255, 255, 255, 0.6) 0,
      rgba(255, 255, 255, 0.14) 2px,
      rgba(255, 255, 255, 0) 20px),
    /* мягкое свечение от верхних углов — «толщина» стекла */
    radial-gradient(130% 90% at 50% 0,
      rgba(255, 255, 255, 0.2) 0,
      rgba(255, 255, 255, 0) 62%);
}
:root.theme-liquid.theme-light #composer::after {
  background:
    linear-gradient(to bottom,
      rgba(255, 255, 255, 0.95) 0,
      rgba(255, 255, 255, 0.3) 2px,
      rgba(255, 255, 255, 0) 20px),
    radial-gradient(130% 90% at 50% 0,
      rgba(255, 255, 255, 0.4) 0,
      rgba(255, 255, 255, 0) 62%);
}

/* ============================================================
   Ликвид Гласс 2 (theme-liquid2) — чистый CSS поверх Glass-базы.
   Кирилл: «скопируй стекло в ликвид гласс 2 и тяни к Apple Liquid Glass».
   Реализация: theme-liquid2 вешается ВМЕСТЕ с theme-glass (см. applyTheme),
   поэтому наследует всю glass-разметку, а тут — только оверрайды под
   Apple-вид. Без нативки и без SVG-displacement (WebKit не умеет url() в
   backdrop-filter) → «жидкость» строим на: насыщенный frost-blur +
   спекулярный блик по верхней кромке + рим-фаска (inset top/bottom) +
   мягкий подъём тенью. Всё iOS-WKWebView-safe → Chrome-превью == айфон.
   Префикс .theme-glass.theme-liquid2 = specificity (1,3,x): бьёт и
   glass-базу (1,2,x), и glass-minimal в светлом (1,3,x, но мы ниже в файле). */
:root.theme-liquid2 {
  --lq2-blur: 36px;
  --lq2-sat: 210%;
  --lq2-rim-top: rgba(255, 255, 255, 0.72);
  --lq2-rim-side: rgba(255, 255, 255, 0.28);
  --lq2-rim-bot: rgba(0, 0, 0, 0.28);
  --lq2-sheen: rgba(255, 255, 255, 0.78);
  --lq2-glow: rgba(255, 255, 255, 0.22);
  --lq2-lift: rgba(0, 0, 0, 0.40);
  --lq2-edge: rgba(255, 255, 255, 0.5); /* тонкий внешний кант стекла */
}
:root.theme-liquid2.theme-light {
  --lq2-rim-top: rgba(255, 255, 255, 1);
  --lq2-rim-side: rgba(255, 255, 255, 0.6);
  --lq2-rim-bot: rgba(0, 0, 0, 0.12);
  --lq2-sheen: rgba(255, 255, 255, 1);
  --lq2-glow: rgba(255, 255, 255, 0.6);
  --lq2-lift: rgba(0, 0, 0, 0.16);
  --lq2-edge: rgba(255, 255, 255, 0.85);
}

/* Композёр: усиленный матовый frost (маску/геометрию наследуем у glass). */
:root.theme-glass.theme-liquid2 #composer::before {
  backdrop-filter: saturate(var(--lq2-sat)) blur(var(--lq2-blur));
  -webkit-backdrop-filter: saturate(var(--lq2-sat)) blur(var(--lq2-blur));
}
/* Рим-фаска (яркая верхняя кромка + тёмная нижняя) + мягкий подъём вверх. */
:root.theme-glass.theme-liquid2 #composer {
  box-shadow:
    inset 0 1px 0 var(--lq2-rim-top),
    inset 0 -1px 0 var(--lq2-rim-bot),
    0 -10px 30px var(--lq2-lift);
}
/* Спекулярный блик: яркая кромка-волосок + сходящий sheen + угловое свечение.
   z-index:0 — под чипами/полем (glass поднимает детей композёра в z-index:1). */
:root.theme-glass.theme-liquid2 #composer::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--composer-h, 140px);
  pointer-events: none;
  z-index: 0;
  background:
    linear-gradient(to bottom,
      var(--lq2-sheen) 0,
      rgba(255, 255, 255, 0.14) 2px,
      rgba(255, 255, 255, 0) 38px),
    radial-gradient(130% 90% at 50% 0,
      var(--lq2-glow) 0,
      rgba(255, 255, 255, 0) 64%);
}

/* Топбар: тот же frost + рим. Blur живёт на самом #topbar; фон rgba(0,0,0,.32)
   не трогаем — он нужен для контраста текста поверх. */
:root.theme-glass.theme-liquid2 #topbar {
  backdrop-filter: saturate(var(--lq2-sat)) blur(var(--lq2-blur));
  -webkit-backdrop-filter: saturate(var(--lq2-sat)) blur(var(--lq2-blur));
  box-shadow:
    inset 0 1px 0 var(--lq2-rim-top),
    inset 0 -1px 0 var(--lq2-rim-bot),
    0 10px 30px var(--lq2-lift);
}

/* Поле ввода: стеклянная пилюля с яркой верхней кромкой и глянцем.
   У glass-базы box-shadow с !important → перебиваем тоже !important. */
:root.theme-glass.theme-liquid2 .input-wrap {
  border-color: var(--lq2-rim-top);
  -webkit-backdrop-filter: blur(14px) saturate(200%);
  backdrop-filter: blur(14px) saturate(200%);
  box-shadow:
    0 12px 26px -10px var(--lq2-lift),
    0 0 0 0.5px var(--lq2-edge),
    inset 0 2px 0 var(--lq2-rim-top),
    inset 1px 0 0 var(--lq2-rim-side),
    inset -1px 0 0 var(--lq2-rim-side),
    inset 0 -4px 10px var(--lq2-rim-bot) !important;
}

/* ===== Переливающийся фон ===== */
/* Опция «Переливающийся фон» → .with-aura на <body> + JS вставляет
   <div id="aura-bg"> первым ребёнком <body>.
   v904: четвёртый подход. position:fixed div с z-index:-1, animation
   на transform (а не background-position) — единственный способ дать
   плавный параллакс на iOS WKWebView: transform идёт через GPU
   композитор, background-position в WK тормозит и часто застывает. */
body.with-aura {
  background: transparent;
}
#aura-bg {
  /* v914: контейнер полноэкранный с чёрной заливкой — закрывает paint-buffer
     WKWebView сверху донизу. Иначе при body.with-aura {background:transparent}
     над топбаром (zona safe-top + sticky #topbar в glass-теме прозрачный) виден
     "белый" canvas WebView с прилипшим paint-snapshot прокрученных .role-label
     и .msg-time — те самые BACH/RACH/FRIT над "МАККОД" и временем.
     Блобы вынесены в дочерний .aura-blobs (top: 33vh, overflow: hidden),
     там и живёт ограничение свечения нижней 2/3 экрана. */
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-color: #000;
  overflow: hidden;
}
#aura-bg .aura-blobs {
  position: absolute;
  /* v917: повышен потолок свечения с нижней 2/3 (top: 33vh) до нижней 3/4 (top: 25vh).
     v1176: Кирилл «свечение выше, больше площадь» — потолок поднят до 8vh (почти
     весь экран, остаётся узкая полоса под safe-top/топбаром, чтобы свечение не
     забивало role-label/время, которые в glass рисуются прямо на ауре). Контейнер
     overflow:hidden режет выход за 8vh; амплитуда translateY ниже (~-45%) и
     укрупнённые блобы (78%) доводят свечение до потолка. */
  top: 8vh;
  left: -20%;
  right: -20%;
  bottom: -20%;
  overflow: hidden;
}
/* v911: чёрный overlay 10% поверх блобов — общая яркость снижена.
   v925: при печати в композёре фон «оживает» — JS бампает --aura-energy (0..1),
   плавно затухает обратно за ~1.5с. Тут уменьшаем тёмный overlay при энергии,
   а на самих блобах ниже бустим saturate/brightness — суммарно фон ярче и
   насыщеннее во время активной печати. */
#aura-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  /* v937: при громком голосе тёмный overlay редеет ещё на 0.05 — блобы
     пробиваются ярче в такт частотам микрофона. --voice-level пишется
     из pumpVoiceBandsToAura(). */
  background: rgba(0, 0, 0, calc(0.10 - var(--aura-energy, 0) * 0.08 - var(--voice-level, 0) * 0.05));
  pointer-events: none;
  z-index: 1;
}
/* v912: УБРАН mix-blend-mode: screen — на iOS WKWebView он создавал
   implicit stacking context у body, который ломал paint-зачистку
   уехавших .role-label/.msg-time (в glass-теме .msg:not(.user)
   полностью прозрачный, текст имени/времени рисуется прямо на aura).
   alpha поднята до 0.95 — цвета не теряются. */
#aura-bg .aura-blob {
  position: absolute;
  /* v1176: 70% → 78% — больше площадь свечения (Кирилл «больше площадь»). */
  width: 78%;
  height: 78%;
  border-radius: 50%;
  /* v925: filter реагирует на --aura-energy (см. JS auraImpulse). При энергии
     блобы становятся насыщеннее и ярче — фон «оживает» во время печати.
     v937: каждый блоб дополнительно драйвится своим частотным диапазоном
     (--voice-b1..b4 переопределены в b1/b2/b3/b4 ниже). Базовый формула
     включает усреднённый --voice-level — это safety-fallback, если конкретная
     полоса не задана. */
  filter: blur(70px)
          saturate(calc(1 + var(--aura-energy, 0) * 0.7 + var(--voice-level, 0) * 1.1))
          brightness(calc(1 + var(--aura-energy, 0) * 0.25 + var(--voice-level, 0) * 0.55));
  will-change: transform, filter;
}
/* v915: цвета блобов берутся из CSS-переменных --aura-c1..c4. applyAccent()
   в app.js пересчитывает их по выбранному акценту: центр + сдвиги hue ±25°/+50°,
   с насыщенностью/светлотой подкрученной для яркого свечения на чёрном.
   Дефолты ниже — палитра под teal (бирюза), на случай если JS не успел
   проставить переменные до первой отрисовки. */
/* v937: каждый блоб реагирует на свой частотный диапазон голоса —
   b1=бас, b2=низ-миды, b3=верх-миды, b4=верха. Спектр пишется в
   --voice-b1..b4 из pumpVoiceBandsToAura() покадрово. */
/* v953: длительности попарно (b1=b2, b3=b4) — фазы зеркально-симметричных
   соседей идут синхронно. Раньше (20s/26s/30s/36s) пары разъезжались, и в
   любой момент блобы могли оказаться сдвинуты в одну сторону экрана —
   при aura-energy импульс высвечивал асимметрию (свечение только справа
   или только слева). Теперь b1↔b2 (нижний центр) и b3↔b4 (нижние края)
   всегда движутся зеркально-противоположно за одинаковое время. */
#aura-bg .aura-blob.b1 {
  bottom: -10%; left: -15%;
  background: radial-gradient(circle, var(--aura-c1, rgba(38, 214, 200, 0.95)) 0%, transparent 70%);
  animation: aura-blob-1 22s ease-in-out infinite alternate;
  filter: blur(70px)
          saturate(calc(1 + var(--aura-energy, 0) * 0.7 + var(--voice-b1, 0) * 1.3))
          brightness(calc(1 + var(--aura-energy, 0) * 0.25 + var(--voice-b1, 0) * 0.65));
}
#aura-bg .aura-blob.b2 {
  bottom: -10%; right: -12%;
  background: radial-gradient(circle, var(--aura-c2, rgba(176, 124, 255, 0.90)) 0%, transparent 70%);
  animation: aura-blob-2 22s ease-in-out infinite alternate;
  filter: blur(70px)
          saturate(calc(1 + var(--aura-energy, 0) * 0.7 + var(--voice-b2, 0) * 1.3))
          brightness(calc(1 + var(--aura-energy, 0) * 0.25 + var(--voice-b2, 0) * 0.65));
}
#aura-bg .aura-blob.b3 {
  bottom: -20%; right: -25%;
  background: radial-gradient(circle, var(--aura-c3, rgba(64, 200, 220, 0.85)) 0%, transparent 70%);
  animation: aura-blob-3 32s ease-in-out infinite alternate;
  filter: blur(70px)
          saturate(calc(1 + var(--aura-energy, 0) * 0.7 + var(--voice-b3, 0) * 1.3))
          brightness(calc(1 + var(--aura-energy, 0) * 0.25 + var(--voice-b3, 0) * 0.65));
}
#aura-bg .aura-blob.b4 {
  bottom: -20%; left: -22%;
  background: radial-gradient(circle, var(--aura-c4, rgba(88, 228, 193, 0.90)) 0%, transparent 70%);
  animation: aura-blob-4 32s ease-in-out infinite alternate;
  filter: blur(70px)
          saturate(calc(1 + var(--aura-energy, 0) * 0.7 + var(--voice-b4, 0) * 1.3))
          brightness(calc(1 + var(--aura-energy, 0) * 0.25 + var(--voice-b4, 0) * 0.65));
}
/* v916: светлая тема — фон aura матчит общий светлый бэкграунд (#f5f3ee),
   overlay тоже светлый, а сами блобы пастельные (opacity 0.55 — иначе
   alpha 0.85-0.95, рассчитанная под чёрный, выжигает на белом). */
:root.theme-light #aura-bg {
  background-color: #f5f3ee;
}
:root.theme-light #aura-bg::after {
  /* v951: в светлой overlay был статичным rgba(245,243,238,0.20). На тапах
     ауры (--aura-energy 0..1) не реагировал — импульс был еле виден. Теперь
     overlay просветлеет на тапах: 0.22 → 0.10 при energy=1, +amplitude в саму
     ауру через blob filter (см. ниже :root.theme-light overrides). */
  background: rgba(245, 243, 238, calc(0.10 - var(--aura-energy, 0) * 0.08 - var(--voice-level, 0) * 0.06));
}
:root.theme-light #aura-bg .aura-blob {
  /* v951: в light подняли базовую opacity 0.55 → 0.62 + усилили filter-импульс.
     v1149: Кирилл «фон в светлой теме более броский» — кремовый overlay срезан
     0.22→0.10, opacity 0.62→0.76, блобы получили базовый saturate/brightness в
     ПОКОЕ (см. b1..b4 ниже): цвет сочный без печати, не только на импульсе. */
  opacity: 0.76;
}
/* v951: в светлой теме филтр блобов реагирует на --aura-energy сильнее —
   saturate/brightness получают +0.3 относительно темного дефолта. На светлом
   overlay'е импульс становится явно различимым (был 0.7/0.25 — в light
   еле видно из-за светлой подложки). */
:root.theme-light #aura-bg .aura-blob.b1 {
  filter:
          saturate(calc(1.3 + var(--aura-energy, 0) * 1.1 + var(--voice-b1, 0) * 1.5))
          brightness(calc(1.07 + var(--aura-energy, 0) * 0.40 + var(--voice-b1, 0) * 0.65));
}
:root.theme-light #aura-bg .aura-blob.b2 {
  filter:
          saturate(calc(1.3 + var(--aura-energy, 0) * 1.1 + var(--voice-b2, 0) * 1.5))
          brightness(calc(1.07 + var(--aura-energy, 0) * 0.40 + var(--voice-b2, 0) * 0.65));
}
:root.theme-light #aura-bg .aura-blob.b3 {
  filter:
          saturate(calc(1.3 + var(--aura-energy, 0) * 1.1 + var(--voice-b3, 0) * 1.5))
          brightness(calc(1.07 + var(--aura-energy, 0) * 0.40 + var(--voice-b3, 0) * 0.65));
}
:root.theme-light #aura-bg .aura-blob.b4 {
  filter:
          saturate(calc(1.3 + var(--aura-energy, 0) * 1.1 + var(--voice-b4, 0) * 1.5))
          brightness(calc(1.07 + var(--aura-energy, 0) * 0.40 + var(--voice-b4, 0) * 0.65));
}
/* v1057: светлое стекло — голый верх контейнера ауры (#f5f3ee) над клипом
   .aura-blobs (top:25vh, overflow:hidden) читался как «белая плита», а резкая
   кромка клипа на 25vh давала видимый горизонтальный шов бирюза↔крем (явный
   баг на скринах Кирилла). В тёмном стекле то же на чёрном незаметно, в ретро
   ауры нет — потому артефакт только в glass-light. Растушёвываем верхнюю кромку
   блоб-слоя маской: свечение проявляется плавно от потолка вниз, жёсткой
   границы больше нет, нижняя масса ауры (40vh+) не трогается. Скоуп строго
   theme-glass.theme-light — тёмное стекло и ретро не задеваем. */
:root.theme-glass.theme-light #aura-bg .aura-blobs {
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 40vh);
  mask-image: linear-gradient(180deg, transparent 0, #000 40vh);
}
/* v1037: «Переливающийся фон 2 (мини)» — облегчённый пресет. Блобы мельче и
   прозрачнее, дрейф остаётся спокойным, реакции на печать/голос нет (JS не
   пишет --aura-energy/--voice-* при aura-mini, см. auraImpulse/pumpVoiceBands).
   Дешевле по GPU — безопасно для нативного IPA, где полный фон + голос ронял
   WebView. */
body.aura-mini #aura-bg .aura-blob {
  width: 45%;
  height: 45%;
  opacity: 0.5;
}
:root.theme-light body.aura-mini #aura-bg .aura-blob {
  opacity: 0.42;
}
/* v1092: «Переливающийся фон 3 (лёгкий)» — полный охват как у первого пресета
   (блобы 70%, на весь фон, БЕЗ ужимания как в mini), но дёшево по GPU. Полный
   пресет дорог из-за scale() в кейфреймах: каждый кадр меняется footprint
   blur(70px) → WK переблюривает весь слой. Здесь дрейф translate-ONLY (scale нет)
   — blur растеризуется один раз и дальше только композится. will-change:transform
   (без filter) не таскает фильтр-слой. Реакций на печать/голос/думанье нет (JS
   гасит --aura-energy/--voice-* при aura-lite, как и при mini). */
body.aura-lite #aura-bg .aura-blob {
  will-change: transform;
}
body.aura-lite #aura-bg .aura-blob.b1 { animation: aura-blob-lite-1 42s ease-in-out infinite alternate; }
body.aura-lite #aura-bg .aura-blob.b2 { animation: aura-blob-lite-2 42s ease-in-out infinite alternate; }
body.aura-lite #aura-bg .aura-blob.b3 { animation: aura-blob-lite-3 54s ease-in-out infinite alternate; }
body.aura-lite #aura-bg .aura-blob.b4 { animation: aura-blob-lite-4 54s ease-in-out infinite alternate; }
@keyframes aura-blob-lite-1 {
  0%   { transform: translate(0,    0); }
  50%  { transform: translate(10%,  -22%); }
  100% { transform: translate(-6%,  -12%); }
}
@keyframes aura-blob-lite-2 {
  0%   { transform: translate(0,    0); }
  50%  { transform: translate(-10%, -20%); }
  100% { transform: translate(6%,   -10%); }
}
@keyframes aura-blob-lite-3 {
  0%   { transform: translate(0,    0); }
  50%  { transform: translate(-12%, -24%); }
  100% { transform: translate(8%,   -12%); }
}
@keyframes aura-blob-lite-4 {
  0%   { transform: translate(0,    0); }
  50%  { transform: translate(12%,  -20%); }
  100% { transform: translate(-6%,  -10%); }
}
/* v1094: «Переливы — насыщенный» — на ступень живее лёгкого: тот же полный охват
   (блобы 70%, без ужимания как в mini), но амплитуда дрейфа ~1.5× и период короче
   (30-36s против 42-54s у lite). По-прежнему translate-ONLY (без scale) — blur
   растеризуется один раз и дальше только композится, без переблюра каждый кадр.
   Реакций на печать/голос/думанье нет (JS гасит --aura-energy/--voice-* и при
   aura-rich, как у mini/lite). Безопасно по GPU для нативного IPA. */
body.aura-rich #aura-bg .aura-blob {
  will-change: transform;
}
body.aura-rich #aura-bg .aura-blob.b1 { animation: aura-blob-rich-1 30s ease-in-out infinite alternate; }
body.aura-rich #aura-bg .aura-blob.b2 { animation: aura-blob-rich-2 30s ease-in-out infinite alternate; }
body.aura-rich #aura-bg .aura-blob.b3 { animation: aura-blob-rich-3 36s ease-in-out infinite alternate; }
body.aura-rich #aura-bg .aura-blob.b4 { animation: aura-blob-rich-4 36s ease-in-out infinite alternate; }
@keyframes aura-blob-rich-1 {
  0%   { transform: translate(0,    0); }
  50%  { transform: translate(16%,  -34%); }
  100% { transform: translate(-10%, -18%); }
}
@keyframes aura-blob-rich-2 {
  0%   { transform: translate(0,    0); }
  50%  { transform: translate(-16%, -30%); }
  100% { transform: translate(10%,  -16%); }
}
@keyframes aura-blob-rich-3 {
  0%   { transform: translate(0,    0); }
  50%  { transform: translate(-18%, -36%); }
  100% { transform: translate(12%,  -18%); }
}
@keyframes aura-blob-rich-4 {
  0%   { transform: translate(0,    0); }
  50%  { transform: translate(18%,  -30%); }
  100% { transform: translate(-10%, -16%); }
}
/* v917: амплитуда translateY поднята до ~-45% — блобы дотягиваются до потолка
   .aura-blobs (top: 25vh, нижняя 3/4 экрана). overflow:hidden контейнера
   режет всё выше 25vh — гарантия что свечение никогда не лезет в верхнюю 1/4. */
@keyframes aura-blob-1 {
  0%   { transform: translate(0,    0)     scale(1.00); }
  35%  { transform: translate(12%,  -22%)  scale(1.10); }
  70%  { transform: translate(-8%,  -45%)  scale(1.05); }
  100% { transform: translate(15%,  -36%)  scale(1.12); }
}
@keyframes aura-blob-2 {
  0%   { transform: translate(0,    0)     scale(1.05); }
  30%  { transform: translate(-12%, -18%)  scale(0.95); }
  60%  { transform: translate(8%,   -42%)  scale(1.18); }
  100% { transform: translate(-15%, -30%)  scale(1.05); }
}
@keyframes aura-blob-3 {
  0%   { transform: translate(0,    0)     scale(1.00); }
  40%  { transform: translate(-18%, -25%)  scale(1.15); }
  75%  { transform: translate(10%,  -45%)  scale(0.95); }
  100% { transform: translate(-8%,  -36%)  scale(1.10); }
}
@keyframes aura-blob-4 {
  0%   { transform: translate(0,    0)     scale(1.10); }
  30%  { transform: translate(18%,  -20%)  scale(1.00); }
  65%  { transform: translate(-10%, -42%)  scale(1.18); }
  100% { transform: translate(12%,  -30%)  scale(1.05); }
}
@media (prefers-reduced-motion: reduce) {
  #aura-bg .aura-blob { animation: none; }
}
/* v1037: заморозка ауры при наборе (v1024) снята — Кирилл вернул реакцию
   полного фона на печать («текст больше не падает, дело не в анимации»).
   Анти-краш записи голоса остаётся ниже (recording-freeze в нативе). */
/* Запись голоса в нативном IPA: тема liquid (тяжёлое стекло) + дрейфующие
   blur(70px) блоба под backdrop-filter = непрерывный re-blur, который на
   время записи добивал WebContent до jetsam. Замораживаем дрейф (как при
   наборе), пара к JS-глушению voice→aura в pumpVoiceBandsToAura. Только в
   нативе: в Telegram запись голоса не падает, фон не трогаем. */
body.maccode-native.recording.with-aura #aura-bg .aura-blob,
body.maccode-native.recording.with-aura #aura-bg .aura-blobs {
  animation-play-state: paused !important;
}

/* v1176: разворот v1047. Кирилл просил обратное тому, что было: «усиль свечение
   на фоне — в покое, когда пишу, когда думаешь» + «убери акцентную обводку в
   поле ввода». Возвращаем реакцию ФОНА на --aura-energy (печать) и поднимаем
   базовое свечение в покое (idle saturate 1.3 / brightness 1.12 против прежних
   статичных 1.2 / 1.06). Голос (--voice-*) в glass НЕ возвращаем — именно он
   добивал WebContent до jetsam при записи в нативе (см. recording-freeze выше);
   фон оживает только на печать и на thinking-пульс (aura-thinking ниже). */
:root.theme-glass body.with-aura #aura-bg .aura-blob,
:root.theme-glass body.with-aura #aura-bg .aura-blob.b1,
:root.theme-glass body.with-aura #aura-bg .aura-blob.b2,
:root.theme-glass body.with-aura #aura-bg .aura-blob.b3,
:root.theme-glass body.with-aura #aura-bg .aura-blob.b4 {
  filter: blur(70px)
          saturate(calc(1.3 + var(--aura-energy, 0) * 0.9))
          brightness(calc(1.12 + var(--aura-energy, 0) * 0.42));
}
/* Тёмный слой поверх блобов: idle 0.06 (светлее прежних 0.10 — фон ярче в
   покое), при печати редеет ещё на energy*0.08 → свечение пробивается сильнее. */
:root.theme-glass body.with-aura #aura-bg .aura-blobs {
  background: rgba(0, 0, 0, calc(0.06 - var(--aura-energy, 0) * 0.06)) !important;
}
:root.theme-glass.theme-light body.with-aura #aura-bg::after {
  background: rgba(245, 243, 238, 0.22) !important;
}
/* v1176: пилюля поля — нейтральная. Убран акцентный glow и подсветка рамки
   (Кирилл: «убери акцентную обводку в поле ввода»). Импульс печати теперь несёт
   фон (выше), не поле. Остаётся только базовая тень/инсеты пилюли. */
:root.theme-glass body.with-aura .input-wrap {
  box-shadow:
    0 8px 14px -6px rgba(0, 0, 0, 0.30),
    inset 0 1px 0 rgba(255, 255, 255, calc(0.10 + var(--bg-mix) * 0.12)),
    inset 0 -2px 6px rgba(0, 0, 0, calc(var(--bg-mix) * 0.18)) !important;
  border-color: rgba(255, 255, 255, 0.14) !important;
}

/* v947: пульсация фона пока Маккод думает + 10с инерция-decay после.
   Анимация навешена на контейнер .aura-blobs — он не имеет собственного
   transform/filter (блобы внутри живут своей жизнью), поэтому композирует
   с дочерними translate-анимациями без конфликтов. Применяется ко всем
   темам где есть .with-aura (опт-ин через body.with-aura). */
/* v953: «интенсивно переливается пока думаю + плавно угасает 5с».
   Раньше pulse был слабый (brightness 1.08, saturate 1.18) и без смены
   цвета — Кирилл хотел чувствовать «переливание». Добавили hue-rotate
   ±18°, усилили амплитуду яркости/насыщенности, ускорили цикл до 1.8с.
   Decay сжали с 10с до 5с — пожелание буквальное. */
/* v1176: амплитуда поднята (Кирилл «усиль свечение когда думаешь»):
   scale 1.055→1.08, brightness 1.22→1.34, saturate 1.55→1.72, hue 22→26°. */
@keyframes aura-thinking-pulse {
  0%, 100% { transform: scale(1);    filter: brightness(1)    saturate(1)    hue-rotate(0deg);  }
  50%      { transform: scale(1.08); filter: brightness(1.34) saturate(1.72) hue-rotate(26deg); }
}
/* 5s decay: 3 затухающих волны + финальный rest. Огибающая возвращает
   и яркость, и hue к норме. 0% совпадает с новым пиком пульса. */
@keyframes aura-thinking-decay {
  0%    { transform: scale(1.08);  filter: brightness(1.34) saturate(1.72) hue-rotate(26deg);  }
  20%   { transform: scale(1.040); filter: brightness(1.14) saturate(1.30) hue-rotate(14deg);  }
  45%   { transform: scale(1.018); filter: brightness(1.05) saturate(1.12) hue-rotate(6deg);   }
  70%   { transform: scale(1.008); filter: brightness(1.02) saturate(1.05) hue-rotate(2deg);   }
  100%  { transform: scale(1);     filter: brightness(1)    saturate(1)    hue-rotate(0deg);   }
}
body.with-aura.aura-thinking #aura-bg .aura-blobs {
  animation: aura-thinking-pulse 1.8s ease-in-out infinite;
  transform-origin: 50% 50%;
  will-change: transform, filter;
}
body.with-aura.aura-thinking-decay #aura-bg .aura-blobs {
  animation: aura-thinking-decay 5s cubic-bezier(0.25, 0.1, 0.25, 1) forwards;
  transform-origin: 50% 50%;
  will-change: transform, filter;
}
/* v951: в светлой теме блобы светлее (opacity 0.62) — стандартная амплитуда
   пульсации (scale 1.035, brightness 1.08) на светлой подложке еле различима.
   Кирилл: «пока ты думаешь — нужно чтобы фон пульсировал. Сейчас этого нет».
   В light играем тот же эффект с удвоенной амплитудой scale/brightness. */
/* v953: то же усиление в light — амплитуда +50% относительно dark (overlay
   светлый, эффект гасится). hue-rotate усилен до 28° — на светлом
   градиенте смены оттенка читаются хуже. */
@keyframes aura-thinking-pulse-light {
  0%, 100% { transform: scale(1);    filter: brightness(1)    saturate(1)    hue-rotate(0deg);  }
  50%      { transform: scale(1.09); filter: brightness(1.32) saturate(1.75) hue-rotate(28deg); }
}
@keyframes aura-thinking-decay-light {
  0%    { transform: scale(1.09);  filter: brightness(1.32) saturate(1.75) hue-rotate(28deg); }
  20%   { transform: scale(1.055); filter: brightness(1.16) saturate(1.38) hue-rotate(15deg); }
  45%   { transform: scale(1.030); filter: brightness(1.08) saturate(1.18) hue-rotate(8deg);  }
  70%   { transform: scale(1.014); filter: brightness(1.03) saturate(1.07) hue-rotate(3deg);  }
  100%  { transform: scale(1);     filter: brightness(1)    saturate(1)    hue-rotate(0deg);  }
}
:root.theme-light body.with-aura.aura-thinking #aura-bg .aura-blobs {
  animation: aura-thinking-pulse-light 1.8s ease-in-out infinite;
}
:root.theme-light body.with-aura.aura-thinking-decay #aura-bg .aura-blobs {
  animation: aura-thinking-decay-light 5s cubic-bezier(0.25, 0.1, 0.25, 1) forwards;
}
@media (prefers-reduced-motion: reduce) {
  body.with-aura.aura-thinking #aura-bg .aura-blobs,
  body.with-aura.aura-thinking-decay #aura-bg .aura-blobs {
    animation: none;
  }
}

/* ===== Finger-ghost: подсветка под пальцем во время свайпов ===== */
/* Один общий элемент на весь документ. Позиция движется без transition
   (мгновенно за пальцем), а opacity/scale пузыря — с плавным переходом.
   v993: переход на «каплю стекла» — soft radial-mask убрала чёткий обод
   круга, верхний блик + лёгкая нижняя тень имитируют выпуклую линзу,
   усиленный backdrop-filter (blur+saturate+contrast) даёт ощущение
   преломления поверхности под касанием. */
#finger-ghost {
  position: fixed;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 2147483600;
  transform: translate3d(-9999px, -9999px, 0);
  will-change: transform;
  opacity: 0;
  transition: opacity 220ms ease;
}
#finger-ghost.is-active { opacity: 1; }
#finger-ghost .finger-ghost-bubble {
  position: absolute;
  left: -80px;
  top: -80px;
  width: 160px;
  height: 160px;
  /* Преломление поверхности под «каплей» — главный визуальный сигнал:
     stronger blur делает фон под пальцем размытым (как смотришь сквозь
     стекло), saturate/contrast усиливают цвета, brightness даёт лёгкий
     «свет в линзе». */
  backdrop-filter: blur(9px) saturate(1.3) contrast(0.96) brightness(1.03);
  -webkit-backdrop-filter: blur(9px) saturate(1.3) contrast(0.96) brightness(1.03);
  /* Два слоя radial-gradient:
     1) Верхний блик-highlight (центр смещён к 28% сверху) — даёт выпуклость капли.
     2) Нижняя лёгкая тень — отделяет каплю от поверхности, добавляет глубину. */
  background:
    radial-gradient(
      ellipse 55% 40% at 50% 28%,
      rgba(255, 255, 255, 0.32) 0%,
      rgba(255, 255, 255, 0.14) 45%,
      rgba(255, 255, 255, 0.00) 80%
    ),
    radial-gradient(
      ellipse 70% 42% at 50% 88%,
      rgba(0, 0, 0, 0.10) 0%,
      rgba(0, 0, 0, 0.00) 70%
    );
  /* Soft edges — мягкая радиальная маска заменяет border-radius. Backdrop-filter
     постепенно сходит на нет к краю, нет чёткой границы круга. */
  -webkit-mask-image: radial-gradient(
    circle at 50% 50%,
    rgba(0, 0, 0, 1) 0%,
    rgba(0, 0, 0, 0.96) 32%,
    rgba(0, 0, 0, 0.58) 58%,
    rgba(0, 0, 0, 0.18) 78%,
    rgba(0, 0, 0, 0) 92%
  );
  mask-image: radial-gradient(
    circle at 50% 50%,
    rgba(0, 0, 0, 1) 0%,
    rgba(0, 0, 0, 0.96) 32%,
    rgba(0, 0, 0, 0.58) 58%,
    rgba(0, 0, 0, 0.18) 78%,
    rgba(0, 0, 0, 0) 92%
  );
  transform: scale(0.5);
  opacity: 0.95;
  transition:
    transform 240ms cubic-bezier(0.2, 0.8, 0.2, 1),
    opacity 220ms ease;
}
#finger-ghost.is-active .finger-ghost-bubble {
  transform: scale(1);
  opacity: 1;
}
/* В светлой теме — инвертируем баланс: верхний блик чуть ярче (тёмный фон даёт
   больше контраста на светлом), нижняя тень глубже. */
:root.theme-light #finger-ghost .finger-ghost-bubble,
:root.theme-glass.theme-light #finger-ghost .finger-ghost-bubble {
  backdrop-filter: blur(9px) saturate(1.18) contrast(0.98) brightness(0.97);
  -webkit-backdrop-filter: blur(9px) saturate(1.18) contrast(0.98) brightness(0.97);
  background:
    radial-gradient(
      ellipse 55% 40% at 50% 28%,
      rgba(255, 255, 255, 0.45) 0%,
      rgba(255, 255, 255, 0.16) 45%,
      rgba(255, 255, 255, 0.00) 80%
    ),
    radial-gradient(
      ellipse 70% 42% at 50% 88%,
      rgba(0, 0, 0, 0.13) 0%,
      rgba(0, 0, 0, 0.00) 70%
    );
}
/* Элемент, который сейчас тянут — слегка «вдавлен»: scale 0.992 через
   individual property (не конфликтует с translate3d в transform), плюс
   filter для лёгкого затемнения. Класс ставит JS в каждом свайп-хендлере
   при `started=true`, снимает в end/cancel. */
.finger-pressed-target {
  filter: brightness(0.97) saturate(1.04);
  scale: 0.992;
  transition:
    filter 200ms ease,
    scale 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
  transform-origin: var(--press-x, 50%) var(--press-y, 50%);
}
@media (prefers-reduced-motion: reduce) {
  #finger-ghost,
  #finger-ghost .finger-ghost-bubble,
  .finger-pressed-target { transition: none; }
}

/* Кнопки под ответом Маккода: «озвучить» (TTS) + «копировать» — как в ChatGPT.
   Borderless приглушённые иконки под баблом; на hover ярче, активная озвучка —
   акцентом. Тема-нейтрально через переменные с фолбэками. */
.msg.bot > .msg-actions {
  display: flex;
  gap: 2px;
  margin-top: 5px;
  margin-left: -5px; /* визуально подвести иконку к левому краю текста */
}
.msg.bot > .msg-actions .msg-act-btn {
  -webkit-appearance: none;
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  margin: 0;
  background: transparent;
  border: none;
  border-radius: 7px;
  color: var(--fg-dim, #8a8a8a);
  opacity: 0.72;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color 140ms ease, background 140ms ease, opacity 140ms ease,
    transform 110ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.msg.bot > .msg-actions .msg-act-btn:hover {
  color: var(--fg, #e8e8e8);
  opacity: 1;
  background: var(--hover-bg, rgba(127, 127, 127, 0.12));
}
.msg.bot > .msg-actions .msg-act-btn:active { transform: scale(0.88); }
.msg.bot > .msg-actions .msg-act-btn.speaking,
.msg.bot > .msg-actions .msg-act-btn.done {
  color: var(--accent, #d97757);
  opacity: 1;
}
.msg.bot > .msg-actions .msg-act-btn svg { display: block; }

/* ===== Режим энергосбережения (html.power-save) =====================
   Кирилл: «оригинал внешнего вида и функций, но отключение всех анимаций и
   вибро». Поэтому НЕ трогаем blur/стекло/aura/фон — внешний вид остаётся как
   в обычном режиме. Глушим только движение: все keyframe-анимации и все
   transition'ы. Aura-слой остаётся виден, но застывает (его анимация тоже
   погашена этим же правилом). Вибро отключается в hapticsAllowed() (app.js).
   Стоит в самом конце файла, чтобы перебивать в т.ч. glass-правила. Тумблер —
   Настройки → Энергосбережение; авто-вкл при батарее <30% (app.js). */
html.power-save *,
html.power-save *::before,
html.power-save *::after {
  animation-duration: 0.001s !important;
  animation-delay: 0s !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.001s !important;
  transition-delay: 0s !important;
}
